Fast Simulation Mode with the Internal C++ Engine

The simulation speed can be significantly accelerated by simply specifying the optional argument cpp=True when defining a World object, as in World(..., cpp=True). With this option, UXsim uses an internal C++ simulation engine instead of the original Python engine. This speeds up the simulation by a factor of roughly 5 to 20, depending on the scenario (the larger the scenario, the greater the speedup). Parallel computation, which further accelerates the simulation by up to 2x, is also available.

No additional software or libraries need to be installed to use this C++ acceleration feature.

This feature is designed to be almost fully compatible with the original Python mode. Users can use this fast mode just like the original Python mode and will never see or have to deal with any C++ code. For example, simulation scenarios can be defined purely in Python, the results can be analyzed purely in Python, and traffic control can be implemented purely in Python.

However, for unavoidable technical reasons, a few features are incompatible, as listed below:

  • The pickle, copy, save_scenario, and load_scenario features of the World object are unavailable or incompatible in C++ mode.

  • User callback functions such as user_function of the World object are unavailable in C++ mode.

  • The “taxi” mode is unavailable.

  • Pseudo-random number generation is not exactly identical between the Python and C++ modes.

Standard usage

Here is a large-scale grid network scenario in Python mode.

[6]:
from uxsim import *

# simulation world
W = World(
    name="grid_python",
    deltan=5,
    tmax=7200,
    no_cyclic_routing=True,
    print_mode=1, show_mode=1,
    random_seed=0,
)

# scenario
#automated network generation
#deploy nodes as an imax x jmax grid
imax = 11
jmax = 11
nodes = {}
for i in range(imax):
    for j in range(jmax):
        nodes[i,j] = W.addNode(f"n{(i,j)}", i, j)

#create links between neighborhood nodes
links = {}
for i in range(imax):
    for j in range(jmax):
        if i != imax-1:
            links[i,j,i+1,j] = W.addLink(f"l{(i,j,i+1,j)}", nodes[i,j], nodes[i+1,j], length=1000, free_flow_speed=20, jam_density=0.2)
        if i != 0:
            links[i,j,i-1,j] = W.addLink(f"l{(i,j,i-1,j)}", nodes[i,j], nodes[i-1,j], length=1000, free_flow_speed=20, jam_density=0.2)
        if j != jmax-1:
            links[i,j,i,j+1] = W.addLink(f"l{(i,j,i,j+1)}", nodes[i,j], nodes[i,j+1], length=1000, free_flow_speed=20, jam_density=0.2)
        if j != 0:
            links[i,j,i,j-1] = W.addLink(f"l{(i,j,i,j-1)}", nodes[i,j], nodes[i,j-1], length=1000, free_flow_speed=20, jam_density=0.2)

#generate traffic demand between the boundary nodes
demand_flow = 0.035
demand_duration = 3600
for n1 in [(0,j) for j in range(jmax)]:
    for n2 in [(imax-1,j) for j in range(jmax)]:
        W.adddemand(nodes[n2], nodes[n1], 0, demand_duration, demand_flow)
        W.adddemand(nodes[n1], nodes[n2], 0, demand_duration, demand_flow)
for n1 in [(i,0) for i in range(imax)]:
    for n2 in [(i,jmax-1) for i in range(imax)]:
        W.adddemand(nodes[n2], nodes[n1], 0, demand_duration, demand_flow)
        W.adddemand(nodes[n1], nodes[n2], 0, demand_duration, demand_flow)


# execute simulation
W.exec_simulation()

# visualize
W.analyzer.print_simple_stats()
W.analyzer.network_average(network_font_size=0, legend_outside=True)
simulation setting:
 scenario name: grid_python
 simulation duration:    7200 s
 number of vehicles:     60500 veh
 total road length:      440000 m
 timestep size:  5 s
 platoon size:           5 veh
 number of timesteps:    1440
 number of platoons:     12100
 number of links:        440
 number of nodes:        121
 setup time:             0.70 s
