Active transport assignment#

We can also assign the resulting active transport trips to the walk and bike networks using AequilibraE

For this integration we have used [AequilibraE](https://www.aequilibrae.com/), which is an open source modelling package with incredibly fast static assignment capabilities, but similar integrations are possible with other packages.

sphinx_gallery_thumbnail_path = ‘../../examples/modelling_like_the_old_days/active_transport.png’

import numpy as np
import pandas as pd
from aequilibrae.matrix import AequilibraeMatrix
from aequilibrae.paths import TrafficAssignment, TrafficClass
from scipy.sparse import coo_matrix

from polaris import Polaris
from polaris.runs.static_assignment.static_active_graph import ActiveGraph
from polaris.utils.testing.temp_model import TempModel

We create a new model directory with a grid network that has already been run

project_dir = TempModel("Grid")
pol = Polaris.from_dir(project_dir)

Before anything else, we build the graphs

ag = ActiveGraph(pol.supply_file)
walk_graph = ag.walkgraph
bike_graph = ag.bikegraph
/venv-py312/lib/python3.12/site-packages/geopandas/array.py:1770: UserWarning: CRS not set for some of the concatenation inputs. Setting output's CRS as NAD83 / UTM zone 16N (the single non-null crs provided).
  return GeometryArray(data, crs=_get_common_crs(to_concat))
/venv-py312/lib/python3.12/site-packages/aequilibrae/paths/graph.py:218: ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment.
Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write).

Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.

See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignment
  build_compressed_graph(self, remove_dead_ends)
/venv-py312/lib/python3.12/site-packages/geopandas/array.py:1770: UserWarning: CRS not set for some of the concatenation inputs. Setting output's CRS as NAD83 / UTM zone 16N (the single non-null crs provided).
  return GeometryArray(data, crs=_get_common_crs(to_concat))
/venv-py312/lib/python3.12/site-packages/aequilibrae/paths/graph.py:218: ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment.
Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write).

Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.

See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignment
  build_compressed_graph(self, remove_dead_ends)

Cleaning the network#

The bike and walk networks may not be fully connected, so let’s make sure we only assign trips between nodes that are part of the main network

walk_graph.set_graph("time")
walk_graph.set_skimming(["time"])

all_nodes = ag.nodes.node.to_numpy()

res = walk_graph.compute_path(all_nodes[0], all_nodes[1])

res.predecessors[walk_graph.nodes_to_indices[all_nodes[0]]] = 0
connected = walk_graph.all_nodes[np.where(res.predecessors >= 0)]
missing_nodes = np.setdiff1d(all_nodes, connected)

# So we got the actual "main island" of the network
assert connected.shape[0] > 0.95 * all_nodes.shape[0]

print(f"Disconnected nodes: {missing_nodes.shape[0]:,}")
Disconnected nodes: 0
locs = pol.network.tables.get("Location")
nodes = ag.nodes[ag.nodes.node.isin(connected)]

Grab the trips#

walk_trips = pol.demand.tables.get("Trip", filter='mode in (8)')
bike_trips = pol.demand.tables.get("Trip", filter='mode in (7)')

Functions to process trips intro matrices and assign them to the graph#

def transform_trips(locs, nodes, df_trips, graph):
    valid_nodes = np.hstack([graph.network.a_node, graph.network.b_node])
    nodes =nodes[nodes.node.isin(valid_nodes)]
    node_loc = locs[["location", "geo"]].sjoin_nearest(nodes[["node", "geo"]])[["node", "location"]]
    node_loc1 = node_loc.rename(columns={"location": "origin", "node":"orig_node"})
    node_loc2 = node_loc.rename(columns={"location": "destination", "node":"dest_node"})

    df_trips2 = df_trips[["origin", "destination"]].merge(node_loc1, on="origin").merge(node_loc2, on="destination")
    df_trips2 = df_trips2.groupby(["orig_node", "dest_node"]).size().reset_index()
    df_trips2.columns = ["origin", "destination", "trips"]
    centroids = np.unique(np.hstack([df_trips2.origin.unique(), df_trips2.destination.unique()]))

    df1 = pd.DataFrame({"origin": centroids, "orig_node": np.arange(centroids.shape[0])})
    df2 = pd.DataFrame({"destination": centroids, "dest_node": np.arange(centroids.shape[0])})
    df = df_trips2.merge(df1, on="origin").merge(df2, on="destination")

    coo_ = coo_matrix((df.trips.values, (df.orig_node.values, df.dest_node.values)))
    demand_mat = AequilibraeMatrix()
    demand_mat.create_empty(zones=centroids.shape[0], matrix_names=["trips"], memory_only=True)

    demand_mat.index[:] = centroids[:]
    demand_mat.matrices[:, :] = 0
    demand_mat.matrices[:, :, 0] = coo_.todense()[:, :]
    return demand_mat

