Example #1
0
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
Example #2
0
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
Example #3
0
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
Example #4
0
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
Example #7
0
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