simulating...
      time| # of vehicles| ave speed| computation time
       0 s|        0 vehs|   0.0 m/s|     0.00 s
     600 s|     8320 vehs|  10.4 m/s|     1.30 s
    1200 s|    15695 vehs|  11.6 m/s|     3.26 s
    1800 s|    19015 vehs|   9.9 m/s|     5.51 s
    2400 s|    21295 vehs|   8.3 m/s|     7.86 s
    3000 s|    24695 vehs|   6.9 m/s|    10.12 s
    3600 s|    30140 vehs|   5.6 m/s|    12.15 s
    4200 s|    24545 vehs|   6.7 m/s|    14.10 s
    4800 s|    16060 vehs|   9.3 m/s|    15.56 s
    5400 s|     5660 vehs|  15.6 m/s|    16.46 s
    6000 s|       70 vehs|  18.9 m/s|    16.69 s
    6600 s|       25 vehs|  18.0 m/s|    16.74 s
    7200 s|        5 vehs|  20.0 m/s|    16.78 s
 simulation finished
results:
 average speed:  9.6 m/s
 number of completed trips:      60495 / 60500
 total travel time:              101275650.0 s
 average travel time of trips:   1674.1 s
 average delay of trips:         992.3 s
 delay ratio:                    0.593
 total distance traveled:        923600500.0 m
../_images/notebooks_demo_notebook_11en_fast_simulation_by_cpp_mode_1_1.png

By simply using cpp=True option in W=World(..., cpp=True), the fast C++ mode can be enabled.

[2]:
from uxsim import *

# simulation world
W = World(
    name="grid_cpp",
    deltan=5,
    tmax=7200,
    no_cyclic_routing=True,
    print_mode=1, show_mode=1,
    random_seed=0,
    cpp=True #<- Just this option!
)

# scenario
#automated network generation
#deploy nodes as an imax x jmax grid
imax = 11
jmax = 11
nodes = {}
for i in range(imax):
    for j in range(jmax):
        nodes[i,j] = W.addNode(f"n{(i,j)}", i, j)

#create links between neighborhood nodes
links = {}
for i in range(imax):
    for j in range(jmax):
        if i != imax-1:
            links[i,j,i+1,j] = W.addLink(f"l{(i,j,i+1,j)}", nodes[i,j], nodes[i+1,j], length=1000, free_flow_speed=20, jam_density=0.2)
        if i != 0:
            links[i,j,i-1,j] = W.addLink(f"l{(i,j,i-1,j)}", nodes[i,j], nodes[i-1,j], length=1000, free_flow_speed=20, jam_density=0.2)
        if j != jmax-1:
            links[i,j,i,j+1] = W.addLink(f"l{(i,j,i,j+1)}", nodes[i,j], nodes[i,j+1], length=1000, free_flow_speed=20, jam_density=0.2)
        if j != 0:
            links[i,j,i,j-1] = W.addLink(f"l{(i,j,i,j-1)}", nodes[i,j], nodes[i,j-1], length=1000, free_flow_speed=20, jam_density=0.2)

#generate traffic demand between the boundary nodes
demand_flow = 0.035
demand_duration = 3600
for n1 in [(0,j) for j in range(jmax)]:
    for n2 in [(imax-1,j) for j in range(jmax)]:
        W.adddemand(nodes[n2], nodes[n1], 0, demand_duration, demand_flow)
        W.adddemand(nodes[n1], nodes[n2], 0, demand_duration, demand_flow)
for n1 in [(i,0) for i in range(imax)]:
    for n2 in [(i,jmax-1) for i in range(imax)]:
        W.adddemand(nodes[n2], nodes[n1], 0, demand_duration, demand_flow)
        W.adddemand(nodes[n1], nodes[n2], 0, demand_duration, demand_flow)