def assign(graph, matrix):
    matrix.computational_view(["trips"])
    graph.prepare_graph(matrix.index)
    graph.set_blocked_centroid_flows(False)
    graph.set_skimming([])

    # Create the assignment class
    assigclass = TrafficClass(name="all_trips", graph=graph, matrix=matrix)

    assig = TrafficAssignment()

    # We start by adding the list of traffic classes to be assigned
    assig.add_class(assigclass)

    # This stuff we don't need, but the API requires
    # --------------------------------------
    assig.set_capacity_field("distance")
    assig.set_vdf("BPR")  # This is not case-sensitive
    assig.set_vdf_parameters({"alpha": 0.15, "beta": 4.0})
    # --------------------------------------

    assig.set_time_field("time")

    # And the algorithm we want to use to assign
    assig.set_algorithm("all-or-nothing")

    # Let's set parameters that make this example run very fast
    assig.max_iter = 1
    assig.rgap_target = 0.01

    # we then execute the assignment
    assig.execute()
    return assig.results().reset_index()

Assign Walk trips#

cols = []
walk_trips_mat = transform_trips(locs, nodes, walk_trips, walk_graph)
walk_link_loads = assign(walk_graph, walk_trips_mat)[["link_id", "trips_ab", "trips_ba", "trips_tot"]]

walk_links = pol.network.tables.get("Transit_Walk").rename(columns={"walk_link":"link_id"})[["link_id", "geo"]]
walk_links = walk_links.merge(walk_link_loads, on="link_id")

factor = 30 / walk_links.trips_tot.max()

walk_links.to_crs(epsg=4326).explore(
        color="blue",
        style_kwds={
            "style_function": lambda x: {
                "weight": x["properties"]["trips_tot"] * factor,
            }
        },
    )
/venv-py312/lib/python3.12/site-packages/aequilibrae/paths/graph.py:218: ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment.
Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write).

Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.

See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignment
  build_compressed_graph(self, remove_dead_ends)
all_trips                                         :   0%|          | 0/173 [00:00<?, ?it/s]
Equilibrium Assignment                            :   0%|          | 0/1 [00:00<?, ?it/s]
Make this Notebook Trusted to load map: File -> Trust Notebook


Assign Bike trips#

bike_trips_mat = transform_trips(locs, nodes, bike_trips, bike_graph)
bike_link_loads = assign(bike_graph, bike_trips_mat)[["link_id", "trips_ab", "trips_ba", "trips_tot"]]

bike_links = pol.network.tables.get("Transit_Bike").rename(columns={"bike_link":"link_id"})[["link_id", "geo"]]
bike_links = bike_links.merge(bike_link_loads, on="link_id")

factor = 30 / bike_links.trips_tot.max()

bike_links.explore(
        color="red",
        style_kwds={
            "style_function": lambda x: {
                "weight": x["properties"]["trips_tot"] * factor,
            }
        },
    )
/venv-py312/lib/python3.12/site-packages/aequilibrae/paths/graph.py:218: ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment.
Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write).

Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.

See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignment
  build_compressed_graph(self, remove_dead_ends)
all_trips                                         :   0%|          | 0/175 [00:00<?, ?it/s]
Equilibrium Assignment                            :   0%|          | 0/1 [00:00<?, ?it/s]
Make this Notebook Trusted to load map: File -> Trust Notebook


Total running time of the script: (0 minutes 4.939 seconds)

Gallery generated by Sphinx-Gallery