def grid_and_non_grid(): """ return networkplan GeoGraph with grid and non-grid components 0 2 | | +-6-+ 1 3-4-5 (inf) where node 3 is a fake node connecting node 0 to the grid """ node_coords = np.array([[0.0, 1.0], [4.0, 0.0], [4.0, 1.0], [5.0, 0.0], [6.0, 0.0], [7.0, 0.0], [0.0, 0.0]]) grid = GeoGraph(gm.PROJ4_FLAT_EARTH, dict(enumerate(node_coords))) budget_values = [5, 5, 5, 5, 5, 5, np.inf] nx.set_node_attributes(grid, 'budget', dict(enumerate(budget_values))) grid.add_edges_from([(0, 6), (1, 2), (3, 4), (4, 5)]) return grid
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 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 dataset_store_to_geograph(dataset_store): """ convenience function for converting a network stored in a dataset_store into a GeoGraph Args: dataset_store containing a network Returns: GeoGraph representation of dataset_store network TODO: determine projection from dataset_store? """ all_nodes = list(dataset_store.cycleNodes()) + \ list(dataset_store.cycleNodes(isFake=True)) # nodes in output GeoGraph are id'd from 0 to n (via enumerate) np_to_nx_id = {node.id: i for i, node in enumerate(all_nodes)} coords = [node.getCommonCoordinates() for node in all_nodes] coords_dict = dict(enumerate(coords)) G = GeoGraph(coords=coords_dict) # only set population, system and budget for now # TODO: Do we need all from the output? for i, node in enumerate(all_nodes): if not node.is_fake: properties = { 'budget': node.metric, 'population': node.output['demographics']['population count'], 'system': node.output['metric']['system'] } G.node[i] = properties def seg_to_nx_ids(seg): """ Return the networkx segment ids """ return (np_to_nx_id[seg.node1_id], np_to_nx_id[seg.node2_id]) edges = [seg_to_nx_ids(s) for s in dataset_store.cycleSegments(is_existing=False)] edge_weights = {seg_to_nx_ids(s): s.weight for s in dataset_store.cycleSegments(is_existing=False)} edge_is_existing = {seg_to_nx_ids(s): s.is_existing for s in dataset_store.cycleSegments(is_existing=False)} edge_subnet_id = {seg_to_nx_ids(s): s.subnet_id for s in dataset_store.cycleSegments(is_existing=False)} G.add_edges_from(edges) nx.set_edge_attributes(G, 'weight', edge_weights) nx.set_edge_attributes(G, 'is_existing', edge_is_existing) nx.set_edge_attributes(G, 'subnet_id', edge_subnet_id) return G
def dataset_store_to_geograph(dataset_store): """ convenience function for converting a network stored in a dataset_store into a GeoGraph Args: dataset_store containing a network Returns: GeoGraph representation of dataset_store network TODO: determine projection from dataset_store? """ all_nodes = list(dataset_store.cycleNodes()) + \ list(dataset_store.cycleNodes(isFake=True)) np_to_nx_id = {node.id: i for i, node in enumerate(all_nodes)} coords = [node.getCommonCoordinates() for node in all_nodes] coords_dict = dict(enumerate(coords)) budget_dict = {i: node.metric for i, node in enumerate(all_nodes)} G = GeoGraph(coords=coords_dict) nx.set_node_attributes(G, 'budget', budget_dict) def seg_to_nx_ids(seg): """ Return the networkx segment ids """ return (np_to_nx_id[seg.node1_id], np_to_nx_id[seg.node2_id]) edges = [seg_to_nx_ids(s) for s in dataset_store.cycleSegments(is_existing=False)] edge_weights = {seg_to_nx_ids(s): s.weight for s in dataset_store.cycleSegments(is_existing=False)} edge_is_existing = {seg_to_nx_ids(s): s.is_existing for s in dataset_store.cycleSegments(is_existing=False)} edge_subnet_id = {seg_to_nx_ids(s): s.subnet_id for s in dataset_store.cycleSegments(is_existing=False)} G.add_edges_from(edges) nx.set_edge_attributes(G, 'weight', edge_weights) nx.set_edge_attributes(G, 'is_existing', edge_is_existing) nx.set_edge_attributes(G, 'subnet_id', edge_subnet_id) return G
def generate_mst(coords): """ Generate a min spanning tree based on coordinate distances """ input_proj = gm.PROJ4_LATLONG if gm.is_in_lon_lat(coords): input_proj = gm.PROJ4_LATLONG else: input_proj = gm.PROJ4_FLAT_EARTH node_dict = dict(enumerate(coords)) geo_nodes = GeoGraph(input_proj, node_dict) geo_full = geo_nodes.get_connected_weighted_graph() geo_mst = nx.minimum_spanning_tree(geo_full) geo_nodes.add_edges_from(geo_mst.edges(data=True)) return geo_nodes
def test_load_write_json(): """ ensure that reading/writing js 'node-link' format works """ os.mkdir('test/tmp') node_dict = {0: [0,0], 1: [0,1], 2: [1,0], 3: [1,1]} g = GeoGraph(gm.PROJ4_LATLONG, node_dict) g.add_edges_from([(0,1),(1,2),(2,3)]) nio.write_json(g, open('test/tmp/g.js', 'w')) g2 = nio.load_json(open('test/tmp/g.js', 'r')) os.remove('test/tmp/g.js') os.rmdir('test/tmp') assert nx.is_isomorphic(g, g2, node_match=operator.eq, edge_match=operator.eq),\ "expected written and read graphs to match"
def test_load_write_json(): """ ensure that reading/writing js 'node-link' format works """ os.mkdir(os.path.join('test', 'tmp')) node_dict = {0: [0,0], 1: [0,1], 2: [1,0], 3: [1,1]} g = GeoGraph(gm.PROJ4_LATLONG, node_dict) g.add_edges_from([(0,1),(1,2),(2,3)]) json_file_path = os.path.join('test', 'tmp', 'g.json') nio.write_json(g, open(json_file_path, 'w')) g2 = nio.read_json_geograph(json_file_path) os.remove(json_file_path) os.rmdir(os.path.join('test', 'tmp')) assert nx.is_isomorphic(g, g2, node_match=operator.eq, edge_match=operator.eq),\ "expected written and read graphs to match"
def grid_and_non_grid(): """ return networkplan GeoGraph with grid and non-grid components 0 2 | | +-6-+ 1 3-4-5 (inf) where node 3 is a fake node connecting node 0 to the grid """ node_coords = np.array([[0.0, 1.0], [4.0, 0.0], [4.0, 1.0], [5.0, 0.0], [6.0, 0.0], [7.0, 0.0], [0.0, 0.0]]) grid = GeoGraph(gm.PROJ4_FLAT_EARTH, dict(enumerate(node_coords))) budget_values = [5, 5, 5, 5, 5, 5, np.inf] nx.set_node_attributes(grid, 'budget', dict(enumerate(budget_values))) grid.add_edges_from([(0, 6), (1, 2), (3, 4), (4, 5)]) return grid
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 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
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
def dataset_store_to_geograph(dataset_store): """ convenience function for converting a network stored in a dataset_store into a GeoGraph Args: dataset_store containing a network Returns: GeoGraph representation of dataset_store network TODO: determine projection from dataset_store? """ all_nodes = list(dataset_store.cycleNodes()) + \ list(dataset_store.cycleNodes(isFake=True)) # nodes in output GeoGraph are id'd from 0 to n (via enumerate) np_to_nx_id = {node.id: i for i, node in enumerate(all_nodes)} coords = [node.getCommonCoordinates() for node in all_nodes] coords_dict = dict(enumerate(coords)) G = GeoGraph(coords=coords_dict) # only set population, system and budget for now # TODO: Do we need all from the output? for i, node in enumerate(all_nodes): if not node.is_fake: properties = { 'budget': node.metric, 'population': node.output['demographics']['population count'], 'system': node.output['metric']['system'] } G.node[i] = properties def seg_to_nx_ids(seg): """ Return the networkx segment ids """ return (np_to_nx_id[seg.node1_id], np_to_nx_id[seg.node2_id]) edges = [ seg_to_nx_ids(s) for s in dataset_store.cycleSegments(is_existing=False) ] edge_weights = { seg_to_nx_ids(s): s.weight for s in dataset_store.cycleSegments(is_existing=False) } edge_is_existing = { seg_to_nx_ids(s): s.is_existing for s in dataset_store.cycleSegments(is_existing=False) } edge_subnet_id = { seg_to_nx_ids(s): s.subnet_id for s in dataset_store.cycleSegments(is_existing=False) } G.add_edges_from(edges) nx.set_edge_attributes(G, 'weight', edge_weights) nx.set_edge_attributes(G, 'is_existing', edge_is_existing) nx.set_edge_attributes(G, 'subnet_id', edge_subnet_id) return G