# execute simulation
W.exec_simulation()

# visualize
W.analyzer.print_simple_stats()
W.analyzer.network_average(network_font_size=0, legend_outside=True)
simulation setting:
 scenario name: grid_cpp
 simulation duration:    7200 s
 number of vehicles:     60500 veh
 total road length:      440000 m
 timestep size:  5 s
 platoon size:           5 veh
 number of timesteps:    1440
 number of platoons:     12100
 number of links:        440
 number of nodes:        121
 setup time:             0.18 s
simulating...
      time| # of vehicles| ave speed| computation time
       0 s|        0 vehs|   0.0 m/s|     0.00 s
     600 s|     8295 vehs|  11.3 m/s|     0.01 s
    1200 s|    15570 vehs|  11.2 m/s|     0.04 s
    1800 s|    18910 vehs|   9.7 m/s|     0.08 s
    2400 s|    21190 vehs|   8.5 m/s|     0.13 s
    3000 s|    23615 vehs|   7.5 m/s|     0.18 s
    3600 s|    27580 vehs|   6.5 m/s|     0.24 s
    4200 s|    21010 vehs|   7.9 m/s|     0.29 s
    4800 s|    12795 vehs|  10.8 m/s|     0.33 s
    5400 s|     2650 vehs|  18.7 m/s|     0.35 s
    6000 s|       30 vehs|  19.2 m/s|     0.35 s
    6600 s|        5 vehs|  20.0 m/s|     0.36 s
    7200 s|        0 vehs|   0.0 m/s|     0.36 s
 simulation finished
results:
 average speed:  10.0 m/s
 number of completed trips:      60500 / 60500
 total travel time:              93333450.0 s
 average travel time of trips:   1542.7 s
 average delay of trips:         860.9 s
 delay ratio:                    0.558
 total distance traveled:        906945625.0 m
../_images/notebooks_demo_notebook_11en_fast_simulation_by_cpp_mode_3_1.png

You can see that this is extremely faster than the original Python mode (from 17 sec to 0.4 sec in the original notebook, but these numbers will vary depending on the environments). The simulation results are basically the same, although there are slight discrepancy due to the difference in the random number generators of Python and C++ (for statistical comparison, see the last section of this notebook).

We can also access the internal objects (e.g., Vehicle) of UXsim just like pure Python objects.

[3]:
veh = W.VEHICLES["0"]   #get Vehicle instance
print("instance:", veh)
print("type:", type(veh)) #this is actually C++ object
print("orig:", veh.orig)  #but, you can access to its variables as in pure-Python mode
print("travel time:", veh.travel_time)
print("travel route:", veh.traveled_route())
instance: <Vehicle 0>
type: <class 'uxsim.uxsim_cpp_wrapper.CppVehicle'>
orig: <Node n(10, 0)>
travel time: 525.0
travel route: (<Route : ['l(10, 0, 9, 0)', 'l(9, 0, 8, 0)', 'l(8, 0, 7, 0)', 'l(7, 0, 6, 0)', 'l(6, 0, 5, 0)', 'l(5, 0, 4, 0)', 'l(4, 0, 3, 0)', 'l(3, 0, 2, 0)', 'l(2, 0, 1, 0)', 'l(1, 0, 0, 0)']>, [np.float64(145.0), np.float64(195.0), np.float64(260.0), np.float64(310.0), np.float64(370.0), np.float64(420.0), np.float64(470.0), np.float64(520.0), np.float64(570.0), np.float64(620.0), np.float64(665.0)])

Furthermore, multi-thread execution is also available for the fast mode by using thread optional arg. About 2x speed up is possible by using thread=4.

[4]:
from uxsim import *

# simulation world
W = World(
    name="grid_cpp",
    deltan=5,
    tmax=7200,
    no_cyclic_routing=True,
    print_mode=1, show_mode=1,
    random_seed=0,
    cpp=True, threads=4 #<- Just this option!
)

