Urban freight simulation based on the MASS-GT multi-agent model
Project description
urban-dollop
urban-dollop is a Python library that extracts and packages the mathematical models from MASS-GT — a multi-agent urban freight simulation system originally developed at TU Delft for the Dutch Randstad region. The models are reimplemented as a transparent, installable Python pipeline intended for academic research and reproducible urban logistics studies.
The primary novel contribution is the introduction of road grade as a dimension in emission accounting, enabling topographically accurate estimates in hilly cities.
Usage
Parcel demand generation
Estimate the daily parcel delivery demand for a study area from zonal population and employment data.
from urban_dollop import Zone, Depot, Carrier, SkimMatrix, generate_parcel_demand
zones = Zone.from_file("zones.gpkg")
depots = Depot.from_file("depots.gpkg")
carriers = Carrier.from_file("carrier_shares.csv")
skim = SkimMatrix.from_file("skim_time.mtx", zones)
demands = generate_parcel_demand(zones, depots, carriers, skim)
demands is a list[ParcelDemand] — one record per unique
(destination zone, depot, vehicle type) combination:
| field | type | description |
|---|---|---|
destination_zone_id |
int |
zone ID of the delivery address |
depot_id |
int |
depot that handles this flow |
vehicle_type |
int |
vehicle type code (default 7 = van) |
n_parcels |
int |
number of parcels in this flow |
The origin zone of each flow is implicit — it is always the zone of the depot.
Save to CSV (join to your zones layer in QGIS or ArcGIS on destination_zone_id
to map parcels delivered per zone):
ParcelDemand.to_file(demands, "parcel_demand.csv")
Calibration — via urban-dollop.toml:
Place an urban-dollop.toml in your current working directory to set calibration
parameters without touching code:
[parcel_demand]
parcels_per_household = 0.2054 # B2C daily deliveries per household
parcels_per_employee = 0.0 # B2B daily deliveries per employee
delivery_success_b2c = 0.75 # first-attempt success rate, residential
delivery_success_b2b = 0.95 # first-attempt success rate, commercial
default_vehicle_type = 7 # 7 = van
random_seed = 42
Calibration — programmatic override:
Pass a ParcelDemandConfig to override TOML values in code:
from urban_dollop import ParcelDemandConfig
demands = generate_parcel_demand(
zones, depots, carriers, skim,
config=ParcelDemandConfig(
parcels_per_household = 0.178,
parcels_per_employee = 0.029,
delivery_success_b2c = 0.80,
delivery_success_b2b = 0.95,
default_vehicle_type = 7,
),
)
Programmatic values take precedence over TOML. Missing fields fall back to TOML. Pydantic raises if a required field is absent from both sources.
Column mapping — when your files use different column names:
zones = Zone.from_file("zones.gpkg", columns={
"zone_id": "id",
"households": "hh_count",
"employment": "jobs",
})
carriers = Carrier.from_file("shares.csv", columns={
"name": "courier",
"share": "market_share",
})
Canonical column names:
| model | field | description |
|---|---|---|
Zone |
zone_id |
unique integer zone identifier |
Zone |
households |
household count |
Zone |
employment |
employee count |
Depot |
depot_id |
unique integer depot identifier |
Depot |
zone_id |
zone the depot is located in |
Depot |
carrier |
carrier name (must match Carrier.name) |
Carrier |
name |
carrier name |
Carrier |
share |
market share fraction (all carriers must sum to 1) |
CLI
Run the parcel demand module from canonical files:
urban-dollop generate-demand data/
This command:
- reads
urban-dollop.tomlfrom the current working directory - reads
zones.gpkg,depots.gpkg,carrier_shares.csv, andskim_time.mtxfromdata/ - writes
parcel_demand.csvto the current working directory by default
To write the CSV somewhere else, pass --outdir with either an existing directory
or a full .csv path:
urban-dollop generate-demand --outdir results/ data/
urban-dollop generate-demand --outdir results/joinville_parcel_demand.csv data/
Example outputs
[!NOTE] Example choropleth of simulated parcel deliveries aggregated by
destination_zone_idfor the Joinville fixture scenario. Zone geometry is based on IBGE territorial and census meshes, household counts on IBGE Census / SIDRA, employment on RAIS microdata, and travel times on OpenStreetMap routed with OSRM. Depot locations are scenario inputs compiled from public carrier and agency sources including Correios, Mercado Envios, Loggi, and Amazon. Carrier shares are scenario estimates synthesized from public market and company sources, including Correios and ABComm.
[!NOTE] Example choropleth of simulated parcel deliveries aggregated by
destination_zone_idfor the Delft fixture scenario. Study area covers five municipalities: Den Haag, Delft, Rijswijk, Leidschendam-Voorburg, and Midden-Delfland. Zone geometry, household counts, and employment are based on AHN and CBS data via the LEAD project (2020). Travel times are derived from the MASS-GT Netherlands skim matrix. Depot locations and carrier shares are sourced from the MASS-GT LEADVersion scenario.
Mathematical models
The following models are extracted from MASS-GT's parcel_dmnd module and implemented
in generate_parcel_demand().
Parcel demand generation
Zonal demand
The number of parcel flows destined for zone $z$ on an average weekday:
$$D_z = \left\lfloor \frac{H_z \cdot r_{HH}}{s_{B2C}} + \frac{E_z \cdot r_E}{s_{B2B}} \right\rceil$$
| symbol | ParcelDemandConfig field |
description |
|---|---|---|
| $H_z$ | — | households in zone $z$ |
| $E_z$ | — | employees in zone $z$ |
| $r_{HH}$ | parcels_per_household |
B2C parcels per household per day |
| $r_E$ | parcels_per_employee |
B2B parcels per employee per day |
| $s_{B2C}$ | delivery_success_b2c |
first-attempt delivery success rate, residential |
| $s_{B2B}$ | delivery_success_b2b |
first-attempt delivery success rate, commercial |
The success rates correct for failed first-attempt deliveries: the generated volume reflects shipments sent, not deliveries completed.
Carrier split
Total zonal demand is distributed across carriers by market share:
$$D_{z,k} = \left\lfloor \sigma_k \cdot D_z \right\rceil$$
where $\sigma_k$ is the market share of carrier $k$ (Carrier.share), and
$\sum_k \sigma_k = 1$. This normalization is required by the implementation and
validated before demand is allocated across carriers.
Depot assignment
Each (zone, carrier) flow is assigned to the nearest depot of that carrier by travel time from the depot zone to the destination zone:
$$\delta(z, k) = \underset{n \in \mathcal{N}k}{\arg\min}\ t(n{\text{zone}}, z)$$
where $\mathcal{N}k$ is the set of depots operated by carrier $k$ and $t(n{\text{zone}}, z)$ is the travel time in seconds from the depot's zone to zone $z$, read from the pre-computed skim matrix.
Aggregation
Flows that share the same destination zone, depot, and vehicle type are summed:
$$F_{z,n,v} = \sum_{k,:,\delta(z,k) = n} D_{z,k} \quad \text{for fixed vehicle type } v$$
Each resulting $(z, n, v)$ tuple with $F_{z,n,v} > 0$ becomes one ParcelDemand
record.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file urban_dollop-0.3.2.tar.gz.
File metadata
- Download URL: urban_dollop-0.3.2.tar.gz
- Upload date:
- Size: 24.5 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cbb2b4d5b69fdcd06feb456c48ac6410d932970d99387a54c73d4bd4a2f93d6a
|
|
| MD5 |
6bd0ef3a0efe385ee4d53cad9f4f40fe
|
|
| BLAKE2b-256 |
ac47e7723495d420e3dd28b401e4e10f11e6125ea0043224e1ea5a0e34092e91
|
Provenance
The following attestation bundles were made for urban_dollop-0.3.2.tar.gz:
Publisher:
deployment.yaml on hcubasd/urban-dollop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
urban_dollop-0.3.2.tar.gz -
Subject digest:
cbb2b4d5b69fdcd06feb456c48ac6410d932970d99387a54c73d4bd4a2f93d6a - Sigstore transparency entry: 1738259461
- Sigstore integration time:
-
Permalink:
hcubasd/urban-dollop@27d968f2cbf9c0450f6ebc0cb50816694e9ff35f -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/hcubasd
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
deployment.yaml@27d968f2cbf9c0450f6ebc0cb50816694e9ff35f -
Trigger Event:
push
-
Statement type:
File details
Details for the file urban_dollop-0.3.2-py3-none-any.whl.
File metadata
- Download URL: urban_dollop-0.3.2-py3-none-any.whl
- Upload date:
- Size: 23.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cd141be8813aee3a30881e99c4a6b4d4981d2f6542b1e0a435cc3408632282ca
|
|
| MD5 |
a069fa2e981725d402ee9bb2ec18005c
|
|
| BLAKE2b-256 |
176ad23b3bca5cce6743cb8b2b2fffc2e1d8d2b7d11c1afffb52cfd85f873aec
|
Provenance
The following attestation bundles were made for urban_dollop-0.3.2-py3-none-any.whl:
Publisher:
deployment.yaml on hcubasd/urban-dollop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
urban_dollop-0.3.2-py3-none-any.whl -
Subject digest:
cd141be8813aee3a30881e99c4a6b4d4981d2f6542b1e0a435cc3408632282ca - Sigstore transparency entry: 1738259495
- Sigstore integration time:
-
Permalink:
hcubasd/urban-dollop@27d968f2cbf9c0450f6ebc0cb50816694e9ff35f -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/hcubasd
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
deployment.yaml@27d968f2cbf9c0450f6ebc0cb50816694e9ff35f -
Trigger Event:
push
-
Statement type: