Getting started¶
Install¶
PyTraf needs Python 3.9 or newer and has prebuilt wheels for Linux, macOS and Windows:
The same wheels are attached to each release.
Build from source¶
You need Rust, Python 3.9 or newer, and maturin:
git clone https://github.com/harisbal/pytraf pytraf && cd pytraf
uv venv .venv && source .venv/bin/activate # or: python -m venv .venv
uv pip install numpy pandas maturin
maturin develop --release # compile and install `pytraf`
To install into another environment without Rust there, build a wheel once and install it there:
maturin build --release --out dist # writes dist/pytraf-0.3.1-cp39-abi3-*.whl
pip install dist/pytraf-0.3.1-cp39-abi3-*.whl
The wheel targets the stable Python ABI (abi3), so a single wheel works on Python 3.9 and newer. It is
specific to the operating system and CPU it was built on.
A small example¶
3 000 drivers travel from Home to Work in one hour. They can take the ring road, cut through the town centre, or start through the centre and use a short link onto the ring road.
flowchart LR
H((0<br/>Home)) -- "link 0: ring, 6 min" --> R((1<br/>Ring))
R -- "link 1: ring, 6 min" --> W((3<br/>Work))
H -- "link 2: centre, 4 min" --> C((2<br/>Centre))
C -- "link 3: centre, 4 min" --> W
C -- "link 4: shortcut, 1 min" --> R
Describe the roads in one table and the trips in another. Nodes are numbered from 0, and each row of the link table is a link, numbered in order.
import pandas as pd
import pytraf
links = pd.DataFrame({
"from_node": [0, 1, 0, 2, 2],
"to_node": [1, 3, 2, 3, 1],
"free_flow_time": [6.0, 6.0, 4.0, 4.0, 1.0], # minutes on an empty road
"capacity": [2000, 2000, 1500, 800, 1000], # vehicles per hour
})
trips = pd.DataFrame({"origin": [0], "destination": [3], "flow": [3000.0]})
net = pytraf.Network.from_tables(links)
demand = pytraf.Demand.from_table(net, trips)
result = net.assign(demand)
print(result)
# AssignResult(converged=True, iterations=13, relative_gap=2.071e-7, classes=1)
Reading the result¶
result.links.table() gives the result per link as a pandas DataFrame:
from_node to_node flow volume voc time
link
0 0 1 1244.73 1244.73 0.62 6.14
1 1 3 1753.07 1753.07 0.88 6.53
2 0 2 1755.27 1755.27 1.17 5.13
3 2 3 1246.93 1246.93 1.56 7.54
4 2 1 508.33 508.33 0.51 1.01
flow counts vehicles, volume counts them in car units (the same here, with only cars), voc is
volume over capacity and time is the travel time in minutes.
PyTraf keeps the routes themselves, so you can also see who goes where:
origin destination flow time links
path
0 0 3 1246.93 12.67 (2, 3)
1 0 3 1244.73 12.67 (0, 1)
2 0 3 508.33 12.67 (2, 4, 1)
The centre is the shortest route when the roads are empty, but its second link has little capacity, so traffic spreads until all three routes take 12.67 minutes. This is the user equilibrium of Wardrop's first principle: every route in use has the same travel time, and no driver can arrive sooner by switching to another.
The same results are available as numpy arrays indexed by link id (result.links.flow,
result.links.time, ...), which are faster for large networks. print(result.summary()) gives a short
account of the run.
Larger networks¶
Real networks come from files or larger tables, and the call stays the same. The repository includes the Sioux Falls benchmark:
net = pytraf.Network.from_tntp("data/tntp/SiouxFalls/SiouxFalls_net.tntp")
demand = pytraf.Demand.from_tntp(net, "data/tntp/SiouxFalls/SiouxFalls_trips.tntp")
result = net.assign(demand)
GMNS datasets work too; see the Python guide.
Next steps¶
The Python guide continues this example: ban a turn, add trucks, load the network with buses and analyse who uses a link.