# scenario
#automated network generation
#deploy nodes as an imax x jmax grid
imax = 11
jmax = 11
nodes = {}
for i in range(imax):
    for j in range(jmax):
        nodes[i,j] = W.addNode(f"n{(i,j)}", i, j)

#create links between neighborhood nodes
links = {}
for i in range(imax):
    for j in range(jmax):
        if i != imax-1:
            links[i,j,i+1,j] = W.addLink(f"l{(i,j,i+1,j)}", nodes[i,j], nodes[i+1,j], length=1000, free_flow_speed=20, jam_density=0.2)
        if i != 0:
            links[i,j,i-1,j] = W.addLink(f"l{(i,j,i-1,j)}", nodes[i,j], nodes[i-1,j], length=1000, free_flow_speed=20, jam_density=0.2)
        if j != jmax-1:
            links[i,j,i,j+1] = W.addLink(f"l{(i,j,i,j+1)}", nodes[i,j], nodes[i,j+1], length=1000, free_flow_speed=20, jam_density=0.2)
        if j != 0:
            links[i,j,i,j-1] = W.addLink(f"l{(i,j,i,j-1)}", nodes[i,j], nodes[i,j-1], length=1000, free_flow_speed=20, jam_density=0.2)

#generate traffic demand between the boundary nodes
demand_flow = 0.035
demand_duration = 3600
for n1 in [(0,j) for j in range(jmax)]:
    for n2 in [(imax-1,j) for j in range(jmax)]:
        W.adddemand(nodes[n2], nodes[n1], 0, demand_duration, demand_flow)
        W.adddemand(nodes[n1], nodes[n2], 0, demand_duration, demand_flow)
for n1 in [(i,0) for i in range(imax)]:
    for n2 in [(i,jmax-1) for i in range(imax)]:
        W.adddemand(nodes[n2], nodes[n1], 0, demand_duration, demand_flow)
        W.adddemand(nodes[n1], nodes[n2], 0, demand_duration, demand_flow)


# execute simulation
W.exec_simulation()

# visualize
W.analyzer.print_simple_stats()
W.analyzer.network_average(network_font_size=0, legend_outside=True)
simulation setting:
 scenario name: grid_cpp
 simulation duration:    7200 s
 number of vehicles:     60500 veh
 total road length:      440000 m
 timestep size:  5 s
 platoon size:           5 veh
 number of timesteps:    1440
 number of platoons:     12100
 number of links:        440
 number of nodes:        121
 setup time:             1.70 s
simulating...
      time| # of vehicles| ave speed| computation time
       0 s|        0 vehs|   0.0 m/s|     0.00 s
     600 s|     8295 vehs|  11.3 m/s|     0.01 s
    1200 s|    15570 vehs|  11.2 m/s|     0.02 s
    1800 s|    18910 vehs|   9.7 m/s|     0.04 s
    2400 s|    21190 vehs|   8.5 m/s|     0.06 s
    3000 s|    23615 vehs|   7.5 m/s|     0.08 s
    3600 s|    27580 vehs|   6.5 m/s|     0.11 s
    4200 s|    21010 vehs|   7.9 m/s|     0.13 s
    4800 s|    12795 vehs|  10.8 m/s|     0.14 s
    5400 s|     2650 vehs|  18.7 m/s|     0.16 s
    6000 s|       30 vehs|  19.2 m/s|     0.16 s
    6600 s|        5 vehs|  20.0 m/s|     0.16 s
    7200 s|        0 vehs|   0.0 m/s|     0.16 s
 simulation finished
results:
 average speed:  10.0 m/s
 number of completed trips:      60500 / 60500
 total travel time:              93333450.0 s
 average travel time of trips:   1542.7 s
 average delay of trips:         860.9 s
 delay ratio:                    0.558
 total distance traveled:        906945625.0 m
