CBEngine

Overview

CBEngine is a highly efficient microscopic traffic simulator. We support real time simulation of traffic system with more than 100,000 intersections and 1,000,000 vehicles.

../../_images/imageonline-gifspeed-6207245.gif

Model of CBEngine

CBEngine is a microscopic traffic simulator, which means it provides modelling for each traffic element independently. As demonstrated in the figure below, vehicles, roads, and traffic signals have their own states which iterate by time based on the action they take.

For example, a vehicle drives in the velocity v_t and with the acceleration of a_t at time t. Its velocity at the next time step v_{t+1} is

v_{t+1} = v_t + a_t * 1

CBEngine iterates the state of every traffic elements with an optimized parallelization architecture , thus making the microscopic traffic simulation more efficient.

fig_intro

More advantages of CBEngine

  • Various of APIs to operate different traffic elements: roads, traffic signals, vehicles, …

  • User customization of driving module and routing module.

  • Pipeline to conduct transformation from map data to road network data file with utility.

Now, use CBEngine to start up your first traffic simulation on large scale traffic system!

Installation

CBEngine is packed as a python package cbengine and can be imported in python with:

import cbengine

CBEngine provides two approaches for installation: with docker and with source code. We highly recommend to use our docker for CBEngine

Using docker

Pull our docker image of CBLab:

docker pull citybrainlab/cbengine:latest

Create a container with the docker image. /path/to/your/folder is the path of folder containing scripts and data for running the simulation:

docker run -it -v /path/to/your/CBLab_files:/CBLab_files citybrainlab/open-engine:latest bash

Now, start up the simulation in the container.

Using source code

Our source code is available on Github for Linux. Run the following command to clone our repo:

git clone https://github.com/CityBrainLab/CityBrainLab.git

To install CBEngine, unzip the file and run the following command in the ‘CBLab/CBEngine/’ folder:

cd ./core
pip install -e ./

The installation will finish in seconds.

For installation with source code, we recommend you to create a new environment as follows:

  • Enter the folder CBLab/

  • Run following commands:

conda create -n cblab python=3.8
    conda activate cblab
    pip install -r requirments.txt

Test the installation

We provide our test - Enter the folder ‘CBLab/CBEngine/test/’ - Run the following command: python main.py - If it outputs as follows, the installation is successfully completed:

t: 0, v: 14
t: 1, v: 30
t: 2, v: 43
t: 3, v: 63
t: 4, v: 78
t: 5, v: 92
t: 6, v: 112
t: 7, v: 129
t: 8, v: 147
t: 9, v: 167
t: 10, v: 187
...

Quick Start

Demo of Simulation

We provide a demo to start up the simplest version of our simulation in CBLab/CBEngine/test/ of our source code <>.

The structure of the simulation demo:

|-test
    |-cfgs
        |-config.cfg: configuration file
    |-data
        |-roadnet.txt: road network data file
        |-flow.txt: traffic flow data file
    |-utils.py: utilities of traffic simulation
    |-main.py: demo script

Specifically,

  • config.cfg assigns the location of roadnet.txt and flow.txt and details some settings for CBEngine.

  • roadnet.txt and flow.txt are data for simulation.

  • utils.py includes an utility class Dataloader used in simulation.

Start up the Demo

Run the demo in the environment with CBEngine installed:

python main.py

Understand the Demo

Firstly, we define some arguments for the simulation.

roadnet_file = './data/roadnet.txt'
flow_file = './data/flow.txt'
cfg_file = './cfgs/config.cfg'
dataloader = Dataloader(roadnet_file, flow_file, cfg_file)

These arguments point out the path to the configuration file and two data files.

Then, we create our simulator instance with the configuration file. cbengine.Engine(cfg_file, thread_num) returns a simulator object.

running_step = 300                      # simulation time
phase_time = 30                         # traffic signal phase duration time
engine = cbengine.Engine(cfg_file, 12)  # create a simulator instance

Finally, we start up the simulation. engine.next_step() iterates the engine for one time step The length of one time step is 1 second as default.

Moreover, we can access some information to observe the simulation and operate some traffic elements

  • engine.get_current_time(): return the current time in the simulation.

  • engine.set_ttl_phase(intersection_id, phase): change the phase of the traffic signal in the intersection.

  • engine.get_vehicle_count(): return the current number of vehicles.

print('Simulation starts ...')
start_time = time.time()
for step in range(running_step):
    for intersection in dataloader.intersections.keys():                                            # for each traffic signal
        engine.set_ttl_phase(intersection, (int(engine.get_current_time()) // phase_time) % 4 + 1)  # change the phase of each traffic signal in the roadnet
    engine.next_step()                                                                              # iterate the simulation for one time step
    print(" time step: {}, number of vehicles: {}".format(step, engine.get_vehicle_count()))        # get_vehicle_count() is an API to observe the number of vehicle
end_time = time.time()
print('Simulation finishes. Runtime: ', end_time - start_time)

Data format

Configuration File Format

#configuration for simulator

# Time Parameters
# start time of simulation
start_time_epoch = 0
# the maximum end time of simulation
max_time_epoch = 3600

# Data
# road network data file address
road_file_addr : ./data/roadnet.txt
# traffic flow data file address
vehicle_file_addr : ./data/flow.txt


# Log Trace
# Log is for visualization only.
report_log_mode : normal
report_log_addr : ./log/
report_log_rate = 10
warning_stop_time_log = 100

Normally, four arguments in Time Parameters and Data is common to be modified. You can change the value of these arguments to reassign the setting of your simulation.

Roadnet File Format

The road network file contains the following three data sections.

  • Intersection section

    Intersection data consists of identification, location and traffic signal installation information about each intersection. A snippet of intersection dataset is shown below.

    92344 // total number of intersections
    30.2795476000 120.1653304000 25926073 1 //latitude, longitude, inter_id, signalized
    30.2801771000 120.1664368000 25926074 0
    ...
    

    The attributes of intersection dataset are described in details as below.

    Attribute Name

    Example

    Description

    latitude

    30.279547600

    local latitude

    longitude

    120.1653304000

    local longitude

    inter_id

    25926073

    intersection ID

    signalized

    1

    1 if traffic signal is installed, 0 otherwise

  • Road section

    Road dataset consists information about road segments in the network. In general, there are two directions on each road segment (i.e., dir1 and dir2). A snippet of road dataset is shown as follows.

    2105 // total number of road segments
    28571560 4353988632 93.2000000000 20 3 3 1 2
    1 0 0 0 1 0 0 1 1 // dir1_mov: permissible movements of direction 1
    1 0 0 0 1 0 0 1 1 // dir2_mov: permissible movements of direction 2
    28571565 4886970741 170.2000000000 20 3 3 3 4
    1 0 0 0 1 0 0 1 1
    1 0 0 0 1 0 0 1 1
    

    The attributes of road dataset are described in details as below. Direction 1 is <from_inter_id,to_inter_id>. Direction 2 is <to_inter_id,from_inter_id>.

    Attribute Name

    Example

    Description

    from_inter_id

    28571560

    upstream intersection ID w.r.t. dir1

    to_inter_id

    4353988632

    downstream intersection ID w.r.t. dir1

    length (m)

    93.2000000000

    length of road segment

    speed_limit (m/s)

    20

    speed limit of road segment

    dir1_num_lane

    3

    number of lanes of direction 1

    dir2_num_lane

    3

    number of lanes of direction 2

    dir1_id

    1

    road segment (edge) ID of direction 1

    dir2_id

    2

    road segment (edge) ID of direction 2

    dir1_mov

    1 0 0 0 1 0 0 1 1

    every 3 digits form a permissible movement indicator for a lane of direction 1, 100 indicates a left-turn only inner lane, 010 indicates through only middle lane, 011 indicates a shared through and right-turn outer lane.

    dir2_mov

    1 0 0 0 1 0 0 1 1

    every 3 digits form a lane permissible movement indicator for a lane of direction 2.

  • Traffic signal section

    This dataset describes the connectivity between intersection and road segments. Note that, we assume that each intersection has no more than four approaches. The exiting approaches 1 to 4 starting from the northern one and rotating in clockwise direction. Here, -1 indicates that the corresponding approach is missing, which generally indicates a three-leg intersection.

    107 // total number of signalized intersections
    1317137908 724 700 611 609 // inter_id, approach1_id, approach2_id, approach3_id, approach4_id
    672874599 311 2260 3830 -1 // -1 indicates a three-leg intersection without western approach
    672879594 341 -1 2012 339
    

    The attributes of road dataset is described in details as below

    Attribute Name

    Example

    Description

    inter_id

    1317137908

    intersection ID

    approach1_id

    724

    road segment (edge) ID of northern exiting approach (Road_1 in example)

    approach2_id

    700

    road segment (edge) ID of eastern exiting approach (Road_3 in example)

    approach3_id

    611

    road segment (edge) ID of southern exiting approach (Road_5 in example)

    approach4_id

    609

    road segment (edge) ID of western exiting approach (Road_7 in example)

Here is an example 1x1 roadnet roadnet.txt .

5 // intersection data
30 120 0 1 // latitude, longitude, inter_id, signalized
31 120 1 0
30 121 2 0
29 120 3 0
30 119 4 0
4 // road data
0 1 30 20 3 3 1 2
1 0 0 0 1 0 0 0 1 // dir1_mov: permissible movements of direction 1
1 0 0 0 1 0 0 0 1 // dir2_mov: permissible movements of direction 2
0 2 30 20 3 3 3 4
1 0 0 0 1 0 0 0 1
1 0 0 0 1 0 0 0 1
0 3 30 20 3 3 5 6
1 0 0 0 1 0 0 0 1
1 0 0 0 1 0 0 0 1
0 4 30 20 3 3 7 8
1 0 0 0 1 0 0 0 1
1 0 0 0 1 0 0 0 1
1 // traffic signal data
0 1 3 5 7 // inter_id, approach1_id, approach2_id, approach3_id, approach4_id

Flow File Format

Flow file is composed by flows. Each flow is represented as a tuple (start_time, end_time, vehicle_interval, route), which means from start_time to end_time, there will be a vehicle with route every vehicle_interval seconds. The format of flows contains serval parts:

  • The first row of flow file is n, which means the number of flow.

  • The following 3n rows indicating configuration of each flow. Each flow have 3 configuration lines.

    • The first row consists of start_time, end_time, vehicle_interval.

    • The second row is the number of road segments of route for this flow : k.

    • The third row describes the route of this flow. Here flow’s route is defined by roads not intersections.

n
flow_1_start_time   flow_1_end_time flow_1_interval
k_1
flow_1_route_0      flow_1_route_1  ...     flow_1_route_k1

flow_2_start_time   flow_2_end_time flow_2_interval
k_2
flow_2_route_0      flow_2_route_1  ...     flow_2_route_k2

...

flow_n_start_time   flow_n_end_time flow_n_interval
k_n
flow_n_route_0      flow_n_route_1  ...     flow_n_route_k

Here is an example flow file

12 // n = 12
0 100 5 // start_time, end_time, vehicle_interval
2 // number of road segments
2 3 // road segment IDs
0 100 5
2
2 5
0 100 5
2
2 7
0 100 5
2
4 5
0 100 5
2
4 7
0 100 5
2
4 1
0 100 5
2
6 7
0 100 5
2
6 1
0 100 5
2
6 3
0 100 5
2
8 1
0 100 5
2
8 3
0 100 5
2
8 5

API

Data API

API

Returned value

Description

get_vehicle_count()

int

The total number of running vehicle

get_vehicles()

list

A list of running vehicles’ ids

get_lane_vehicle_count()

dict

A dict. Keys are lane_id, values are number of running vehicles on this lane

get_lane_vehicles()

dict

A dict. Keys are lane_id, values are a list of running vehicles on this lane

get_lane_waiting_vehicle_count()

int

A dict. Keys are lane_id, values are a list of waiting vehicles on this lane

get_vehicle_speed()

float

A dict. Keys are vehicle_id of running vehicles, values are their speed

get_average_travel_time()

float

The average travel time of both running vehicles and finished vehicles

get_vehicle_info(vehicle_id)

dict

Input vehicle_id, output the information of the vehicle as a dict

get_current_time()

int

Output the current time as an integer

get_road_speed_limit(road_id)

float

Input road_id, output the speed limit of the road as a float

get_car_following_params()

dict

Output the parameters of the driving module

get_vehicle_route(vehicle_id)

list

Input vehicle_id, output the list of roads in the route

get_ttl_phase(intersection_id)

int

Input intersection_id, output the phase of its traffic signal as an integer

Operating API

API

Input type

Description

set_road_velocity(road_id, speed_limit)

int, float

Set the speed limit of a road

set_car_following_params(params)

dict

Set the parameters in the driving module

set_vehicle_route(routes)

list

Set the route for a vehicle

set_ttl_phase(int)

int

Set the traffic signal phase for an intersection

Load & Save API

API

Input type

Description

load_state(snapshot_path)

string

Load a snapshot of the simulation

save_state(snapshot_path)

string

Save the current state of the simulation as a snapshot

Module Customization

An obvious characteristic of CBEngine is that it supports the user customization of two modules used in the simulation: driving module and routing module. We achieve this by abstracting these two modules in two concrete C++ classes and tune the structure of the simulator to make these clases independent. Note that customization is only for CBEngine installed with source code.

To conduct the module customization, we highly recommend you to read the source code of CBEngine, while this involves several classes as inputs and outputs of the module.

Driving Module

Driving module describes how vehicles adjust their driving behaviours according to the traffic conditions in the road, including those vehicles around and traffic signals. In CBEngine, we abstract this module with a class and provide the following guidance to help users implement their own driving modules.

Data

Various data should be taken into consideration when a driver decides the driving behaviours of a vehicle. CBEngine aggregates a lot of data visible to drivers in the real world for the driving modules. For customization, user can design driving modules which make use of it in determining the driving behaviours.

Data

Type

Description

dis_to_signal_

double

Distance to the next signal

signal_remained_time_

double

Next signal green light remained time (Judging green light by signal_can_go_)

signal_can_go_

bool

Judging green light

next_signal_

const TrafficSignal*

Information about the next signal

dis_to_next_car_

double

Distance to the next vehicle in the lane

next_car_

const VehiclePlanned*

Information about the next vehicle in the lane

this_car_

const VehiclePlanned*

Information about this car

left_next_car_

const VehiclePlanned*

Information about the next car in the left lane, similar in the right lane.

left_last_car_

const VehiclePlanned*

Information about the last car in the left lane, similar in the right lane.

dis_to_left_next_car_

double

Distance to the next vehicle in the left lane, similar in the right lane.

dis_to_left_last_car_

double

Distance to the last vehicle in the left lane, similar in the right lane.

num_of_car_to_light_

std::vector<int>

Number of car in front of this car to signal

next_signal_direction_

Direction

The status of the car at the next signal light (turn left, turn right, etc)

Template

We provide a template for users to implement their own driving modules. To customize the driving module, we mainly need to implement the ConductDriving function, which takes aforementioned data as inputs and returns a vector of VehicleAction.

#include "vehicle.h"

/*
 * head file
 */
class DrivingModule {
private:
    const DrivingModule* data_;
    ...
public:
    DrivingModule(const DrivingModule* data):data_(data){};         // obtain all the data above
    std::vector<VehicleAction> ConductDriving();                    // this is the virtual function to implement, do not rename this function
    ...
}

/*
 * source file
 */
std::vector<VehicleAction> DrivingModule::ConductDriving() {
    double dis_to_next_car = data_->dis_to_next_car_;
    ...
}

Example

See source code in /src/modules/driving.cc

Routing Module

Data

Data

Type

Description

roadnet_

RoadNet

A vector of intersections and a graph of the roadnet

vehicle_group_

VehicleGroup

Including OD of all vehicles and the road all vehicles currently on

Template

We provide a template for users to implement their own driving modules. To customize the routing module, we mainly need to implement the ConductRouting function, which observes the overall conditions of the traffic and directly modify the route of the vehicle.

#include "routing.h"

/*
 * head file
 */
class RoutingModule {
private:
    const RoutingData* data_;
    ...
public:
    RoutingModule(const RoutingModule* data):data_(data){};         // obtain all the data above
    void ConductRouting(std::vector<VehicleInfo> &vehicle);         // this is the virtual function to implement, do not rename this function
    ...
}

/*
 * source file
 */
void RoutingModule::ConductRouting(std::vector<VehicleInfo> &vehicle) {
    data_->compacted_graph_->FindShortestPath(from_id, to_id, is_spfa);
    ...
}

Example

See source code in /src/modules/routing.cc.

Deploy the Customized Modules

Cover the driving and routing modules in /src/modules/driving.cc and /src/modules/routing.cc. After that, build the project as the aforementioned installation guidance.

Visualization

CBEngine makes use of yarn for its visualization. The user interface is available on Google Drive.

Install the User Interface

Download the file from Googld Drive and unzip it. Install yarn according to the instruction. For installation in Windows, simply run the following command.

yarn

Obtain Visualization Records

To visualize the simulation, record files are required. Add the following code line in your simulation loop.

engine.log_info(os.path.join(log_path, 'time{}.json'.format(int(engine.get_current_time()))))

A correct sample is as follows.

import cbengine

cfg_file = './config.cfg'
intersections = [1, 2, 3, 4, 5, 6]
LOG_ADDR = './log' # can be modified

running_step = 300
engine = cbengine.Engine(cfg_file, 12)
for step in range(running_step):
    engine.log_info(os.path.join(LOG_ADDR, 'time{}.json'.format(int(engine.get_current_time()))))
    for intersection in intersections:
        engine.set_ttl_phase(intersection, (int(engine.get_current_time()) // 30) % 4 + 1)
    engine.next_step()

Make sure that report_log_addr in your configuration file is an available address, consistent with the LOG_ADDR in the code, then run the simulation script. After the simulation ends, there will be three types of files in the report_log_addr: lightinfo.json, roadinfo.json, timeX.json (X is the time). To visualize these records, move them to ui/src/log in the downloaded user interface.

Boot the Visualization

Before booting the user interface, modify this.maxtime in ui/src/index.js (line 14) to the maximum time of your records. After that, run the following command in ui/ to boot the user interface for visualation:

yarn start

yarn will then wake up a window to visualize the simulation.