Skip to main content

Urban freight simulation based on the MASS-GT multi-agent model

Project description

urban-dollop

A Python package for urban freight simulation based on the MASS-GT multi-agent model. Consolidates the original six-container LEAD pipeline into a single installable library — one pip install replaces six Docker images.

Built as academic research: every step's mathematical model is documented alongside the API so the simulation is fully transparent and citable.


Install

pip install urban-dollop

For performance-critical runs (enables Numba JIT on hot loops):

pip install urban-dollop[fast]

Usage

Each pipeline step is a plain function: it accepts data objects and returns a pandas.DataFrame. No files are written unless you ask — the library produces results; you decide what to do with them.

Step 1 — Demand generation

Estimate the daily parcel delivery demand for a city from zonal population and employment data.

from urban_dollop import Zone, Depots, CarrierShares, DemandData, generate_demand

data = DemandData(
    zones          = Zone("zones.gpkg"),
    depots         = Depots("depots.gpkg"),
    skim_time      = "skim_time.mtx",
    carrier_shares = CarrierShares("shares.csv"),
    study_area     = ["Joinville"],
)

parcels = generate_demand(data)

parcels is a pandas.DataFrame — one row per parcel:

column type description
parcel_id int unique parcel identifier
origin_zone int zone ID of the originating depot
destination_zone int zone ID of the delivery address
depot_id int depot that handles this parcel
carrier str carrier name (as supplied in carrier_shares)
vehicle_type int vehicle type code (7 = van)
locker_zone int zone ID if routed to a parcel locker; 0 otherwise
segment str "B2C" (business-to-consumer) or "C2C" (consumer-to-consumer)
local_to_local bool parcel originates and delivers within the study area
crowdship_eligible bool eligible for crowdshipping (always a subset of local_to_local)
fulfilment_type str "Hubspoke" or "Hyperconnected"

Save as CSV:

parcels.to_csv("parcel_demand.csv", index=False)

Save as a GIS layer (QGIS, ArcGIS):

from urban_dollop import demand_layer

# attach zone geometry and aggregate by destination zone
gdf = demand_layer(parcels, data.zones)
gdf.to_file("parcel_demand.gpkg")   # drag directly into QGIS

gdf is a geopandas.GeoDataFrame — one row per zone — with the original zone geometry plus:

column description
parcel_count total parcels delivered to this zone
hubspoke_count via standard depot-to-door fulfilment
hyperconnected_count via local / crowdshipping fulfilment
b2c_count business-to-consumer parcels
c2c_count consumer-to-consumer parcels
local_to_local_count parcels that originate within the study area

Column mapping — when your file uses different names:

Each input type expects canonical column names by default. If your files use different names, supply a mapping — only the columns that differ:

data = DemandData(
    zones = Zone("zones.gpkg", columns={
        "zone_id":    "id",
        "households": "hh_count",
        "jobs":       "employment",
    }),
    depots = Depots("depots.gpkg", columns={
        "carrier": "operator",
    }),
    skim_time      = "skim_time.mtx",
    carrier_shares = CarrierShares("shares.csv", columns={
        "share": "market_share",
    }),
    study_area = ["Joinville"],
)

Canonical column names:

input column description
Zone zone_id unique integer zone identifier
Zone municipality municipality name (used for study_area)
Zone households household count
Zone jobs job / employee count
Depots depot_id unique integer depot identifier
Depots zone_id zone the depot is located in
Depots carrier carrier / operator name
CarrierShares share market share fraction (sums to 1)

Adjust calibration parameters:

from urban_dollop import DemandConfig

parcels = generate_demand(
    data,
    DemandConfig(
        parcels_per_household   = 0.178,   # default: 0.2054 (MASS-GT / NL)
        parcels_per_employee    = 0.029,   # default: 0.0
        local_to_local_fraction = 0.08,    # default: 0.04
        random_seed             = 42,
    ),
)

Command-line interface

If your data directory contains the canonical input filenames, you can run the simulation without writing any Python:

urban-dollop generate-demand data/
urban-dollop generate-demand data/ --outdir results/
urban-dollop generate-demand data/ --study-area Joinville --outdir results/

The CLI looks for these filenames in <data-dir>:

file description
zones.gpkg zone polygons with socioeconomic attributes
depots.gpkg depot / parcel node locations
skim_time.mtx travel-time matrix (binary, seconds)
carrier_shares.csv carrier market share table

Output written to <data-dir> (or --outdir):

file description
parcel_demand.csv one row per parcel
parcel_demand.gpkg parcel counts joined to zone geometry

Calibration via config file — place urban-dollop.toml in your project root to override defaults without touching code:

[demand]
parcels_per_household     = 0.178   # NL baseline: 0.2054
parcels_per_employee      = 0.029   # NL baseline: 0.0
local_to_local_fraction   = 0.08    # NL baseline: 0.04
random_seed               = 42

If no config file is present, the MASS-GT original defaults are used.


Mathematical model

Step 1 — Parcel demand generation

Zonal demand

The number of parcels destined for zone $z$ on an average weekday:

