docs: def simplify_graph(G, strict=True):
      """
      Simplify a graph's topology by removing all nodes that are not intersections
      or dead-ends.
  
      Create an edge directly between the end points that encapsulate them,
      but retain the geometry of the original edges, saved as attribute in new
      edge.
  
      Parameters
      ----------
      G : networkx multidigraph
      strict : bool
          if False, allow nodes to be end points even if they fail all other rules
          but have edges with different OSM IDs
  
      Returns
      -------
      networkx multidigraph
      """
  
      if is_simplified(G):
          raise Exception('This graph has already been simplified, cannot simplify it again.')
  
      log('Begin topologically simplifying the graph...')
      G = G.copy()
      initial_node_count = len(list(G.nodes()))
      initial_edge_count = len(list(G.edges()))
      all_nodes_to_remove = []
      all_edges_to_add = []
  
      # construct a list of all the paths that need to be simplified
      paths = get_paths_to_simplify(G, strict=strict)
  
      start_time = time.time()
      for path in paths:
  
          # add the interstitial edges we're removing to a list so we can retain
          # their spatial geometry
          edge_attributes = {}
          for u, v in zip(path[:-1], path[1:]):
  
              # there shouldn't be multiple edges between interstitial nodes
              if not G.number_of_edges(u, v) == 1:
                  log('Multiple edges between "{}" and "{}" found when simplifying'.format(u, v), level=lg.WARNING)
  
              # the only element in this list as long as above check is True
              # (MultiGraphs use keys (the 0 here), indexed with ints from 0 and
              # up)
              edge = G.edges[u, v, 0]
              for key in edge:
                  if key in edge_attributes:
                      # if this key already exists in the dict, append it to the
                      # value list
                      edge_attributes[key].append(edge[key])
                  else:
                      # if this key doesn't already exist, set the value to a list
                      # containing the one value
                      edge_attributes[key] = [edge[key]]
  
          for key in edge_attributes:
              # don't touch the length attribute, we'll sum it at the end
              if len(set(edge_attributes[key])) == 1 and not key == 'length':
                  # if there's only 1 unique value in this attribute list,
                  # consolidate it to the single value (the zero-th)
                  edge_attributes[key] = edge_attributes[key][0]
              elif not key == 'length':
                  # otherwise, if there are multiple values, keep one of each value
                  edge_attributes[key] = list(set(edge_attributes[key]))
  
          # construct the geometry and sum the lengths of the segments
          edge_attributes['geometry'] = LineString([Point((G.nodes[node]['x'], G.nodes[node]['y'])) for node in path])
          edge_attributes['length'] = sum(edge_attributes['length'])
  
          # add the nodes and edges to their lists for processing at the end
          all_nodes_to_remove.extend(path[1:-1])
          all_edges_to_add.append({'origin':path[0],
                                   'destination':path[-1],
                                   'attr_dict':edge_attributes})
  
      # for each edge to add in the list we assembled, create a new edge between
      # the origin and destination
      for edge in all_edges_to_add:
          G.add_edge(edge['origin'], edge['destination'], **edge['attr_dict'])
  
      # finally remove all the interstitial nodes between the new edges
      G.remove_nodes_from(set(all_nodes_to_remove))
  
      G.graph['simplified'] = True
  
      msg = 'Simplified graph (from {:,} to {:,} nodes and from {:,} to {:,} edges) in {:,.2f} seconds'
      log(msg.format(initial_node_count, len(list(G.nodes())), initial_edge_count, len(list(G.edges())), time.time()-start_time))
      return G