tripdist 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tripdist-0.1.0/CHANGELOG.md +31 -0
- tripdist-0.1.0/CITATION.cff +21 -0
- tripdist-0.1.0/LICENSE +21 -0
- tripdist-0.1.0/MANIFEST.in +9 -0
- tripdist-0.1.0/PKG-INFO +251 -0
- tripdist-0.1.0/README.md +216 -0
- tripdist-0.1.0/examples/quickstart.py +56 -0
- tripdist-0.1.0/pyproject.toml +109 -0
- tripdist-0.1.0/setup.cfg +4 -0
- tripdist-0.1.0/src/tripdist/__init__.py +131 -0
- tripdist-0.1.0/src/tripdist/__main__.py +7 -0
- tripdist-0.1.0/src/tripdist/_common.py +106 -0
- tripdist-0.1.0/src/tripdist/_version.py +1 -0
- tripdist-0.1.0/src/tripdist/balancing.py +219 -0
- tripdist-0.1.0/src/tripdist/calibration.py +296 -0
- tripdist-0.1.0/src/tripdist/cli.py +282 -0
- tripdist-0.1.0/src/tripdist/deterrence.py +233 -0
- tripdist-0.1.0/src/tripdist/diagnostics.py +138 -0
- tripdist-0.1.0/src/tripdist/generation.py +282 -0
- tripdist-0.1.0/src/tripdist/gravity.py +280 -0
- tripdist-0.1.0/src/tripdist/growth.py +290 -0
- tripdist-0.1.0/src/tripdist/io.py +208 -0
- tripdist-0.1.0/src/tripdist/py.typed +0 -0
- tripdist-0.1.0/src/tripdist/tlfd.py +178 -0
- tripdist-0.1.0/src/tripdist.egg-info/PKG-INFO +251 -0
- tripdist-0.1.0/src/tripdist.egg-info/SOURCES.txt +35 -0
- tripdist-0.1.0/src/tripdist.egg-info/dependency_links.txt +1 -0
- tripdist-0.1.0/src/tripdist.egg-info/entry_points.txt +2 -0
- tripdist-0.1.0/src/tripdist.egg-info/requires.txt +7 -0
- tripdist-0.1.0/src/tripdist.egg-info/top_level.txt +1 -0
- tripdist-0.1.0/tests/conftest.py +34 -0
- tripdist-0.1.0/tests/test_balancing_growth.py +156 -0
- tripdist-0.1.0/tests/test_calibration_tlfd.py +184 -0
- tripdist-0.1.0/tests/test_common.py +32 -0
- tripdist-0.1.0/tests/test_generation.py +111 -0
- tripdist-0.1.0/tests/test_gravity.py +177 -0
- tripdist-0.1.0/tests/test_io_cli.py +175 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-11-15
|
|
8
|
+
### Added
|
|
9
|
+
- Trip generation: OLS regression with standard errors, t statistics and R²;
|
|
10
|
+
cross-classification (category analysis) trip rates; production/attraction balancing
|
|
11
|
+
with optional fixed zones.
|
|
12
|
+
- Growth-factor methods: uniform, origin- and destination-specific, average factor,
|
|
13
|
+
Detroit, Fratar and Furness, with convergence histories.
|
|
14
|
+
- Furness / IPF biproportional balancing with margin-mismatch handling and diagnostics.
|
|
15
|
+
- Deterrence functions: exponential, power, combined (Tanner), gamma and tabulated
|
|
16
|
+
friction factors; JSON-style specs.
|
|
17
|
+
- Gravity models: unconstrained, production-, attraction- and doubly constrained, with
|
|
18
|
+
K-factors, intrazonal exclusion and nearest-neighbour intrazonal costs.
|
|
19
|
+
- Calibration: Hyman's (1969) method for any single deterrence parameter, iterative
|
|
20
|
+
friction-factor fitting to an observed TLFD, ratio K-factors by zone or district.
|
|
21
|
+
- Trip length frequency distributions, coincidence ratio, RMSE / %RMSE / R² / GEH matrix
|
|
22
|
+
comparison, district aggregation.
|
|
23
|
+
- CSV / JSON I/O and the command line: `tripdist balance | growth | gravity | calibrate | tlfd`.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- Corrected section references to Ortúzar & Willumsen (2011): Hyman's calibration method
|
|
27
|
+
is in section 5.5 (calibration of gravity models), not 5.6; other uncertain section
|
|
28
|
+
numbers replaced by chapter references.
|
|
29
|
+
- Gamma deterrence: documented the sign mapping to the NCHRP Report 716 notation
|
|
30
|
+
`a t^b exp(c t)` (`b = -b_716`, `g = -c_716`); the form was previously described as the
|
|
31
|
+
report's own parameterisation.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use this software, please cite it as below."
|
|
3
|
+
authors:
|
|
4
|
+
- family-names: "Eminoğlu"
|
|
5
|
+
given-names: "Yusuf"
|
|
6
|
+
email: "geospacephilo@gmail.com"
|
|
7
|
+
orcid: "https://orcid.org/0009-0005-6000-2934"
|
|
8
|
+
title: "tripdist"
|
|
9
|
+
version: "0.1.0"
|
|
10
|
+
date-released: "2026-11-15"
|
|
11
|
+
license: "MIT"
|
|
12
|
+
repository-code: "https://gitlab.com/geophilo1/tripdist_sdk"
|
|
13
|
+
url: "https://pypi.org/project/tripdist/"
|
|
14
|
+
abstract: "Trip generation and distribution for the four-step travel demand model: regression and cross-classification trip generation, production/attraction balancing, growth-factor methods (uniform, average, Detroit, Fratar, Furness), unconstrained, singly and doubly constrained gravity models with exponential, power, combined, gamma and tabulated deterrence functions, Hyman calibration to an observed mean trip cost, trip length frequency distribution comparison, K-factors and convergence diagnostics."
|
|
15
|
+
keywords:
|
|
16
|
+
- transport planning
|
|
17
|
+
- travel demand
|
|
18
|
+
- trip distribution
|
|
19
|
+
- gravity model
|
|
20
|
+
- Furness method
|
|
21
|
+
- four-step model
|
tripdist-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yusuf Eminoğlu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tripdist-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tripdist
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Trip generation and distribution for the four-step model: regression and cross-classification, P/A balancing, growth-factor methods, gravity models with Furness balancing, Hyman calibration and trip length frequency diagnostics.
|
|
5
|
+
Author-email: Yusuf Eminoğlu <geospacephilo@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://gitlab.com/geophilo1/tripdist_sdk
|
|
8
|
+
Project-URL: Repository, https://gitlab.com/geophilo1/tripdist_sdk
|
|
9
|
+
Project-URL: Issues, https://gitlab.com/geophilo1/tripdist_sdk/-/work_items
|
|
10
|
+
Project-URL: Changelog, https://gitlab.com/geophilo1/tripdist_sdk/-/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: transport-planning,travel-demand,four-step-model,trip-generation,trip-distribution,gravity-model,furness,fratar,ipf,hyman,origin-destination,od-matrix,urban-planning
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Environment :: Console
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering
|
|
22
|
+
Classifier: Intended Audience :: Science/Research
|
|
23
|
+
Classifier: Intended Audience :: Developers
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: numpy>=1.21
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
33
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# tripdist
|
|
37
|
+
|
|
38
|
+
[](https://pypi.org/project/tripdist/)
|
|
39
|
+
[](https://pypi.org/project/tripdist/)
|
|
40
|
+
[](https://gitlab.com/geophilo1/tripdist_sdk/-/pipelines)
|
|
41
|
+
[](https://gitlab.com/geophilo1/tripdist_sdk/-/blob/main/LICENSE)
|
|
42
|
+
|
|
43
|
+
**Trip generation and distribution (stages 1 and 2 of the four-step model) in plain NumPy.**
|
|
44
|
+
|
|
45
|
+
`tripdist` covers the first two stages of the classic four-step travel demand model as
|
|
46
|
+
set out in Ortúzar & Willumsen, *Modelling Transport*:
|
|
47
|
+
|
|
48
|
+
- **Trip generation**: zonal or household linear regression with standard errors, t
|
|
49
|
+
statistics and R²; cross-classification (category analysis) trip rates; balancing of
|
|
50
|
+
productions and attractions.
|
|
51
|
+
- **Growth-factor methods**: uniform, singly constrained, average factor, Detroit, Fratar
|
|
52
|
+
and Furness.
|
|
53
|
+
- **Gravity models**: unconstrained, production-constrained, attraction-constrained and
|
|
54
|
+
doubly constrained (Furness / IPF balancing), with exponential, power, combined (Tanner),
|
|
55
|
+
gamma and tabulated deterrence functions, K-factors and intrazonal handling.
|
|
56
|
+
- **Calibration**: Hyman's (1969) method for a deterrence parameter against an observed
|
|
57
|
+
mean trip cost, iterative fitting of stepwise friction factors to an observed trip
|
|
58
|
+
length frequency distribution, ratio K-factors at zone or district level.
|
|
59
|
+
- **Diagnostics**: Furness convergence history, trip length frequency distributions and
|
|
60
|
+
the coincidence ratio, RMSE / %RMSE / R² / GEH matrix comparison, district aggregation.
|
|
61
|
+
|
|
62
|
+
It needs only NumPy. A command-line tool reads CSV matrices and zone tables and writes JSON
|
|
63
|
+
and CSV.
|
|
64
|
+
|
|
65
|
+
## Installation
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install tripdist
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Requires Python 3.9 or newer.
|
|
72
|
+
|
|
73
|
+
## Quickstart
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import numpy as np
|
|
77
|
+
import tripdist as td
|
|
78
|
+
|
|
79
|
+
cost = np.array([[3, 11, 18, 22],
|
|
80
|
+
[12, 3, 13, 19],
|
|
81
|
+
[15.5, 13, 5, 7],
|
|
82
|
+
[24, 18, 8, 5]], dtype=float) # minutes
|
|
83
|
+
productions = np.array([98, 106, 122, 174], dtype=float)
|
|
84
|
+
attractions = np.array([102, 118, 141, 139], dtype=float)
|
|
85
|
+
|
|
86
|
+
# doubly constrained gravity model with an exponential deterrence function
|
|
87
|
+
res = td.gravity(cost, productions, attractions, td.exponential(0.1))
|
|
88
|
+
res.matrix, res.iterations, res.converged, res.mean_cost
|
|
89
|
+
res.history # largest relative margin error per Furness pass
|
|
90
|
+
|
|
91
|
+
# calibrate beta so the model reproduces an observed mean trip cost (Hyman 1969)
|
|
92
|
+
cal = td.hyman(cost, productions, attractions, target_mean_cost=10.0)
|
|
93
|
+
cal.value, cal.modelled_mean_cost, cal.trace
|
|
94
|
+
|
|
95
|
+
# compare trip length distributions with an observed matrix
|
|
96
|
+
edges = td.cost_bins(cost, 5.0)
|
|
97
|
+
cmp = td.compare_tlfd(cal.model.matrix, observed_matrix, cost, edges)
|
|
98
|
+
cmp.coincidence_ratio
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Trip generation and growth factors:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
m = td.fit_regression(trips, np.column_stack([households, cars]), ["households", "cars"])
|
|
105
|
+
m.coefficients, m.t_stats, m.r_squared
|
|
106
|
+
rates = td.cross_classification_rates(hh_trips, list(zip(car_ownership, hh_size)))
|
|
107
|
+
p = td.apply_category_rates(households_by_category, rates, category_keys)
|
|
108
|
+
p, a = td.balance_pa(p, a, method="productions")
|
|
109
|
+
|
|
110
|
+
td.furness_growth(base, origin_totals=o_future, dest_totals=d_future)
|
|
111
|
+
td.fratar(base, origin_factors=tau, dest_factors=tau)
|
|
112
|
+
td.growth_factor(base, "detroit", origin_totals=o_future, dest_totals=d_future)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Other deterrence functions and options:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
td.power(2.0) # c^-n
|
|
119
|
+
td.combined(-0.5, 0.05) # Tanner: c^n exp(-beta c)
|
|
120
|
+
td.gamma(a=1.0, b=0.5, g=0.05) # a c^-b exp(-g c); parameters are yours
|
|
121
|
+
td.tabular([0, 5, 10, 20], [1.0, 0.6, 0.2])
|
|
122
|
+
td.gravity(cost, p, a, {"form": "exponential", "beta": 0.08},
|
|
123
|
+
constraint="production", k_factors=k, intrazonal="exclude")
|
|
124
|
+
td.intrazonal_costs(cost, factor=0.5, neighbours=1) # factor is a modelling choice
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Command line
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
tripdist balance --zones zones.csv --method productions --csv balanced.csv
|
|
131
|
+
tripdist growth base.csv --method furness --targets targets.csv --matrix-out future.csv
|
|
132
|
+
tripdist gravity cost.csv --zones zones.csv --form exponential --beta 0.1 \
|
|
133
|
+
--observed observed.csv --bin-width 5 --matrix-out modelled.csv -o report.json
|
|
134
|
+
tripdist calibrate cost.csv --zones zones.csv --observed observed.csv
|
|
135
|
+
tripdist calibrate cost.csv --zones zones.csv --target-mean 14 --form power --n 1 \
|
|
136
|
+
--parameter n --start 1 --zero-cost-value 1
|
|
137
|
+
tripdist tlfd modelled.csv cost.csv --edges 0,5,10,20,40 --observed observed.csv --csv tlfd.csv
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Matrices are CSV grids, either plain or with a header row and a first column of zone
|
|
141
|
+
labels (give the top-left cell a name such as `zone`); empty cells are 0 and `inf` marks
|
|
142
|
+
impossible movements. Zone tables are CSV files with a header (`zone,productions,
|
|
143
|
+
attractions` by default; growth targets use `origins,destinations`). Deterrence functions
|
|
144
|
+
come from `--form` with its parameters or from a JSON file such as
|
|
145
|
+
`{"form": "gamma", "a": 1, "b": 0.5, "g": 0.05}`. Every command prints JSON or writes it
|
|
146
|
+
with `-o`.
|
|
147
|
+
|
|
148
|
+
## API overview
|
|
149
|
+
|
|
150
|
+
| Area | Functions / classes |
|
|
151
|
+
| --- | --- |
|
|
152
|
+
| Generation | `fit_regression -> RegressionModel`, `cross_classification_rates -> CategoryRates`, `apply_category_rates`, `balance_pa` |
|
|
153
|
+
| Balancing | `furness -> BalanceResult`, `margin_error` |
|
|
154
|
+
| Growth factors | `uniform_growth`, `singly_constrained_growth`, `average_factor`, `detroit`, `fratar`, `furness_growth`, `growth_factor -> GrowthResult` |
|
|
155
|
+
| Deterrence | `exponential`, `power`, `combined` / `tanner`, `gamma`, `tabular`, `deterrence_from_dict -> Deterrence` |
|
|
156
|
+
| Gravity | `gravity -> GravityResult`, `mean_cost`, `intrazonal_costs`, `intrazonal_share` |
|
|
157
|
+
| Calibration | `hyman -> CalibrationResult`, `observed_mean_cost`, `calibrate_friction_factors -> FrictionCalibration`, `k_factors` |
|
|
158
|
+
| TLFD | `tlfd -> TLFD`, `compare_tlfd -> TLFDComparison`, `coincidence_ratio`, `cost_bins` |
|
|
159
|
+
| Diagnostics | `summarize`, `compare_matrices -> MatrixFit`, `geh`, `aggregate` |
|
|
160
|
+
| I/O | `read_matrix`, `write_matrix`, `read_zone_table`, `read_long`, `read_json`, `write_json` |
|
|
161
|
+
|
|
162
|
+
All result objects have `to_dict()` returning JSON-safe values (NaN and infinity become
|
|
163
|
+
`null`).
|
|
164
|
+
|
|
165
|
+
## Methods and sources
|
|
166
|
+
|
|
167
|
+
| Method | Definition | Source |
|
|
168
|
+
| --- | --- | --- |
|
|
169
|
+
| Regression trip generation | OLS `y = b0 + sum b_k x_k`, with SE, t, R², adjusted R² | Ortúzar & Willumsen (2011), ch. 4 |
|
|
170
|
+
| Cross-classification | mean trips per household by category; `P_i = sum_c H_ic t_c` | Wootton & Pick (1967) |
|
|
171
|
+
| P/A balancing | scale attractions to productions (or the reverse, or the mean); optional fixed zones | Ortúzar & Willumsen (2011), ch. 4 |
|
|
172
|
+
| Uniform, singly constrained | `T = tau t`; `T_ij = tau_i t_ij` | Ortúzar & Willumsen (2011), §5.2 |
|
|
173
|
+
| Average factor | `T_ij = t_ij (tau_i + tau_j) / 2`, iterated | Ortúzar & Willumsen (2011), §5.2 |
|
|
174
|
+
| Detroit | `T_ij = t_ij tau_i tau_j / tau`, iterated | Detroit Metropolitan Area Traffic Study (1956), via Ortúzar & Willumsen |
|
|
175
|
+
| Fratar | `T_ij = t_ij tau_i tau_j (L_i + L_j) / 2`, `L_i = sum_j t_ij / sum_j t_ij tau_j`, iterated | Fratar (1954) |
|
|
176
|
+
| Furness / IPF | `T_ij = A_i B_j t_ij`, alternate row and column scaling | Furness (1965); Deming & Stephan (1940); Bacharach (1970) |
|
|
177
|
+
| Gravity model | `T_ij = A_i O_i B_j D_j f(c_ij)` and its singly / unconstrained forms | Wilson (1970); Ortúzar & Willumsen (2011), §5.3 |
|
|
178
|
+
| Deterrence | `exp(-beta c)`, `c^-n`, `c^n exp(-beta c)` | Ortúzar & Willumsen (2011), ch. 5; Tanner (1961) |
|
|
179
|
+
| Gamma deterrence | `a c^-b exp(-g c)`, i.e. NCHRP 716's `a t^b exp(c t)` with `b = -b_716`, `g = -c_716` (form only; no parameter tables included) | NCHRP Report 716 (2012) |
|
|
180
|
+
| Hyman calibration | `beta_0 = 1/c*`, `beta_1 = beta_0 c_0 / c*`, then secant updates on the mean cost | Hyman (1969); Ortúzar & Willumsen (2011), §5.5 |
|
|
181
|
+
| Friction-factor fitting | `F_k <- F_k p_k(obs) / p_k(mod)` per cost band | FHWA (1965) |
|
|
182
|
+
| K-factors | ratio `observed / modelled`, by zone or district pair | FHWA (1965) (simple ratio form) |
|
|
183
|
+
| Coincidence ratio | `sum min(p_k, q_k) / sum max(p_k, q_k)` | FHWA Travel Model Validation Manual (2010) |
|
|
184
|
+
| GEH | `sqrt(2 (M - O)^2 / (M + O))` | UK DfT TAG unit M3.1 |
|
|
185
|
+
|
|
186
|
+
Notes:
|
|
187
|
+
|
|
188
|
+
- The Furness iteration stops when the largest relative margin error falls below `tol`
|
|
189
|
+
(default `1e-6`). Unequal target totals raise an error unless `total_mismatch` says how
|
|
190
|
+
to reconcile them. Seed rows or columns that are all zero but have positive targets
|
|
191
|
+
raise an error; other infeasible zero patterns are reported as `converged=False`.
|
|
192
|
+
- Infinite costs give `f = 0`. Power-type deterrence at `c = 0` raises an error unless
|
|
193
|
+
intrazonal costs are set or `zero_cost_value` is given.
|
|
194
|
+
- In Hyman's method the start value `1 / c*` applies to the exponential `beta`; for other
|
|
195
|
+
forms give `start`. If a secant step would make a positive parameter non-positive, the
|
|
196
|
+
step is halved towards zero instead.
|
|
197
|
+
- `intrazonal_costs` replaces the diagonal by a fraction of the cost to the nearest
|
|
198
|
+
zone(s); the fraction is yours to choose.
|
|
199
|
+
|
|
200
|
+
## Limitations
|
|
201
|
+
|
|
202
|
+
- No default parameter values: published parameter sets (for example the NCHRP 716 gamma
|
|
203
|
+
tables) are not bundled; calibrate or supply your own.
|
|
204
|
+
- No acceptance thresholds for coincidence ratios, %RMSE or GEH are built in; they differ
|
|
205
|
+
between guidance documents.
|
|
206
|
+
- Dense matrices only; large zone systems (tens of thousands of zones) need care with
|
|
207
|
+
memory.
|
|
208
|
+
- No mode choice or assignment: this package covers stages 1 and 2 only.
|
|
209
|
+
|
|
210
|
+
## References
|
|
211
|
+
|
|
212
|
+
- Bacharach, M. (1970). *Biproportional Matrices and Input-Output Change*. Cambridge
|
|
213
|
+
University Press.
|
|
214
|
+
- Cambridge Systematics (2010). *Travel Model Validation and Reasonableness Checking
|
|
215
|
+
Manual*, 2nd ed. Federal Highway Administration, Washington, DC.
|
|
216
|
+
- Cambridge Systematics et al. (2012). *NCHRP Report 716: Travel Demand Forecasting:
|
|
217
|
+
Parameters and Techniques*. Transportation Research Board, Washington, DC.
|
|
218
|
+
- Deming, W. E. & Stephan, F. F. (1940). On a least squares adjustment of a sampled
|
|
219
|
+
frequency table when the expected marginal totals are known. *Annals of Mathematical
|
|
220
|
+
Statistics* 11(4), 427-444.
|
|
221
|
+
- Federal Highway Administration (1965). *Calibrating and Testing a Gravity Model for Any
|
|
222
|
+
Size Urban Area*. US Department of Commerce, Bureau of Public Roads.
|
|
223
|
+
- Fratar, T. J. (1954). Vehicular trip distribution by successive approximations.
|
|
224
|
+
*Traffic Quarterly* 8(1), 53-65.
|
|
225
|
+
- Furness, K. P. (1965). Time function iteration. *Traffic Engineering and Control* 7(7),
|
|
226
|
+
458-460.
|
|
227
|
+
- Hyman, G. M. (1969). The calibration of trip distribution models. *Environment and
|
|
228
|
+
Planning* 1(1), 105-112.
|
|
229
|
+
- Ortúzar, J. de D. & Willumsen, L. G. (2011). *Modelling Transport*, 4th ed. Wiley,
|
|
230
|
+
Chichester.
|
|
231
|
+
- Tanner, J. C. (1961). *Factors affecting the amount of travel*. Road Research Technical
|
|
232
|
+
Paper 51, HMSO, London.
|
|
233
|
+
- Wilson, A. G. (1970). *Entropy in Urban and Regional Modelling*. Pion, London.
|
|
234
|
+
- Wootton, H. J. & Pick, G. W. (1967). A model for trips generated by households.
|
|
235
|
+
*Journal of Transport Economics and Policy* 1(2), 137-153.
|
|
236
|
+
|
|
237
|
+
## Academic citation
|
|
238
|
+
|
|
239
|
+
```bibtex
|
|
240
|
+
@software{eminoglu_tripdist,
|
|
241
|
+
author = {Eminoğlu, Yusuf},
|
|
242
|
+
title = {tripdist},
|
|
243
|
+
year = {2026},
|
|
244
|
+
version = {0.1.0},
|
|
245
|
+
url = {https://gitlab.com/geophilo1/tripdist_sdk}
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## License
|
|
250
|
+
|
|
251
|
+
MIT, see [LICENSE](LICENSE).
|
tripdist-0.1.0/README.md
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# tripdist
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/tripdist/)
|
|
4
|
+
[](https://pypi.org/project/tripdist/)
|
|
5
|
+
[](https://gitlab.com/geophilo1/tripdist_sdk/-/pipelines)
|
|
6
|
+
[](https://gitlab.com/geophilo1/tripdist_sdk/-/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
**Trip generation and distribution (stages 1 and 2 of the four-step model) in plain NumPy.**
|
|
9
|
+
|
|
10
|
+
`tripdist` covers the first two stages of the classic four-step travel demand model as
|
|
11
|
+
set out in Ortúzar & Willumsen, *Modelling Transport*:
|
|
12
|
+
|
|
13
|
+
- **Trip generation**: zonal or household linear regression with standard errors, t
|
|
14
|
+
statistics and R²; cross-classification (category analysis) trip rates; balancing of
|
|
15
|
+
productions and attractions.
|
|
16
|
+
- **Growth-factor methods**: uniform, singly constrained, average factor, Detroit, Fratar
|
|
17
|
+
and Furness.
|
|
18
|
+
- **Gravity models**: unconstrained, production-constrained, attraction-constrained and
|
|
19
|
+
doubly constrained (Furness / IPF balancing), with exponential, power, combined (Tanner),
|
|
20
|
+
gamma and tabulated deterrence functions, K-factors and intrazonal handling.
|
|
21
|
+
- **Calibration**: Hyman's (1969) method for a deterrence parameter against an observed
|
|
22
|
+
mean trip cost, iterative fitting of stepwise friction factors to an observed trip
|
|
23
|
+
length frequency distribution, ratio K-factors at zone or district level.
|
|
24
|
+
- **Diagnostics**: Furness convergence history, trip length frequency distributions and
|
|
25
|
+
the coincidence ratio, RMSE / %RMSE / R² / GEH matrix comparison, district aggregation.
|
|
26
|
+
|
|
27
|
+
It needs only NumPy. A command-line tool reads CSV matrices and zone tables and writes JSON
|
|
28
|
+
and CSV.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install tripdist
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Requires Python 3.9 or newer.
|
|
37
|
+
|
|
38
|
+
## Quickstart
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import numpy as np
|
|
42
|
+
import tripdist as td
|
|
43
|
+
|
|
44
|
+
cost = np.array([[3, 11, 18, 22],
|
|
45
|
+
[12, 3, 13, 19],
|
|
46
|
+
[15.5, 13, 5, 7],
|
|
47
|
+
[24, 18, 8, 5]], dtype=float) # minutes
|
|
48
|
+
productions = np.array([98, 106, 122, 174], dtype=float)
|
|
49
|
+
attractions = np.array([102, 118, 141, 139], dtype=float)
|
|
50
|
+
|
|
51
|
+
# doubly constrained gravity model with an exponential deterrence function
|
|
52
|
+
res = td.gravity(cost, productions, attractions, td.exponential(0.1))
|
|
53
|
+
res.matrix, res.iterations, res.converged, res.mean_cost
|
|
54
|
+
res.history # largest relative margin error per Furness pass
|
|
55
|
+
|
|
56
|
+
# calibrate beta so the model reproduces an observed mean trip cost (Hyman 1969)
|
|
57
|
+
cal = td.hyman(cost, productions, attractions, target_mean_cost=10.0)
|
|
58
|
+
cal.value, cal.modelled_mean_cost, cal.trace
|
|
59
|
+
|
|
60
|
+
# compare trip length distributions with an observed matrix
|
|
61
|
+
edges = td.cost_bins(cost, 5.0)
|
|
62
|
+
cmp = td.compare_tlfd(cal.model.matrix, observed_matrix, cost, edges)
|
|
63
|
+
cmp.coincidence_ratio
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Trip generation and growth factors:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
m = td.fit_regression(trips, np.column_stack([households, cars]), ["households", "cars"])
|
|
70
|
+
m.coefficients, m.t_stats, m.r_squared
|
|
71
|
+
rates = td.cross_classification_rates(hh_trips, list(zip(car_ownership, hh_size)))
|
|
72
|
+
p = td.apply_category_rates(households_by_category, rates, category_keys)
|
|
73
|
+
p, a = td.balance_pa(p, a, method="productions")
|
|
74
|
+
|
|
75
|
+
td.furness_growth(base, origin_totals=o_future, dest_totals=d_future)
|
|
76
|
+
td.fratar(base, origin_factors=tau, dest_factors=tau)
|
|
77
|
+
td.growth_factor(base, "detroit", origin_totals=o_future, dest_totals=d_future)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Other deterrence functions and options:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
td.power(2.0) # c^-n
|
|
84
|
+
td.combined(-0.5, 0.05) # Tanner: c^n exp(-beta c)
|
|
85
|
+
td.gamma(a=1.0, b=0.5, g=0.05) # a c^-b exp(-g c); parameters are yours
|
|
86
|
+
td.tabular([0, 5, 10, 20], [1.0, 0.6, 0.2])
|
|
87
|
+
td.gravity(cost, p, a, {"form": "exponential", "beta": 0.08},
|
|
88
|
+
constraint="production", k_factors=k, intrazonal="exclude")
|
|
89
|
+
td.intrazonal_costs(cost, factor=0.5, neighbours=1) # factor is a modelling choice
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Command line
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
tripdist balance --zones zones.csv --method productions --csv balanced.csv
|
|
96
|
+
tripdist growth base.csv --method furness --targets targets.csv --matrix-out future.csv
|
|
97
|
+
tripdist gravity cost.csv --zones zones.csv --form exponential --beta 0.1 \
|
|
98
|
+
--observed observed.csv --bin-width 5 --matrix-out modelled.csv -o report.json
|
|
99
|
+
tripdist calibrate cost.csv --zones zones.csv --observed observed.csv
|
|
100
|
+
tripdist calibrate cost.csv --zones zones.csv --target-mean 14 --form power --n 1 \
|
|
101
|
+
--parameter n --start 1 --zero-cost-value 1
|
|
102
|
+
tripdist tlfd modelled.csv cost.csv --edges 0,5,10,20,40 --observed observed.csv --csv tlfd.csv
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Matrices are CSV grids, either plain or with a header row and a first column of zone
|
|
106
|
+
labels (give the top-left cell a name such as `zone`); empty cells are 0 and `inf` marks
|
|
107
|
+
impossible movements. Zone tables are CSV files with a header (`zone,productions,
|
|
108
|
+
attractions` by default; growth targets use `origins,destinations`). Deterrence functions
|
|
109
|
+
come from `--form` with its parameters or from a JSON file such as
|
|
110
|
+
`{"form": "gamma", "a": 1, "b": 0.5, "g": 0.05}`. Every command prints JSON or writes it
|
|
111
|
+
with `-o`.
|
|
112
|
+
|
|
113
|
+
## API overview
|
|
114
|
+
|
|
115
|
+
| Area | Functions / classes |
|
|
116
|
+
| --- | --- |
|
|
117
|
+
| Generation | `fit_regression -> RegressionModel`, `cross_classification_rates -> CategoryRates`, `apply_category_rates`, `balance_pa` |
|
|
118
|
+
| Balancing | `furness -> BalanceResult`, `margin_error` |
|
|
119
|
+
| Growth factors | `uniform_growth`, `singly_constrained_growth`, `average_factor`, `detroit`, `fratar`, `furness_growth`, `growth_factor -> GrowthResult` |
|
|
120
|
+
| Deterrence | `exponential`, `power`, `combined` / `tanner`, `gamma`, `tabular`, `deterrence_from_dict -> Deterrence` |
|
|
121
|
+
| Gravity | `gravity -> GravityResult`, `mean_cost`, `intrazonal_costs`, `intrazonal_share` |
|
|
122
|
+
| Calibration | `hyman -> CalibrationResult`, `observed_mean_cost`, `calibrate_friction_factors -> FrictionCalibration`, `k_factors` |
|
|
123
|
+
| TLFD | `tlfd -> TLFD`, `compare_tlfd -> TLFDComparison`, `coincidence_ratio`, `cost_bins` |
|
|
124
|
+
| Diagnostics | `summarize`, `compare_matrices -> MatrixFit`, `geh`, `aggregate` |
|
|
125
|
+
| I/O | `read_matrix`, `write_matrix`, `read_zone_table`, `read_long`, `read_json`, `write_json` |
|
|
126
|
+
|
|
127
|
+
All result objects have `to_dict()` returning JSON-safe values (NaN and infinity become
|
|
128
|
+
`null`).
|
|
129
|
+
|
|
130
|
+
## Methods and sources
|
|
131
|
+
|
|
132
|
+
| Method | Definition | Source |
|
|
133
|
+
| --- | --- | --- |
|
|
134
|
+
| Regression trip generation | OLS `y = b0 + sum b_k x_k`, with SE, t, R², adjusted R² | Ortúzar & Willumsen (2011), ch. 4 |
|
|
135
|
+
| Cross-classification | mean trips per household by category; `P_i = sum_c H_ic t_c` | Wootton & Pick (1967) |
|
|
136
|
+
| P/A balancing | scale attractions to productions (or the reverse, or the mean); optional fixed zones | Ortúzar & Willumsen (2011), ch. 4 |
|
|
137
|
+
| Uniform, singly constrained | `T = tau t`; `T_ij = tau_i t_ij` | Ortúzar & Willumsen (2011), §5.2 |
|
|
138
|
+
| Average factor | `T_ij = t_ij (tau_i + tau_j) / 2`, iterated | Ortúzar & Willumsen (2011), §5.2 |
|
|
139
|
+
| Detroit | `T_ij = t_ij tau_i tau_j / tau`, iterated | Detroit Metropolitan Area Traffic Study (1956), via Ortúzar & Willumsen |
|
|
140
|
+
| Fratar | `T_ij = t_ij tau_i tau_j (L_i + L_j) / 2`, `L_i = sum_j t_ij / sum_j t_ij tau_j`, iterated | Fratar (1954) |
|
|
141
|
+
| Furness / IPF | `T_ij = A_i B_j t_ij`, alternate row and column scaling | Furness (1965); Deming & Stephan (1940); Bacharach (1970) |
|
|
142
|
+
| Gravity model | `T_ij = A_i O_i B_j D_j f(c_ij)` and its singly / unconstrained forms | Wilson (1970); Ortúzar & Willumsen (2011), §5.3 |
|
|
143
|
+
| Deterrence | `exp(-beta c)`, `c^-n`, `c^n exp(-beta c)` | Ortúzar & Willumsen (2011), ch. 5; Tanner (1961) |
|
|
144
|
+
| Gamma deterrence | `a c^-b exp(-g c)`, i.e. NCHRP 716's `a t^b exp(c t)` with `b = -b_716`, `g = -c_716` (form only; no parameter tables included) | NCHRP Report 716 (2012) |
|
|
145
|
+
| Hyman calibration | `beta_0 = 1/c*`, `beta_1 = beta_0 c_0 / c*`, then secant updates on the mean cost | Hyman (1969); Ortúzar & Willumsen (2011), §5.5 |
|
|
146
|
+
| Friction-factor fitting | `F_k <- F_k p_k(obs) / p_k(mod)` per cost band | FHWA (1965) |
|
|
147
|
+
| K-factors | ratio `observed / modelled`, by zone or district pair | FHWA (1965) (simple ratio form) |
|
|
148
|
+
| Coincidence ratio | `sum min(p_k, q_k) / sum max(p_k, q_k)` | FHWA Travel Model Validation Manual (2010) |
|
|
149
|
+
| GEH | `sqrt(2 (M - O)^2 / (M + O))` | UK DfT TAG unit M3.1 |
|
|
150
|
+
|
|
151
|
+
Notes:
|
|
152
|
+
|
|
153
|
+
- The Furness iteration stops when the largest relative margin error falls below `tol`
|
|
154
|
+
(default `1e-6`). Unequal target totals raise an error unless `total_mismatch` says how
|
|
155
|
+
to reconcile them. Seed rows or columns that are all zero but have positive targets
|
|
156
|
+
raise an error; other infeasible zero patterns are reported as `converged=False`.
|
|
157
|
+
- Infinite costs give `f = 0`. Power-type deterrence at `c = 0` raises an error unless
|
|
158
|
+
intrazonal costs are set or `zero_cost_value` is given.
|
|
159
|
+
- In Hyman's method the start value `1 / c*` applies to the exponential `beta`; for other
|
|
160
|
+
forms give `start`. If a secant step would make a positive parameter non-positive, the
|
|
161
|
+
step is halved towards zero instead.
|
|
162
|
+
- `intrazonal_costs` replaces the diagonal by a fraction of the cost to the nearest
|
|
163
|
+
zone(s); the fraction is yours to choose.
|
|
164
|
+
|
|
165
|
+
## Limitations
|
|
166
|
+
|
|
167
|
+
- No default parameter values: published parameter sets (for example the NCHRP 716 gamma
|
|
168
|
+
tables) are not bundled; calibrate or supply your own.
|
|
169
|
+
- No acceptance thresholds for coincidence ratios, %RMSE or GEH are built in; they differ
|
|
170
|
+
between guidance documents.
|
|
171
|
+
- Dense matrices only; large zone systems (tens of thousands of zones) need care with
|
|
172
|
+
memory.
|
|
173
|
+
- No mode choice or assignment: this package covers stages 1 and 2 only.
|
|
174
|
+
|
|
175
|
+
## References
|
|
176
|
+
|
|
177
|
+
- Bacharach, M. (1970). *Biproportional Matrices and Input-Output Change*. Cambridge
|
|
178
|
+
University Press.
|
|
179
|
+
- Cambridge Systematics (2010). *Travel Model Validation and Reasonableness Checking
|
|
180
|
+
Manual*, 2nd ed. Federal Highway Administration, Washington, DC.
|
|
181
|
+
- Cambridge Systematics et al. (2012). *NCHRP Report 716: Travel Demand Forecasting:
|
|
182
|
+
Parameters and Techniques*. Transportation Research Board, Washington, DC.
|
|
183
|
+
- Deming, W. E. & Stephan, F. F. (1940). On a least squares adjustment of a sampled
|
|
184
|
+
frequency table when the expected marginal totals are known. *Annals of Mathematical
|
|
185
|
+
Statistics* 11(4), 427-444.
|
|
186
|
+
- Federal Highway Administration (1965). *Calibrating and Testing a Gravity Model for Any
|
|
187
|
+
Size Urban Area*. US Department of Commerce, Bureau of Public Roads.
|
|
188
|
+
- Fratar, T. J. (1954). Vehicular trip distribution by successive approximations.
|
|
189
|
+
*Traffic Quarterly* 8(1), 53-65.
|
|
190
|
+
- Furness, K. P. (1965). Time function iteration. *Traffic Engineering and Control* 7(7),
|
|
191
|
+
458-460.
|
|
192
|
+
- Hyman, G. M. (1969). The calibration of trip distribution models. *Environment and
|
|
193
|
+
Planning* 1(1), 105-112.
|
|
194
|
+
- Ortúzar, J. de D. & Willumsen, L. G. (2011). *Modelling Transport*, 4th ed. Wiley,
|
|
195
|
+
Chichester.
|
|
196
|
+
- Tanner, J. C. (1961). *Factors affecting the amount of travel*. Road Research Technical
|
|
197
|
+
Paper 51, HMSO, London.
|
|
198
|
+
- Wilson, A. G. (1970). *Entropy in Urban and Regional Modelling*. Pion, London.
|
|
199
|
+
- Wootton, H. J. & Pick, G. W. (1967). A model for trips generated by households.
|
|
200
|
+
*Journal of Transport Economics and Policy* 1(2), 137-153.
|
|
201
|
+
|
|
202
|
+
## Academic citation
|
|
203
|
+
|
|
204
|
+
```bibtex
|
|
205
|
+
@software{eminoglu_tripdist,
|
|
206
|
+
author = {Eminoğlu, Yusuf},
|
|
207
|
+
title = {tripdist},
|
|
208
|
+
year = {2026},
|
|
209
|
+
version = {0.1.0},
|
|
210
|
+
url = {https://gitlab.com/geophilo1/tripdist_sdk}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
MIT, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""tripdist quickstart on a small synthetic city (no input files needed).
|
|
2
|
+
|
|
3
|
+
Run: python examples/quickstart.py
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
import tripdist as td
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main() -> None:
|
|
14
|
+
# 1. Trip generation: a zonal regression of productions on households and cars
|
|
15
|
+
households = np.array([1200, 950, 600, 1500, 800, 400], dtype=float)
|
|
16
|
+
cars = np.array([700, 500, 450, 1000, 300, 350], dtype=float)
|
|
17
|
+
observed_productions = np.array([2400, 1800, 1350, 3050, 1350, 950], dtype=float)
|
|
18
|
+
model = td.fit_regression(
|
|
19
|
+
observed_productions, np.column_stack([households, cars]), ["households", "cars"]
|
|
20
|
+
)
|
|
21
|
+
print(f"Regression R2 = {model.r_squared:.3f}, coefficients {model.coefficients.round(3)}")
|
|
22
|
+
productions = model.predict(np.column_stack([households, cars]))
|
|
23
|
+
jobs = np.array([3000, 900, 700, 2500, 1500, 400], dtype=float)
|
|
24
|
+
productions, attractions = td.balance_pa(productions, jobs, method="productions")
|
|
25
|
+
|
|
26
|
+
# 2. A cost skim (minutes) with intrazonal costs from the nearest neighbour
|
|
27
|
+
xy = np.array([[0, 0], [4, 1], [8, 0], [2, 5], [6, 6], [10, 4]], dtype=float)
|
|
28
|
+
cost = 3.0 * np.hypot(*(xy[:, None, :] - xy[None, :, :]).transpose(2, 0, 1))
|
|
29
|
+
cost = td.intrazonal_costs(cost, factor=0.5)
|
|
30
|
+
|
|
31
|
+
# 3. Doubly constrained gravity model, calibrated to an observed mean trip time
|
|
32
|
+
calib = td.hyman(cost, productions, attractions, target_mean_cost=14.0)
|
|
33
|
+
print(
|
|
34
|
+
f"Hyman: beta = {calib.value:.4f} after {calib.iterations} runs, "
|
|
35
|
+
f"mean cost {calib.modelled_mean_cost:.2f} min"
|
|
36
|
+
)
|
|
37
|
+
trips = calib.model.matrix
|
|
38
|
+
print("Furness iterations:", calib.model.iterations)
|
|
39
|
+
print("Intrazonal share:", round(td.intrazonal_share(trips), 3))
|
|
40
|
+
|
|
41
|
+
# 4. Trip length frequency distribution
|
|
42
|
+
dist = td.tlfd(trips, cost, td.cost_bins(cost, 5.0))
|
|
43
|
+
for band in dist.records():
|
|
44
|
+
print(f" {band['lower']:4.0f}-{band['upper']:4.0f} min {band['share']:6.1%}")
|
|
45
|
+
|
|
46
|
+
# 5. Grow the matrix to a horizon year with the Furness method
|
|
47
|
+
horizon = td.furness_growth(
|
|
48
|
+
trips,
|
|
49
|
+
origin_totals=productions * 1.2,
|
|
50
|
+
dest_totals=attractions * 1.2,
|
|
51
|
+
)
|
|
52
|
+
print(f"Horizon total {horizon.total:.0f} trips, converged={horizon.converged}")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
main()
|