Generates a graph from an adjacency matrix specification.
       Returns a tuple containing the graph and a list-mapping of node ids to matrix column indices.

       The graph will be an UndirectedGraph if the provided adjacency matrix is symmetric.
       The graph will be a DirectedGraph if the provided adjacency matrix is not symmetric.
       Ref: http://mathworld.wolfram.com/AdjacencyMatrix.html

def create_graph_from_adjacency_matrix(adjacency_matrix):
    """Generates a graph from an adjacency matrix specification.
       Returns a tuple containing the graph and a list-mapping of node ids to matrix column indices.

       The graph will be an UndirectedGraph if the provided adjacency matrix is symmetric.
       The graph will be a DirectedGraph if the provided adjacency matrix is not symmetric.
       Ref: http://mathworld.wolfram.com/AdjacencyMatrix.html"""
    if is_adjacency_matrix_symmetric(adjacency_matrix):
        graph = UndirectedGraph()
    else:
        graph = DirectedGraph()

    node_column_mapping = []

    num_columns = len(adjacency_matrix)
    for _ in range(num_columns):
        node_id = graph.new_node()
        node_column_mapping.append(node_id)

    for j in range(num_columns):
        for i in range(num_columns):
            if adjacency_matrix[j][i]:
                jnode_id = node_column_mapping[j]
                inode_id = node_column_mapping[i]
                # Because of our adjacency matrix encoding, [j][i] in our code corresponds to [i][j] in a traditional matrix interpretation
                # Thus, we need to put an edge from node i to node j if [j][i] in our code is non-zero
                graph.new_edge(inode_id, jnode_id)

    return (graph, node_column_mapping)