../_images/notebooks_demo_notebook_11en_fast_simulation_by_cpp_mode_7_1.png

Dynamic Traffic Assignment with C++ mode

The Dynamic Traffic Assignment (DTA) solvers for DSO and DUE are also accelerated by C++ mode. To use this feature, you need to specify cpp=True option to both of DSOSolver (or DUESolver) and World.

Let’s use the large scale scenario identical to DTA tutorial as an example. This time, we add cpp=True option when defining the World.

[15]:
import uxsim
from uxsim import DTAsolvers

def create_World():
    # simulation world
    W = uxsim.World(
        name="",
        deltan=10,
        tmax=4800,
        duo_update_time=300,
        print_mode=0, save_mode=0, show_mode=1,
        random_seed=42,
        cpp=True #<- this option
    )

    # scenario
    #automated network generation
    #deploy nodes as an imax x jmax grid
    imax = 9
    jmax = 9
    id_center = 4
    nodes = {}
    for i in range(imax):
        for j in range(jmax):
            nodes[i,j] = W.addNode(f"n{(i,j)}", i, j, flow_capacity=1.6)

    #create links between neighborhood nodes
    links = {}
    for i in range(imax):
        for j in range(jmax):
            free_flow_speed = 10
            if i != imax-1:
                if j == id_center:
                    free_flow_speed = 20
                links[i,j,i+1,j] = W.addLink(f"l{(i,j,i+1,j)}", nodes[i,j], nodes[i+1,j], length=1000, free_flow_speed=free_flow_speed)
            if i != 0:
                if j == id_center:
                    free_flow_speed = 20
                links[i,j,i-1,j] = W.addLink(f"l{(i,j,i-1,j)}", nodes[i,j], nodes[i-1,j], length=1000, free_flow_speed=free_flow_speed)
            if j != jmax-1:
                if i == id_center:
                    free_flow_speed = 20
                links[i,j,i,j+1] = W.addLink(f"l{(i,j,i,j+1)}", nodes[i,j], nodes[i,j+1], length=1000, free_flow_speed=free_flow_speed)
            if j != 0:
                if i == id_center:
                    free_flow_speed = 20
                links[i,j,i,j-1] = W.addLink(f"l{(i,j,i,j-1)}", nodes[i,j], nodes[i,j-1], length=1000, free_flow_speed=free_flow_speed)

    #generate traffic demand between the boundary nodes
    demand_flow = 0.08
    demand_duration = 2400
    outer_ids = 3
    for n1 in [(0,j) for j in range(outer_ids, jmax-outer_ids)]:
        for n2 in [(imax-1,j) for j in range(outer_ids,jmax-outer_ids)]:
            W.adddemand(nodes[n1], nodes[n2], 0, demand_duration, demand_flow)
        for n2 in [(i,jmax-1) for i in range(outer_ids, imax-outer_ids)]:
            W.adddemand(nodes[n1], nodes[n2], 0, demand_duration, demand_flow)
    for n1 in [(i,0) for i in range(outer_ids, imax-outer_ids)]:
        for n2 in [(i,jmax-1) for i in range(outer_ids, imax-outer_ids)]:
            W.adddemand(nodes[n1], nodes[n2], 0, demand_duration, demand_flow)
        for n2 in [(imax-1,j) for j in range(outer_ids,jmax-outer_ids)]:
            W.adddemand(nodes[n1], nodes[n2], 0, demand_duration, demand_flow)

    return W

W = create_World()
W.change_print_mode(1)
W.show_network(network_font_size=0)
../_images/notebooks_demo_notebook_11en_fast_simulation_by_cpp_mode_9_0.png

Then compute Dynamic System Optimum solution by SolverDSO_D2D(..., cpp=True).

You can see that the computation speed is significantly faster than the original Python mode (this result), but the solution is similar (again, not exactly identical due to the stochasticity. For statistical comparison, see the last section).

