code: def create_graph_from_adjacency_matrix(adjacency_matrix):
      
      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)
docs: 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