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/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))

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,
            }
        },
    )
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,
            }
        },
    )
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 5.768 seconds)

Gallery generated by Sphinx-Gallery