[16]:
solver_DSO = DTAsolvers.SolverDSO_D2D(create_World, cpp=True)
solver_DSO.solve(max_iter=50)

W_DSO = solver_DSO.W_sol
W_DSO.analyzer.print_simple_stats(force_print=True)
W_DSO.analyzer.network_average(network_font_size=0, legend=True)

W_DSO.analyzer.network_anim(state_variables="flow_speed", figsize=4, animation_speed_inverse=20, timestep_skip=8, detailed=0, network_font_size=0, file_name="out/anim_DSO.gif")
uxsim.display_image_in_notebook("out/anim_DSO.gif")

solver_DSO.plot_convergence()
simulation setting (not finalized):
 scenario name:
 simulation duration:    4800 s
 number of vehicles:     6840 veh
 total road length:      288000 m
 timestep size:  10 s
 platoon size:           10 veh
 number of timesteps:    480.0
 number of platoons:     684
 number of links:        288
 number of nodes:        81
 setup time:             0.01 s
number of OD pairs: 6480, number of routes: 63097
solving DSO...
 iter 0: marginal time gap: 5096.6, potential route change: 5670.0, route change: 320.0, total travel time:  6818000.0, delay ratio:  0.482
 iter 1: marginal time gap: 5496.7, potential route change: 5630.0, route change: 320.0, total travel time:  6662600.0, delay ratio:  0.470
 iter 2: marginal time gap: 3969.3, potential route change: 5540.0, route change: 270.0, total travel time:  6116700.0, delay ratio:  0.422
 iter 3: marginal time gap: 3359.8, potential route change: 5330.0, route change: 260.0, total travel time:  6010400.0, delay ratio:  0.412
 iter 4: marginal time gap: 3080.6, potential route change: 5400.0, route change: 300.0, total travel time:  5887700.0, delay ratio:  0.400
 iter 5: marginal time gap: 2994.8, potential route change: 5260.0, route change: 250.0, total travel time:  5905900.0, delay ratio:  0.402
 iter 6: marginal time gap: 2977.5, potential route change: 5360.0, route change: 270.0, total travel time:  5858800.0, delay ratio:  0.397
 iter 7: marginal time gap: 2209.4, potential route change: 5510.0, route change: 310.0, total travel time:  5741100.0, delay ratio:  0.384
 iter 8: marginal time gap: 1962.7, potential route change: 5390.0, route change: 210.0, total travel time:  5685800.0, delay ratio:  0.378
 iter 9: marginal time gap: 1663.1, potential route change: 5420.0, route change: 310.0, total travel time:  5601900.0, delay ratio:  0.369
 iter 10: marginal time gap: 1296.7, potential route change: 5320.0, route change: 300.0, total travel time:  5547400.0, delay ratio:  0.363
 iter 11: marginal time gap: 1033.1, potential route change: 5410.0, route change: 340.0, total travel time:  5542600.0, delay ratio:  0.362
 iter 12: marginal time gap: 737.0, potential route change: 5580.0, route change: 290.0, total travel time:  5460400.0, delay ratio:  0.353
 iter 13: marginal time gap: 704.2, potential route change: 5530.0, route change: 340.0, total travel time:  5422800.0, delay ratio:  0.348
 iter 14: marginal time gap: 571.7, potential route change: 5570.0, route change: 250.0, total travel time:  5369700.0, delay ratio:  0.342
 iter 15: marginal time gap: 600.1, potential route change: 5430.0, route change: 250.0, total travel time:  5330400.0, delay ratio:  0.337
 iter 16: marginal time gap: 464.1, potential route change: 5460.0, route change: 320.0, total travel time:  5318300.0, delay ratio:  0.336
 iter 17: marginal time gap: 534.0, potential route change: 5390.0, route change: 370.0, total travel time:  5275200.0, delay ratio:  0.330
 iter 18: marginal time gap: 444.7, potential route change: 5480.0, route change: 230.0, total travel time:  5246300.0, delay ratio:  0.326
 iter 19: marginal time gap: 448.5, potential route change: 5370.0, route change: 270.0, total travel time:  5232400.0, delay ratio:  0.325
 iter 20: marginal time gap: 582.3, potential route change: 5330.0, route change: 140.0, total travel time:  5211000.0, delay ratio:  0.322
 iter 21: marginal time gap: 653.8, potential route change: 5340.0, route change: 260.0, total travel time:  5202400.0, delay ratio:  0.321
 iter 22: marginal time gap: 852.9, potential route change: 5190.0, route change: 270.0, total travel time:  5193900.0, delay ratio:  0.320
 iter 23: marginal time gap: 347.4, potential route change: 5400.0, route change: 250.0, total travel time:  5165600.0, delay ratio:  0.316
 iter 24: marginal time gap: 389.4, potential route change: 5340.0, route change: 310.0, total travel time:  5166100.0, delay ratio:  0.316
 iter 25: marginal time gap: 438.6, potential route change: 5290.0, route change: 270.0, total travel time:  5170000.0, delay ratio:  0.316
 iter 26: marginal time gap: 517.2, potential route change: 5400.0, route change: 240.0, total travel time:  5152800.0, delay ratio:  0.314
 iter 27: marginal time gap: 437.8, potential route change: 5370.0, route change: 280.0, total travel time:  5134600.0, delay ratio:  0.312
 iter 28: marginal time gap: 648.7, potential route change: 5230.0, route change: 260.0, total travel time:  5148500.0, delay ratio:  0.314
 iter 29: marginal time gap: 1189.1, potential route change: 5230.0, route change: 210.0, total travel time:  5192700.0, delay ratio:  0.319
 iter 30: marginal time gap: 634.6, potential route change: 5270.0, route change: 180.0, total travel time:  5174400.0, delay ratio:  0.317
 iter 31: marginal time gap: 608.1, potential route change: 5290.0, route change: 300.0, total travel time:  5162600.0, delay ratio:  0.315
 iter 32: marginal time gap: 520.0, potential route change: 5230.0, route change: 280.0, total travel time:  5137200.0, delay ratio:  0.312
 iter 33: marginal time gap: 376.9, potential route change: 5170.0, route change: 280.0, total travel time:  5128200.0, delay ratio:  0.311
 iter 34: marginal time gap: 522.2, potential route change: 5220.0, route change: 210.0, total travel time:  5145400.0, delay ratio:  0.313
 iter 35: marginal time gap: 547.9, potential route change: 5070.0, route change: 190.0, total travel time:  5154500.0, delay ratio:  0.314
 iter 36: marginal time gap: 492.4, potential route change: 5230.0, route change: 290.0, total travel time:  5131300.0, delay ratio:  0.311
 iter 37: marginal time gap: 991.0, potential route change: 5150.0, route change: 300.0, total travel time:  5255600.0, delay ratio:  0.328
 iter 38: marginal time gap: 770.2, potential route change: 5060.0, route change: 310.0, total travel time:  5208400.0, delay ratio:  0.321
 iter 39: marginal time gap: 718.8, potential route change: 5180.0, route change: 250.0, total travel time:  5196800.0, delay ratio:  0.320
 iter 40: marginal time gap: 623.6, potential route change: 5220.0, route change: 220.0, total travel time:  5196800.0, delay ratio:  0.320
 iter 41: marginal time gap: 728.2, potential route change: 5250.0, route change: 270.0, total travel time:  5198500.0, delay ratio:  0.320
 iter 42: marginal time gap: 374.1, potential route change: 5320.0, route change: 290.0, total travel time:  5175200.0, delay ratio:  0.317
 iter 43: marginal time gap: 582.0, potential route change: 5150.0, route change: 180.0, total travel time:  5174200.0, delay ratio:  0.317
 iter 44: marginal time gap: 486.2, potential route change: 5240.0, route change: 230.0, total travel time:  5171500.0, delay ratio:  0.317
 iter 45: marginal time gap: 561.5, potential route change: 5290.0, route change: 230.0, total travel time:  5166100.0, delay ratio:  0.316
 iter 46: marginal time gap: 565.7, potential route change: 5130.0, route change: 250.0, total travel time:  5171600.0, delay ratio:  0.317
 iter 47: marginal time gap: 497.7, potential route change: 5340.0, route change: 160.0, total travel time:  5160100.0, delay ratio:  0.315
 iter 48: marginal time gap: 554.4, potential route change: 5370.0, route change: 160.0, total travel time:  5161300.0, delay ratio:  0.315
 iter 49: marginal time gap: 610.1, potential route change: 5220.0, route change: 330.0, total travel time:  5154700.0, delay ratio:  0.314