$$D_z = \left\lfloor \frac{H_z \cdot r_{HH}}{s_{B2C}(z)} + \frac{E_z \cdot r_E}{s_{B2B}(z)} \right\rceil$$

symbol DemandConfig field description
$H_z$ households in zone $z$ (households)
$E_z$ employees in zone $z$ (jobs)
$r_{HH}$ parcels_per_household parcels per household per day
$r_E$ parcels_per_employee parcels per employee per day
$s_{B2C}^0$ delivery_success_b2c baseline first-attempt success rate, B2C
$s_{B2B}^0$ delivery_success_b2b baseline first-attempt success rate, B2B

When a parcel locker is present in zone $z$, the effective success rate is raised:

$$s_{B2C}(z) = \alpha_z + (1 - \alpha_z), s_{B2C}^0$$

where $\alpha_z = \ell \cdot \mathbf{1}[z \in \mathcal{L}]$, $\mathcal{L}$ is the set of zones with lockers (locker_zones), and $\ell$ is the locker adoption rate (locker_adoption_rate).

Carrier split

Total 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$ (column share in carrier_shares).

Depot assignment

Each parcel is assigned to the nearest depot of its carrier by travel time:

$$\delta(z, k) = \underset{n \in \mathcal{N}_k}{\arg\min}; t(n, z)$$

where $\mathcal{N}_k$ is the set of depots operated by carrier $k$ and $t(n, z)$ is the travel time in seconds from depot $n$ to zone $z$, read from the pre-computed skim matrix.

Market segment

Each parcel is independently assigned a segment:

$$\text{segment}i \sim \text{Bernoulli}(p{C2C}), \qquad p_{C2C} = \frac{r_{HH,C2C}}{r_{HH}}$$

where $r_{HH,C2C}$ is parcels_per_household_c2c.

Local-to-local, crowdshipping, and fulfilment type

A fraction $f_{L2L}$ of parcels are flagged local-to-local; of those, a fraction $f_{CS}$ are crowdshipping-eligible:

$$\text{L2L}i \sim \text{Bernoulli}(f{L2L})$$

$$\text{CS}_i \sim \text{L2L}i \cdot \text{Bernoulli}(f{CS})$$

For L2L parcels, the origin zone is resampled from the study-area household distribution (reflecting that senders are residents, not distant warehouses):

$$P(\text{origin}i = z) = \frac{H_z}{\displaystyle\sum{z'} H_{z'}}$$

Fulfilment type is then assigned deterministically:

$$\text{fulfilment}_i = \begin{cases} \text{Hyperconnected} & \text{if } \text{L2L}_i \lor \text{CS}_i \lor (\text{locker}_i \neq 0) \ \text{Hubspoke} & \text{otherwise} \end{cases}$$


History and research context

Forked from MASS-GT, a multi-agent simulation system for urban goods transport originally developed for the Dutch Randstad region.

This refactor is part of ongoing academic research with two primary contributions:

  1. Unified package. The original six-container LEAD microservice architecture is dissolved into a single in-memory Python pipeline. Shared data (zone maps, skim matrices) is loaded once and passed through all steps without intermediate files.

  2. Grade-aware emission accounting. The original COPERT emission model assumes flat roads. This package introduces road gradient as a third coordinate into the emission step via the Vehicle Specific Power (VSP) formulation, enabling topographically accurate emission estimates in hilly cities. The Joinville (Brazil) case study, with elevation ranging from 0 to 881 m, is the primary validation dataset.

The original LEAD Packaging and Modeler Version source files are preserved in tmp/ during the refactoring process.

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

urban_dollop-0.2.0.tar.gz (267.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

urban_dollop-0.2.0-py3-none-any.whl (21.3 kB view details)

Uploaded Python 3

File details

Details for the file urban_dollop-0.2.0.tar.gz.

File metadata

  • Download URL: urban_dollop-0.2.0.tar.gz
  • Upload date:
  • Size: 267.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for urban_dollop-0.2.0.tar.gz
Algorithm Hash digest
SHA256 b64434a31fcd2ead86220376bed4cda8f15b3c144153dd9a160efa0e14e85596
MD5 baddb201709cc391efa32225a18a2f70
BLAKE2b-256 0011efdf993d4d5d14cb794c10d7a7bd9dc33af4f5a10f630a02aea0eceff4fd

See more details on using hashes here.

Provenance

The following attestation bundles were made for urban_dollop-0.2.0.tar.gz:

Publisher: deployment.yaml on hcubasd/urban-dollop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file urban_dollop-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: urban_dollop-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 21.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for urban_dollop-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c5f9d8f3f1df736ec122992ab66f8db4ec5913708a71b4a98e2537bacb10f572
MD5 678437e9681b254d18837f9c8be8a8d7
BLAKE2b-256 1f9c132f6a46289e62d01900c67d4cee51e04d6d6526e4a994f58ed266f91abf

See more details on using hashes here.

Provenance

The following attestation bundles were made for urban_dollop-0.2.0-py3-none-any.whl:

Publisher: deployment.yaml on hcubasd/urban-dollop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page