Public transport assignment with Optimal Strategies#

In this example, perform a Spiess & Florian assignment. Click here to check out the paper.

We use data from Coquimbo, a city in La Serena Metropolitan Area in Chile.

References

# Imports for example construction
from uuid import uuid4
from os.path import join
from tempfile import gettempdir

from aequilibrae.transit import Transit
from aequilibrae.utils.create_example import create_example

Let’s create an empty project on an arbitrary folder.

fldr = join(gettempdir(), uuid4().hex)

project = create_example(fldr, "coquimbo")

Let’s create our Transit object.

data = Transit(project)

Graph building#

Let’s build the transit network. We’ll disable outer_stop_transfers and walking_edges because Coquimbo doesn’t have any parent stations.

For the OD connections we’ll use the overlapping_regions method and create some accurate line geometry later. Creating the graph should only take a moment. By default zoning information is pulled from the project network. If you have your own zoning information add it using graph.add_zones(zones) then graph.create_graph().

graph = data.create_graph(with_outer_stop_transfers=False, with_walking_edges=False, blocking_centroid_flows=False, connector_method="overlapping_regions")

# We drop geometry here for the sake of display.
graph.vertices.drop(columns="geometry")
node_id node_type stop_id line_id line_seg_idx taz_id
0 1 od -1 1
1 2 od -1 2
2 3 od -1 3
3 4 od -1 4
4 5 od -1 5
... ... ... ... ... ... ...
362 363 alighting 10000000075 1_10001003000 31 -1
363 364 alighting 10000000076 1_10001003000 32 -1
364 365 alighting 10000000077 1_10001003000 33 -1
365 366 alighting 10000000078 1_10001003000 34 -1
366 367 alighting 10000000053 1_10001003000 35 -1

367 rows × 6 columns



graph.edges
link_id link_type line_id stop_id line_seg_idx b_node a_node trav_time freq o_line_id d_line_id direction
0 1 on-board 1_10001001000 0 212 290 90.000000 inf 1
1 2 on-board 1_10001001000 1 213 291 90.000000 inf 1
2 3 on-board 1_10001001000 2 214 292 120.000000 inf 1
3 4 on-board 1_10001001000 3 215 293 120.000000 inf 1
4 5 on-board 1_10001001000 4 216 294 120.000000 inf 1
... ... ... ... ... ... ... ... ... ... ... ... ...
641 642 egress_connector -1 166 112 630.543431 inf 1
642 643 egress_connector -1 177 112 754.081137 inf 1
643 644 egress_connector -1 178 112 386.262027 inf 1
644 645 egress_connector -1 179 112 558.108038 inf 1
645 646 egress_connector -1 166 113 604.831967 inf 1

646 rows × 12 columns



The graphs also also stored in the Transit.graphs dictionary. They are keyed by the ‘period_id’ they were created for. A graph for a different ‘period_id’ can be created by providing period_id= in the Transit.create_graph call. You can view previously created periods with the Periods object.

periods = project.network.periods
periods.data
period_id period_start period_end period_description
0 1 0 86400 Default time period, whole day


Connector project matching#

project.network.build_graphs()
/home/runner/work/aequilibrae/aequilibrae/aequilibrae/paths/graph.py:248: UserWarning: Found centroids not present in the graph!
[  1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18
  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36
  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54
  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72
  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90
  91  92  93  94  95  96  97  98  99 100 101 102 103 104 105 106 107 108
 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
 127 128 129 130 131 132 133]
  warnings.warn("Found centroids not present in the graph!\n" + str(centroids[~present_centroids]))
/home/runner/work/aequilibrae/aequilibrae/aequilibrae/paths/graph.py:248: UserWarning: Found centroids not present in the graph!
[  1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18
  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36
  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54
  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72
  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90
  91  92  93  94  95  96  97  98  99 100 101 102 103 104 105 106 107 108
 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
 127 128 129 130 131 132 133]
  warnings.warn("Found centroids not present in the graph!\n" + str(centroids[~present_centroids]))

Now we’ll create the line strings for the access connectors, this step is optional but provides more accurate distance estimations and better looking geometry.

Because Coquimbo doesn’t have many walking edges we’ll match onto the "c" graph.