DSO summary:
 total travel time: initial 6818000.0 -> minimum 5128200.0
 number of potential route changes: initial 5670.0 -> minimum 5060.0
 route marginal travel time gap: initial 5096.6 -> minimum 347.4
 computation time: 3.7 seconds
results:
 average speed:  12.1 m/s
 number of completed trips:      6840 / 6840
 total travel time:              5128200.0 s
 average travel time of trips:   749.7 s
 average delay of trips:         233.1 s
 delay ratio:                    0.311
 total distance traveled:        60247750.0 m
../_images/notebooks_demo_notebook_11en_fast_simulation_by_cpp_mode_11_1.png
../_images/notebooks_demo_notebook_11en_fast_simulation_by_cpp_mode_11_2.png
../_images/notebooks_demo_notebook_11en_fast_simulation_by_cpp_mode_11_3.png

Benchmark and Statistical Comparison

Below is a detailed benchmark result obtained with this benchmark code on v1.14.0. The C++ mode is significantly faster, and the results are statistically very similar to those of the Python mode.

Note that the “large” scenario shows low link-level similarity in the per-seed comparison because of the strong symmetry of the network shape (the scenario is inherently chaotic, and bifurcation occurs). Nevertheless, when the average states over multiple seeds are compared, similar results are obtained. This indicates that the two modes are essentially compatible.

