def simple_nodes_disjoint_grid(): """ return disjoint net plus nodes with fakes fakes are associated with disjoint subnets nodes by id (budget in parens) (5) 0-------1 (5) | | +-+-+ +-+-+ <-- disjoint existing grid Useful for testing treating existing grid as single grid vs disjoint """ # setup grid grid_coords = np.array([[-1.0, 0.0], [1.0, 0.0], [3.0, 0.0], [5.0, 0.0]]) grid = GeoGraph(gm.PROJ4_FLAT_EARTH, {'grid-' + str(n): c for n, c in enumerate(grid_coords)}) nx.set_node_attributes(grid, 'budget', {n: 0 for n in grid.nodes()}) grid.add_edges_from([('grid-0', 'grid-1'), ('grid-2', 'grid-3')]) # setup input nodes node_coords = np.array([[0.0, 1.0], [4.0, 1.0]]) nodes = GeoGraph(gm.PROJ4_FLAT_EARTH, dict(enumerate(node_coords))) budget_values = [5, 5] nx.set_node_attributes(nodes, 'budget', dict(enumerate(budget_values))) fakes = [2, 3] return grid, nodes, fakes
def nodes_plus_existing_grid(): """ return net plus existing grid with certain properties for testing nodes by id (budget in parens) 1 (3) \ \ 0 (2) | 2 (5) | | +-+-+-+-+-+-+-+-+-+-+ <-- existing grid """ # setup grid grid_coords = np.array([[-5.0, 0.0], [5.0, 0.0]]) grid = GeoGraph(gm.PROJ4_FLAT_EARTH, {'grid-' + str(n): c for n, c in enumerate(grid_coords)}) nx.set_node_attributes(grid, 'budget', {n: 0 for n in grid.nodes()}) grid.add_edges_from([('grid-0', 'grid-1')]) # setup input nodes node_coords = np.array([[0.0, 2.0], [-1.0, 4.0], [4.0, 1.0]]) nodes = GeoGraph(gm.PROJ4_FLAT_EARTH, dict(enumerate(node_coords))) budget_values = [2, 3, 5] nx.set_node_attributes(nodes, 'budget', dict(enumerate(budget_values))) # setup resulting edges when creating msf through the sequence of nodes # Note: Fake nodes integer label begins at the total number of nodes + 1 # Hence why the fake node in the test is incremented by one on each # iteration edges_at_iteration = [ [(0, 1)], # 0 connects to fake_node [(0, 2)], # 0, 1 can't connect [(0, 3), (2, 5), (1, 0)] ] # 2 connects grid return grid, nodes, edges_at_iteration
def nodes_plus_existing_grid(): """ return net plus existing grid with certain properties for testing nodes by id (budget in parens) 1 (3) \ \ 0 (2) | 2 (5) | | +-+-+-+-+-+-+-+-+-+-+ <-- existing grid """ # setup grid grid_coords = np.array([[-5.0, 0.0], [5.0, 0.0]]) grid = GeoGraph(gm.PROJ4_FLAT_EARTH, {'grid-' + str(n): c for n, c in enumerate(grid_coords)}) nx.set_node_attributes(grid, 'budget', {n: 0 for n in grid.nodes()}) grid.add_edges_from([('grid-0', 'grid-1')]) # setup input nodes node_coords = np.array([[0.0, 2.0], [-1.0, 4.0], [4.0, 1.0]]) nodes = GeoGraph(gm.PROJ4_FLAT_EARTH, dict(enumerate(node_coords))) budget_values = [2, 3, 5] nx.set_node_attributes(nodes, 'budget', dict(enumerate(budget_values))) # setup resulting edges when creating msf through the sequence of nodes # Note: Fake nodes integer label begins at the total number of nodes + 1 # Hence why the fake node in the test is incremented by one on each # iteration edges_at_iteration = [[(0, 1)], # 0 connects to fake_node [(0, 2)], # 0, 1 can't connect [(0, 3), (2, 5), (1, 0)]] # 2 connects grid return grid, nodes, edges_at_iteration
def build_network(demand_nodes, existing=None, min_node_count=2, single_network=True, network_algorithm='mod_boruvka', one_based=False ): """ project demand nodes onto optional existing supply network and return the 'optimized' network Args: demand_nodes: GeoGraph of demand nodes existing: GeoGraph of existing grid (assumes node ids don't conflict with demand_nodes min_node_count: minimum number of nodes allowed in a subgraph of the result network_algorithm: Algorithm from ALGOS to run one_based: Whether result GeoGraph's nodes should be one_based (if not, they are 0 based) Returns: msf: GeoGraph of minimum spanning forest proposed by the chosen network algorithm existing: The existing grid GeoGraph (None if it doesn't exist) """ geo_graph = subgraphs = rtree = None if existing: log.info("merging network and nodes") geo_graph, subgraphs, rtree = \ merge_network_and_nodes(existing, demand_nodes, single_network=single_network) else: geo_graph = demand_nodes log.info("running {} on {} demand nodes and {} total nodes".format( network_algorithm, len(demand_nodes), len(geo_graph))) # now run the selected algorithm network_algo = NetworkerRunner.ALGOS[network_algorithm] result_geo_graph = network_algo(geo_graph, subgraphs=subgraphs, rtree=rtree) # TODO: Remove unreferenced fake nodes? # now filter out subnetworks via minimum node count # TODO: update union_all to support GeoGraph? filtered_graph = nx.union_all(filter( lambda sub: len(sub.node) >= min_node_count, nx.connected_component_subgraphs(result_geo_graph))) # map coords back to geograph # NOTE: explicit relabel to int as somewhere in filtering above, some # node ids are set to numpy types which screws up comparisons to tuples # in write op # NOTE: relabeling nodes in-place here drops node attributes for some # reason so create a copy for now def id_label(i): id = int(i+1) if one_based else int(i) return id msf = None if filtered_graph: coords = {id_label(i): result_geo_graph.coords[i] for i in filtered_graph} relabeled = nx.relabel_nodes(filtered_graph, {i: id_label(i) for i in filtered_graph}, copy=True) msf = GeoGraph(result_geo_graph.srs, coords=coords, data=relabeled) log.info("filtered result has {} nodes and {} edges".format( len(msf.nodes()), len(msf.edges()))) return msf
def build_network(demand_nodes, existing=None, min_node_count=2, single_network=True, network_algorithm='mod_boruvka', spherical_accuracy=False, one_based=False): """ project demand nodes onto optional existing supply network and return the 'optimized' network Args: demand_nodes: GeoGraph of demand nodes existing: GeoGraph of existing grid (assumes node ids don't conflict with demand_nodes min_node_count: minimum number of nodes allowed in a subgraph of the result network_algorithm: Algorithm from ALGOS to run spherical_accuracy: Whether to connect nodes to network on a sphere one_based: Whether result GeoGraph's nodes should be one_based (if not, they are 0 based) Returns: msf: GeoGraph of minimum spanning forest proposed by the chosen network algorithm existing: The existing grid GeoGraph (None if it doesn't exist) """ geo_graph = subgraphs = rtree = None if existing: log.info("merging network and nodes") geo_graph, subgraphs, rtree = \ merge_network_and_nodes(existing, demand_nodes, single_network=single_network, spherical_accuracy=spherical_accuracy) else: geo_graph = demand_nodes log.info("running {} on {} demand nodes and {} total nodes".format( network_algorithm, len(demand_nodes), len(geo_graph))) # now run the selected algorithm network_algo = NetworkerRunner.ALGOS[network_algorithm] result_geo_graph = network_algo(geo_graph, subgraphs=subgraphs, rtree=rtree) filtered_graph = filter_min_node_subnetworks(result_geo_graph, min_node_count) # map coords back to geograph # NOTE: explicit relabel to int as somewhere in filtering above, some # node ids are set to numpy types which screws up comparisons to tuples # in write op # NOTE: relabeling nodes in-place here drops node attributes for some # reason so create a copy for now def id_label(i): id = int(i+1) if one_based else int(i) return id msf = GeoGraph(result_geo_graph.srs) if filtered_graph: coords = {id_label(i): result_geo_graph.coords[i] for i in filtered_graph} relabeled = nx.relabel_nodes(filtered_graph, {i: id_label(i) for i in filtered_graph}, copy=True) msf = GeoGraph(result_geo_graph.srs, coords=coords, data=relabeled) log.info("filtered result has {} nodes and {} edges".format( len(msf.nodes()), len(msf.edges()))) return msf
def nodes_plus_grid(): """ Return: nodes as graph and grid as UnionFind/Rtree combo This example input demonstrates the "more" optimal nature of mod_boruvka vs mod_kruskal. 2(10) | 1(4) | sqrt(5){ /| | / | | / | }3 | } 5 (2)0 | | 1{| | | +-+-3-+-4-+-+-+-+-+-+-+5-+-+-+ <-- existing grid In this case, all nodes will be connected via either algorithm, but the graph produced by mod_kruskal will have edge (4,1) whereas mod_boruvka will produce a graph with edge (0,1). Therefore, the mod_boruvka graph is more optimal. """ mv_max_values = [2, 4, 10] coords = np.array([[0.0, 1.0], [1.0, 3.0], [10.0, 5.0]]) coords_dict = dict(enumerate(coords)) nodes = GeoGraph(gm.PROJ4_FLAT_EARTH, coords=coords_dict) nx.set_node_attributes(nodes, 'budget', dict(enumerate(mv_max_values))) grid_coords = np.array([[-5.0, 0.0], [15.0, 0.0]]) grid = GeoGraph(gm.PROJ4_FLAT_EARTH, {'grid-' + str(n): c for n, c in enumerate(grid_coords)}) nx.set_node_attributes(grid, 'budget', {n: 0 for n in grid.nodes()}) grid.add_edges_from([('grid-0', 'grid-1')]) # now find projections onto grid rtree = grid.get_rtree_index() projected = grid.project_onto(nodes, rtree_index=rtree) projected.remove_nodes_from(grid) projected.remove_nodes_from(nodes) # populate disjoint set of subgraphs subgraphs = UnionFind() # only one connected component, so just associate all nodes # with first node of grid parent = grid.nodes()[0] subgraphs.add_component(parent, budget=grid.node[parent]['budget']) for node in grid.nodes()[1:]: subgraphs.add_component(node, budget=grid.node[node]['budget']) subgraphs.union(parent, node, 0) # and the projected "fake" nodes for node in projected.nodes(): subgraphs.add_component(node, budget=np.inf) subgraphs.union(parent, node, 0) # add projected nodes to node set nodes.add_nodes_from(projected, budget=np.inf) # merge coords nodes.coords = dict(nodes.coords, **projected.coords) return nodes, subgraphs, rtree