Skip to content

Commit 500dcea

Browse files
committed
Merge branch 'dev' of https://github.com/jdlph/PATH4GMNS into dev
2 parents ac194d3 + 8af6bf3 commit 500dcea

File tree

13 files changed

+77
-63
lines changed

13 files changed

+77
-63
lines changed

README.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Path4GMNS
22
[![platform](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-red)](https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-red)
3-
[![Downloads](https://pepy.tech/badge/path4gmns)](https://pepy.tech/project/path4gmns) [![GitHub release](https://img.shields.io/badge/release-v0.9.3-brightgreen)](https://img.shields.io/badge/release-v0.8.2-brightgreen) ![Read the Docs](https://img.shields.io/readthedocs/path4gmns)
3+
[![Downloads](https://pepy.tech/badge/path4gmns)](https://pepy.tech/project/path4gmns) [![GitHub release](https://img.shields.io/badge/release-v0.9.4-brightgreen)](https://img.shields.io/badge/release-v0.8.2-brightgreen) ![Read the Docs](https://img.shields.io/readthedocs/path4gmns)
44

55
Path4GMNS is an open-source, cross-platform, lightweight, and fast Python path engine for networks encoded in [GMNS](https://github.com/zephyr-data-specs/GMNS). Besides finding static shortest paths for simple analyses, its main functionality is to provide an efficient and flexible framework for column-based (path-based) modeling and applications in transportation (e.g., activity-based demand modeling). Path4GMNS supports, in short,
66

@@ -19,16 +19,16 @@ Path4GMNS also serves as an API to the C++-based [DTALite](https://github.com/jd
1919
![Architecture](/docs/source/imgs/architecture.png)
2020

2121
## Installation
22-
Path4GMNS has been published on [PyPI](https://pypi.org/project/path4gmns/0.9.3/), and can be installed using
22+
Path4GMNS has been published on [PyPI](https://pypi.org/project/path4gmns/0.9.4/), and can be installed using
2323
```
2424
$ pip install path4gmns
2525
```
26-
If you need a specific version of Path4GMNS, say, 0.9.3,
26+
If you need a specific version of Path4GMNS, say, 0.9.4,
2727
```
28-
$ pip install path4gmns==0.9.3
28+
$ pip install path4gmns==0.9.4
2929
```
3030

31-
v0.9.3 fixes the bug on handling link capacity reduction in traffic assignment and removes dependency on read_demand() for loading columns. Please **update to or install the latest version** and **discard all old versions**.
31+
v0.9.4 fixes crucial bugs in the simulation module. Any versions prior to v0.9.4 will generate INCORRECT simulation results. Please **update to or install the latest version** and **discard all old versions**.
3232

3333
### Dependency
3434
The Python modules are written in **Python 3.x**, which is the minimum requirement to explore the most of Path4GMNS. Some of its functions require further run-time support, which we will go through along with the corresponding **[Use Cases](https://path4gmns.readthedocs.io/en/latest/)**.
@@ -93,6 +93,11 @@ DTALite uses arrays rather than STL containers to store columns. These arrays ar
9393
38. Fix the bug on handling link capacity reduction in traffic assignment (v0.9.3)
9494
39. Remove dependency on demand.csv for loading columns (v0.9.3)
9595
40. Deprecate find_path_for_agents() (v0.9.3)
96+
41. Remove beg_iteration and end_iteration from setting up a special event (v0.9.4)
97+
42. Enhance DemandPeriod setup on time_period (v0.9.4)
98+
43. fix multiple bugs related to simulation including calculation of agent arrival time and agent waiting time, link traverse time, and link outflow cap (v0.9.4)
99+
44. Remove memory_blocks and its implementations (which were intended for multiprocessing) (v0.9.4)
100+
45. Bring back the postprocessing after UE in case users do not do column updating (i.e., column_update_num = 0) (v0.9.4)
96101

97102
Detailed update information can be found in [Releases](https://github.com/jdlph/Path4GMNS/releases).
98103

@@ -106,7 +111,7 @@ You are encouraged to join our [Discord Channel](https://discord.gg/JGFMta7kxZ)
106111

107112
## How to Cite
108113

109-
Li, P. and Zhou, X. (2023, April 8). *Path4GMNS*. Retrieved from https://github.com/jdlph/Path4GMNS
114+
Li, P. and Zhou, X. (2023, August 1). *Path4GMNS*. Retrieved from https://github.com/jdlph/Path4GMNS
110115

111116
## References
112117
Lu, C. C., Mahmassani, H. S., Zhou, X. (2009). Equivalent gap function-based reformulation and solution algorithm for the dynamic user equilibrium problem. Transportation Research Part B: Methodological, 43, 345-364.

docs/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
author = 'Dr. Peiheng Li and Dr. Xuesong (Simon) Zhou'
2727

2828
# The full version, including alpha/beta/rc tags
29-
release = 'v0.9.3'
29+
release = 'v0.9.4'
3030

3131

3232
# -- General configuration ---------------------------------------------------

docs/source/installation.rst

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@
22
Installation
33
============
44

5-
Path4GMNS has been published on `PyPI <https://pypi.org/project/path4gmns/0.9.3/>`_, and can be installed using
5+
Path4GMNS has been published on `PyPI <https://pypi.org/project/path4gmns/0.9.4/>`_, and can be installed using
66

77
.. code-block:: bash
88
99
$ pip install path4gmns
1010
11-
If you need a specific version of Path4GMNS, say, 0.9.3,
11+
If you need a specific version of Path4GMNS, say, 0.9.4,
1212

1313
.. code-block:: bash
1414
15-
$ pip install path4gmns==0.9.3
15+
$ pip install path4gmns==0.9.4
1616
1717
1818
Dependency
@@ -55,7 +55,7 @@ As CMAKE_BUILD_TYPE will be IGNORED for IDE (Integrated Development Environment)
5555
# from the root directory of PATH4GMNS
5656
$ python setup.py sdist bdist_wheel
5757
$ cd dist
58-
# or python -m pip install path4gmns-0.9.3.tar.gz
59-
$ python -m pip instal pypath4gmns-0.9.3-py3-none-any.whl
58+
# or python -m pip install path4gmns-0.9.4.tar.gz
59+
$ python -m pip instal pypath4gmns-0.9.4-py3-none-any.whl
6060
61-
Here, 0.9.3 is the version number. Replace it with the one specified in setup.py.
61+
Here, 0.9.4 is the version number. Replace it with the one specified in setup.py.

path4gmns/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from .zonesyn import *
77

88

9-
__version__ = '0.9.4a1'
9+
__version__ = '0.9.4'
1010

1111

1212
# print out the current version

path4gmns/classes.py

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
from collections import deque
33
from copy import deepcopy
44
from datetime import datetime
5-
from math import ceil
5+
from math import ceil, floor
66
from random import choice, randint, uniform
77

8-
from .consts import MAX_LABEL_COST, SMALL_DIVISOR
8+
from .consts import MAX_LABEL_COST, SMALL_DIVISOR, SECONDS_IN_MINUTE, SECONDS_IN_HOUR
99
from .path import find_path_for_agents, find_shortest_path, \
1010
single_source_shortest_path, benchmark_apsp
1111

@@ -252,9 +252,9 @@ def get_dp_id(self):
252252
def get_od(self):
253253
return self.o_zone_id, self.d_zone_id
254254

255-
def update_dep_interval(self, tt):
255+
def update_dep_interval(self, intvl):
256256
self.link_dep_interval[self.curr_link_pos] = (
257-
self.link_arr_interval[self.curr_link_pos] + tt
257+
self.link_arr_interval[self.curr_link_pos] + intvl
258258
)
259259

260260
def get_curr_dep_interval(self):
@@ -1332,11 +1332,10 @@ def __init__(self):
13321332
self.network = None
13331333
self.spnetworks = []
13341334
self.accessnetwork = None
1335-
self.memory_blocks = 1
13361335
self.map_atstr_id = {}
13371336
self.map_dpstr_id = {}
13381337
self.map_name_atstr = {}
1339-
# number of seconds per simulation
1338+
# number of seconds per simulation interval
13401339
self.simu_rez = 6
13411340
# duration of simulation in minutes
13421341
self.simu_dur = 60
@@ -1529,17 +1528,14 @@ def setup_spnetwork(self):
15291528
for d in self.demands:
15301529
at = self.get_agent_type(d.get_agent_type_str())
15311530
dp = self.get_demand_period(d.get_period())
1532-
# it requires an ascending order of zone ids
1533-
# otherwise, a KeyError may be encountered, where else block is
1534-
# executed before the if block
1535-
if z - 1 < self.memory_blocks:
1531+
k = (at.get_id(), dp.get_id())
1532+
if k not in spvec.keys():
15361533
sp = SPNetwork(self.network, at, dp)
1537-
spvec[(at.get_id(), dp.get_id(), z-1)] = sp
1534+
spvec[k] = sp
15381535
sp.orig_zones.append(z)
15391536
self.spnetworks.append(sp)
15401537
else:
1541-
m = (z - 1) % self.memory_blocks
1542-
sp = spvec[(at.get_id(), dp.get_id(), m)]
1538+
sp = spvec[k]
15431539
sp.orig_zones.append(z)
15441540

15451541
def get_link(self, seq_no):
@@ -1674,7 +1670,7 @@ def initialize_simulation(self, loading_profile):
16741670
t += randint(0, self.simu_dur - 1)
16751671

16761672
# simulation interval
1677-
i = (t - self.simu_st) * 60 // self.simu_rez
1673+
i = self.cast_minute_to_interval(t - self.simu_st)
16781674
agent.link_arr_interval[-1] = i
16791675
agent.dep_time = t
16801676

@@ -1694,10 +1690,9 @@ def initialize_simulation(self, loading_profile):
16941690
if link.length == 0:
16951691
continue
16961692

1697-
link.calculate_td_vdf()
1698-
# link_capacity is for one hour, i.e., 60 minutes
1699-
c1 = link.link_capacity / (60 * self.simu_rez)
1700-
c2 = link.link_capacity // (60 * self.simu_rez)
1693+
# link_capacity is for one hour, i.e., 3600 s
1694+
c1 = link.link_capacity / SECONDS_IN_HOUR * self.simu_rez
1695+
c2 = floor(c1)
17011696
residual = c1 - c2
17021697

17031698
r = uniform(0, 1)
@@ -1712,6 +1707,7 @@ def initialize_simulation(self, loading_profile):
17121707
link.outflow_cap = [cap] * n1
17131708
link.cum_arr = [0] * n1
17141709
link.cum_dep = [0] * n1
1710+
# waiting time in terms of simulation interval
17151711
link.waiting_time = [0] * n2
17161712

17171713
def get_simu_resolution(self):
@@ -1738,6 +1734,15 @@ def set_capacity_ratio(self, tau, link_id, r):
17381734
link = self.get_link(link_no)
17391735
link.set_capacity_ratio(tau, r)
17401736

1737+
def cast_interval_to_minute(self, i):
1738+
return floor(i * self.simu_rez / SECONDS_IN_MINUTE)
1739+
1740+
def cast_interval_to_minute_float(self, i):
1741+
return i * self.simu_rez / SECONDS_IN_MINUTE
1742+
1743+
def cast_minute_to_interval(self, m):
1744+
return floor(m * SECONDS_IN_MINUTE / self.simu_rez)
1745+
17411746

17421747
class UI:
17431748
""" an abstract class only with user interfaces """

path4gmns/colgen.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,11 +294,13 @@ def perform_column_generation(column_gen_num, column_update_num, ui):
294294
print(f'\nprocessing time of generating columns: {time()-st:.2f} s\n')
295295

296296
for i in range(column_update_num):
297-
_update_column_gradient_cost_and_flow(column_pool, links, ats, i)
298-
_update_link_and_column_volume(column_pool, links, i + 1, False)
297+
_update_link_and_column_volume(column_pool, links, i, False)
299298
_update_link_travel_time(links)
299+
_update_column_gradient_cost_and_flow(column_pool, links, ats, i)
300300

301301
# postprocessing
302+
_update_link_and_column_volume(column_pool, links, column_gen_num, False)
303+
_update_link_travel_time(links)
302304
_update_column_attributes(column_pool, links)
303305

304306

path4gmns/consts.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@
1313
# unit
1414
MILE_TO_METER = 1609
1515
MPH_TO_KPH = 1.609
16-
# reserved for simulation
17-
NUM_OF_SECS_PER_SIMU_INTERVAL = 6
16+
# simulation
17+
SECONDS_IN_MINUTE = 60
18+
SECONDS_IN_HOUR = 3600

path4gmns/dtaapi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from time import sleep
66

77

8-
__all__ = ['perform_network_assignment_DTALite', 'run_DTALite']
8+
__all__ = ['perform_network_assignment_DTALite']
99

1010

1111
_os = platform.system()

path4gmns/simulation.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,16 @@ def perform_simple_simulation(ui, loading_profile='uniform'):
4242
"""
4343
A = ui._base_assignment
4444
A.initialize_simulation(loading_profile)
45+
if not A.get_agents():
46+
return
4547

4648
links = A.get_links()
4749
nodes = A.get_nodes()
4850

4951
cum_arr = cum_dep = 0
5052

5153
# number of simulation intervals in one minute (60s)
52-
num = 60 // A.get_simu_resolution()
54+
num = A.cast_minute_to_interval(1)
5355
for i in range(A.get_total_simu_intervals()):
5456
if i % num == 0:
5557
print(f'simu time = {i/num} min, CA = {cum_arr}, CD = {cum_dep}')
@@ -79,8 +81,8 @@ def perform_simple_simulation(ui, loading_profile='uniform'):
7981
a_no = link.entr_queue.popleft()
8082
agent = A.get_agent(a_no)
8183
link.exit_queue.append(a_no)
82-
tt = ceil(link.get_period_travel_time(0) * num)
83-
agent.update_dep_interval(tt)
84+
intvl = A.cast_minute_to_interval(link.get_period_fftt(0))
85+
agent.update_dep_interval(intvl)
8486

8587
for node in nodes:
8688
m = node.get_incoming_link_num()
@@ -97,7 +99,7 @@ def perform_simple_simulation(ui, loading_profile='uniform'):
9799
break
98100

99101
if agent.reached_last_link():
100-
link.cum_dep[i] += 1
102+
# link.cum_dep[i] += 1
101103
cum_dep += 1
102104
else:
103105
link_no = agent.get_next_link_no()
@@ -107,10 +109,11 @@ def perform_simple_simulation(ui, loading_profile='uniform'):
107109
# set up arrival time for the next link, i.e., next_link
108110
agent.set_arr_interval(i, 1)
109111

110-
actual_tt = i - agent.get_arr_interval()
111-
waiting_t = actual_tt - link.get_period_travel_time(0)
112-
minute = agent.get_arr_interval() // A.get_simu_resolution()
113-
link.update_waiting_time(minute, waiting_t)
112+
travel_intvl = i - agent.get_arr_interval()
113+
waiting_intvl = travel_intvl - A.cast_minute_to_interval(link.get_period_fftt(0))
114+
# arrival time in minutes
115+
arr_minute = A.cast_interval_to_minute(agent.get_arr_interval())
116+
link.update_waiting_time(arr_minute, waiting_intvl)
114117
# link.cum_dep[i] += 1
115118
# next_link.cum_arr[i] += 1
116119

path4gmns/utils.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1433,19 +1433,17 @@ def output_agent_trajectory(ui, output_dir='.'):
14331433
'travel_time_in_min',
14341434
'PCE',
14351435
'distance',
1436-
'number_of_nodes',
14371436
'node_sequence',
14381437
'geometry',
14391438
'time_sequence',
14401439
'time_sequence_hhmmss']
14411440

14421441
writer.writerow(line)
14431442

1444-
base = ui._base_assignment
1445-
nodes = base.get_nodes()
1446-
agents = base.get_agents()
1447-
r = base.get_simu_resolution()
1448-
st = base.get_simu_start_time()
1443+
A = ui._base_assignment
1444+
nodes = A.get_nodes()
1445+
agents = A.get_agents()
1446+
st = A.get_simu_start_time()
14491447

14501448
pre_dt = -1
14511449
pre_od = -1, -1
@@ -1460,8 +1458,8 @@ def output_agent_trajectory(ui, output_dir='.'):
14601458
pre_dt = a.get_dep_time()
14611459
pre_od = a.get_od()
14621460

1463-
at = a.link_arr_interval[-1] * r / 60 + st
1464-
dt = a.link_dep_interval[-1] * r / 60 + st
1461+
at = A.cast_interval_to_minute_float(a.link_arr_interval[-1]) + st
1462+
dt = A.cast_interval_to_minute_float(a.link_dep_interval[-1]) + st
14651463
time_seq1 = [at, dt]
14661464
time_seq2 = [_get_time_stamp(at), _get_time_stamp(dt)]
14671465

@@ -1472,7 +1470,7 @@ def output_agent_trajectory(ui, output_dir='.'):
14721470
if k < 0:
14731471
break
14741472

1475-
dt_ = k * r / 60 + st
1473+
dt_ = A.cast_interval_to_minute_float(k) + st
14761474
time_seq1.append(dt_)
14771475
time_seq2.append(_get_time_stamp(dt_))
14781476

@@ -1487,9 +1485,9 @@ def output_agent_trajectory(ui, output_dir='.'):
14871485
at_ = time_seq1[-1]
14881486
# the original implementation using arrival time to the last link
14891487
# to calculate trip time does not make sense
1490-
tt = at_ - a.get_dep_time() + st
1488+
tt = at_ - a.get_dep_time()
14911489

1492-
node_path_str = base.get_agent_node_path(a.get_id(), True)
1490+
node_path_str = A.get_agent_node_path(a.get_id(), True)
14931491
geometry = ', '.join(
14941492
nodes[x].get_coordinate() for x in reversed(a.get_node_path())
14951493
)
@@ -1504,7 +1502,6 @@ def output_agent_trajectory(ui, output_dir='.'):
15041502
tt,
15051503
a.PCE_factor,
15061504
a.get_path_cost(),
1507-
len(a.get_node_path()),
15081505
node_path_str,
15091506
geometry,
15101507
time_seq1_str,

0 commit comments

Comments
 (0)