Small & Large scenarios (mean ± std, 10 seeds)

Scenario

Mode

Elapsed (s)

Total Travel Time

Average Delay

Completed Trips

small

Python

0.040 ± 0.014

118,960 ± 686

61.9 ± 0.9

735 ± 0

small

C++

0.008 ± 0.001

119,200 ± 854

62.2 ± 1.2

735 ± 0

large

Python

14.859 ± 1.000

93,248,782 ± 4,628,559

859.9 ± 76.7

60,486 ± 16

large

C++

0.751 ± 0.149

91,721,835 ± 4,754,306

834.6 ± 78.7

60,487 ± 11

DUE/DSO scenario (mean ± std, 10 seeds)

Mode

Total Elapsed (s)

DUO TTT

DUE TTT

DSO TTT

Python

70.6 ± 3.4

7,149,660 ± 215,605

5,858,050 ± 211,236

5,308,440 ± 47,607

C++

7.2 ± 0.4

7,299,920 ± 159,439

5,958,970 ± 155,729

5,280,430 ± 64,398

Speedup (Python / C++)

  • small: 5.03x

  • large: 19.79x

  • due_dso: 9.77x

C++ threads comparison: threads=1 (default) vs threads=8 (mean ± std, 10 seeds)

Scenario

Threads

Elapsed (s)

Total Travel Time

Speedup vs threads=1

small

1

0.008 ± 0.001

119,200 ± 854

small

8

0.008 ± 0.002

119,200 ± 854

0.94x

large

1

0.751 ± 0.149

91,721,835 ± 4,754,306

large

8

0.590 ± 0.075

91,721,835 ± 4,754,306

1.27x