graph.create_line_geometry(method="connector project match", graph="c")
/home/runner/work/aequilibrae/aequilibrae/aequilibrae/transit/transit_graph_builder.py:1191: UserWarning: In its current implementation, the "connector project match" method may take a while for large networks.
  warnings.warn(

Saving and reloading#

Lets save all graphs to the ‘public_transport.sqlite’ database.

data.save_graphs()

We can reload the saved graphs with data.load. This will create new TransitGraphBuilder's based on the ‘period_id’ of the saved graphs. The graph configuration is stored in the ‘transit_graph_config’ table in ‘project_database.sqlite’ as serialised JSON.

data.load()

We can also remove the previously saved graphs.

# data.remove_graphs()

Links and nodes are stored in a similar manner to the ‘project_database.sqlite’ database.

Reading back into AequilibraE#

You can create back in a particular graph via it’s ‘period_id’.

from aequilibrae.project.database_connection import database_connection
from aequilibrae.transit.transit_graph_builder import TransitGraphBuilder
pt_con = database_connection("transit")

graph_db = TransitGraphBuilder.from_db(pt_con, periods.default_period.period_id)
graph_db.vertices.drop(columns="geometry")
node_id node_type stop_id line_id line_seg_idx taz_id
0 1 od -1 1
1 2 od -1 2
2 3 od -1 3
3 4 od -1 4
4 5 od -1 5
... ... ... ... ... ... ...
362 363 alighting 10000000075 1_10001003000 31 -1
363 364 alighting 10000000076 1_10001003000 32 -1
364 365 alighting 10000000077 1_10001003000 33 -1
365 366 alighting 10000000078 1_10001003000 34 -1
366 367 alighting 10000000053 1_10001003000 35 -1

367 rows × 6 columns



graph_db.edges
link_id link_type line_id stop_id line_seg_idx b_node a_node trav_time freq o_line_id d_line_id direction geometry
0 1 on-board 1_10001001000 0 212 290 90.000000 inf 1 b'\x01\x02\x00\x00\x00\x02\x00\x00\x00\xb8Y\xf...
1 2 on-board 1_10001001000 1 213 291 90.000000 inf 1 b'\x01\x02\x00\x00\x00\x02\x00\x00\x00U2\x00T\...
2 3 on-board 1_10001001000 2 214 292 120.000000 inf 1 b'\x01\x02\x00\x00\x00\x02\x00\x00\x00A\xb1\xf...
3 4 on-board 1_10001001000 3 215 293 120.000000 inf 1 b'\x01\x02\x00\x00\x00\x02\x00\x00\x008\xde\xf...
4 5 on-board 1_10001001000 4 216 294 120.000000 inf 1 b'\x01\x02\x00\x00\x00\x02\x00\x00\x00\xc8\xd5...
... ... ... ... ... ... ... ... ... ... ... ... ... ...
641 642 egress_connector -1 166 112 1039.945114 inf 1 b"\x01\x02\x00\x00\x00\x1b\x00\x00\x00\xa1\x92...
642 643 egress_connector -1 177 112 1149.832226 inf 1 b"\x01\x02\x00\x00\x00\x1e\x00\x00\x00\xa1\x92...
643 644 egress_connector -1 178 112 508.359402 inf 1 b"\x01\x02\x00\x00\x00\x13\x00\x00\x00\xa1\x92...
644 645 egress_connector -1 179 112 968.705083 inf 1 b'\x01\x02\x00\x00\x00\x1d\x00\x00\x00\xa1\x92...
645 646 egress_connector -1 166 113 1831.651685 inf 1 b'\x01\x02\x00\x00\x00/\x00\x00\x00\x1ci\x91$\...

646 rows × 13 columns



Converting to a AequilibraE graph object#

To perform an assignment we need to convert the graph builder into a graph.

transit_graph = graph.to_transit_graph()
/home/runner/work/aequilibrae/aequilibrae/aequilibrae/paths/graph.py:248: UserWarning: Found centroids not present in the graph!
[  1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18
  21  22  23  24  25  26  30  31  32  34  35  36  38  41  42  43  44  45
  46  51  52  53  54  55  56  61  62  63  64  65  66  67  72  73  77  78
  79  80  82  83  84  85  86  87  88  89  90  91  92  93  94  95  96  97
  98  99 100 101 102 103 104 105 109 110 111 114 115 116 117 118 119 120
 121 122 123 124 125 126 127 128 129 130 131 132 133]
  warnings.warn("Found centroids not present in the graph!\n" + str(centroids[~present_centroids]))
/home/runner/work/aequilibrae/aequilibrae/aequilibrae/paths/graph.py:248: UserWarning: Found centroids not present in the graph!
[  1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18
  21  22  23  24  25  26  30  31  32  34  35  36  38  41  42  43  44  45
  46  51  52  53  54  55  56  61  62  63  64  65  66  67  72  73  77  78
  79  80  82  83  84  85  86  87  88  89  90  91  92  93  94  95  96  97
  98  99 100 101 102 103 104 105 109 110 111 114 115 116 117 118 119 120
 121 122 123 124 125 126 127 128 129 130 131 132 133]
  warnings.warn("Found centroids not present in the graph!\n" + str(centroids[~present_centroids]))

Mock demand matrix#

We’ll create a mock demand matrix with demand 1 for every zone. We’ll also need to convert from zone_id's to node_id's.

import numpy as np
from aequilibrae.matrix import AequilibraeMatrix
zones_in_the_model = len(transit_graph.centroids)

names_list = ['pt']

mat = AequilibraeMatrix()
mat.create_empty(zones=zones_in_the_model,
                 matrix_names=names_list,
                 memory_only=True)
mat.index = transit_graph.centroids[:]
mat.matrices[:, :, 0] = np.full((zones_in_the_model, zones_in_the_model), 1.0)
mat.computational_view()

Hyperpath generation/assignment#

We’ll create a TransitAssignment object as well as a TransitClass

from aequilibrae.paths import TransitAssignment, TransitClass

Create the assignment class

assigclass = TransitClass(name="pt", graph=transit_graph, matrix=mat)

assig = TransitAssignment()

assig.add_class(assigclass)

# We need to tell AequilbraE where to find the appropriate fields we want to use,
# as well as the assignment algorithm to use.
assig.set_time_field("trav_time")
assig.set_frequency_field("freq")

assig.set_algorithm("os")

When there’s multiple matrix cores we’ll also need to set the core to use for the demand as we can only assign one at a time.

assigclass.set_demand_matrix_core("pt")

Let’s perform the assignment for the transit classes added

assig.execute()

View the results

assig.results()
pt_volume
479 0.0
480 33.0
481 24.0
482 17.0
483 12.0
... ...
74 47.0
75 24.0
76 24.0
77 27.0
78 0.0

646 rows × 1 columns



Saving results#

We’ll be saving the results to another sqlite db called ‘results_database.sqlite’. The ‘results’ table with ‘project_database.sqlite’ contains some metadata about each table in ‘results_database.sqlite’.

assig.save_results(table_name='hyperpath example')

Wrapping up

project.close()
This project at /tmp/11e931f57c7d4f959c6ea14a0c841ebe is already closed

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

Gallery generated by Sphinx-Gallery