Skip to content

Commit

Permalink
Merge branch 'dev' of https://github.com/jdlph/PATH4GMNS into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
jdlph committed Aug 2, 2023
2 parents ac194d3 + 8af6bf3 commit 500dcea
Show file tree
Hide file tree
Showing 13 changed files with 77 additions and 63 deletions.
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Path4GMNS
[![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)
[![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)
[![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)

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,

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

## Installation
Path4GMNS has been published on [PyPI](https://pypi.org/project/path4gmns/0.9.3/), and can be installed using
Path4GMNS has been published on [PyPI](https://pypi.org/project/path4gmns/0.9.4/), and can be installed using
```
$ pip install path4gmns
```
If you need a specific version of Path4GMNS, say, 0.9.3,
If you need a specific version of Path4GMNS, say, 0.9.4,
```
$ pip install path4gmns==0.9.3
$ pip install path4gmns==0.9.4
```

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**.
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**.

### Dependency
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/)**.
Expand Down Expand Up @@ -93,6 +93,11 @@ DTALite uses arrays rather than STL containers to store columns. These arrays ar
38. Fix the bug on handling link capacity reduction in traffic assignment (v0.9.3)
39. Remove dependency on demand.csv for loading columns (v0.9.3)
40. Deprecate find_path_for_agents() (v0.9.3)
41. Remove beg_iteration and end_iteration from setting up a special event (v0.9.4)
42. Enhance DemandPeriod setup on time_period (v0.9.4)
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)
44. Remove memory_blocks and its implementations (which were intended for multiprocessing) (v0.9.4)
45. Bring back the postprocessing after UE in case users do not do column updating (i.e., column_update_num = 0) (v0.9.4)

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

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

## How to Cite

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

## References
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.
Expand Down
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
author = 'Dr. Peiheng Li and Dr. Xuesong (Simon) Zhou'

# The full version, including alpha/beta/rc tags
release = 'v0.9.3'
release = 'v0.9.4'


# -- General configuration ---------------------------------------------------
Expand Down
12 changes: 6 additions & 6 deletions docs/source/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
Installation
============

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

.. code-block:: bash
$ pip install path4gmns
If you need a specific version of Path4GMNS, say, 0.9.3,
If you need a specific version of Path4GMNS, say, 0.9.4,

.. code-block:: bash
$ pip install path4gmns==0.9.3
$ pip install path4gmns==0.9.4
Dependency
Expand Down Expand Up @@ -55,7 +55,7 @@ As CMAKE_BUILD_TYPE will be IGNORED for IDE (Integrated Development Environment)
# from the root directory of PATH4GMNS
$ python setup.py sdist bdist_wheel
$ cd dist
# or python -m pip install path4gmns-0.9.3.tar.gz
$ python -m pip instal pypath4gmns-0.9.3-py3-none-any.whl
# or python -m pip install path4gmns-0.9.4.tar.gz
$ python -m pip instal pypath4gmns-0.9.4-py3-none-any.whl
Here, 0.9.3 is the version number. Replace it with the one specified in setup.py.
Here, 0.9.4 is the version number. Replace it with the one specified in setup.py.
2 changes: 1 addition & 1 deletion path4gmns/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from .zonesyn import *


__version__ = '0.9.4a1'
__version__ = '0.9.4'


# print out the current version
Expand Down
41 changes: 23 additions & 18 deletions path4gmns/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
from collections import deque
from copy import deepcopy
from datetime import datetime
from math import ceil
from math import ceil, floor
from random import choice, randint, uniform

from .consts import MAX_LABEL_COST, SMALL_DIVISOR
from .consts import MAX_LABEL_COST, SMALL_DIVISOR, SECONDS_IN_MINUTE, SECONDS_IN_HOUR
from .path import find_path_for_agents, find_shortest_path, \
single_source_shortest_path, benchmark_apsp

Expand Down Expand Up @@ -252,9 +252,9 @@ def get_dp_id(self):
def get_od(self):
return self.o_zone_id, self.d_zone_id

def update_dep_interval(self, tt):
def update_dep_interval(self, intvl):
self.link_dep_interval[self.curr_link_pos] = (
self.link_arr_interval[self.curr_link_pos] + tt
self.link_arr_interval[self.curr_link_pos] + intvl
)

def get_curr_dep_interval(self):
Expand Down Expand Up @@ -1332,11 +1332,10 @@ def __init__(self):
self.network = None
self.spnetworks = []
self.accessnetwork = None
self.memory_blocks = 1
self.map_atstr_id = {}
self.map_dpstr_id = {}
self.map_name_atstr = {}
# number of seconds per simulation
# number of seconds per simulation interval
self.simu_rez = 6
# duration of simulation in minutes
self.simu_dur = 60
Expand Down Expand Up @@ -1529,17 +1528,14 @@ def setup_spnetwork(self):
for d in self.demands:
at = self.get_agent_type(d.get_agent_type_str())
dp = self.get_demand_period(d.get_period())
# it requires an ascending order of zone ids
# otherwise, a KeyError may be encountered, where else block is
# executed before the if block
if z - 1 < self.memory_blocks:
k = (at.get_id(), dp.get_id())
if k not in spvec.keys():
sp = SPNetwork(self.network, at, dp)
spvec[(at.get_id(), dp.get_id(), z-1)] = sp
spvec[k] = sp
sp.orig_zones.append(z)
self.spnetworks.append(sp)
else:
m = (z - 1) % self.memory_blocks
sp = spvec[(at.get_id(), dp.get_id(), m)]
sp = spvec[k]
sp.orig_zones.append(z)

def get_link(self, seq_no):
Expand Down Expand Up @@ -1674,7 +1670,7 @@ def initialize_simulation(self, loading_profile):
t += randint(0, self.simu_dur - 1)

# simulation interval
i = (t - self.simu_st) * 60 // self.simu_rez
i = self.cast_minute_to_interval(t - self.simu_st)
agent.link_arr_interval[-1] = i
agent.dep_time = t

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

link.calculate_td_vdf()
# link_capacity is for one hour, i.e., 60 minutes
c1 = link.link_capacity / (60 * self.simu_rez)
c2 = link.link_capacity // (60 * self.simu_rez)
# link_capacity is for one hour, i.e., 3600 s
c1 = link.link_capacity / SECONDS_IN_HOUR * self.simu_rez
c2 = floor(c1)
residual = c1 - c2

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

def get_simu_resolution(self):
Expand All @@ -1738,6 +1734,15 @@ def set_capacity_ratio(self, tau, link_id, r):
link = self.get_link(link_no)
link.set_capacity_ratio(tau, r)

def cast_interval_to_minute(self, i):
return floor(i * self.simu_rez / SECONDS_IN_MINUTE)

def cast_interval_to_minute_float(self, i):
return i * self.simu_rez / SECONDS_IN_MINUTE

def cast_minute_to_interval(self, m):
return floor(m * SECONDS_IN_MINUTE / self.simu_rez)


class UI:
""" an abstract class only with user interfaces """
Expand Down
6 changes: 4 additions & 2 deletions path4gmns/colgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,11 +294,13 @@ def perform_column_generation(column_gen_num, column_update_num, ui):
print(f'\nprocessing time of generating columns: {time()-st:.2f} s\n')

for i in range(column_update_num):
_update_column_gradient_cost_and_flow(column_pool, links, ats, i)
_update_link_and_column_volume(column_pool, links, i + 1, False)
_update_link_and_column_volume(column_pool, links, i, False)
_update_link_travel_time(links)
_update_column_gradient_cost_and_flow(column_pool, links, ats, i)

# postprocessing
_update_link_and_column_volume(column_pool, links, column_gen_num, False)
_update_link_travel_time(links)
_update_column_attributes(column_pool, links)


Expand Down
5 changes: 3 additions & 2 deletions path4gmns/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
# unit
MILE_TO_METER = 1609
MPH_TO_KPH = 1.609
# reserved for simulation
NUM_OF_SECS_PER_SIMU_INTERVAL = 6
# simulation
SECONDS_IN_MINUTE = 60
SECONDS_IN_HOUR = 3600
2 changes: 1 addition & 1 deletion path4gmns/dtaapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from time import sleep


__all__ = ['perform_network_assignment_DTALite', 'run_DTALite']
__all__ = ['perform_network_assignment_DTALite']


_os = platform.system()
Expand Down
19 changes: 11 additions & 8 deletions path4gmns/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,16 @@ def perform_simple_simulation(ui, loading_profile='uniform'):
"""
A = ui._base_assignment
A.initialize_simulation(loading_profile)
if not A.get_agents():
return

links = A.get_links()
nodes = A.get_nodes()

cum_arr = cum_dep = 0

# number of simulation intervals in one minute (60s)
num = 60 // A.get_simu_resolution()
num = A.cast_minute_to_interval(1)
for i in range(A.get_total_simu_intervals()):
if i % num == 0:
print(f'simu time = {i/num} min, CA = {cum_arr}, CD = {cum_dep}')
Expand Down Expand Up @@ -79,8 +81,8 @@ def perform_simple_simulation(ui, loading_profile='uniform'):
a_no = link.entr_queue.popleft()
agent = A.get_agent(a_no)
link.exit_queue.append(a_no)
tt = ceil(link.get_period_travel_time(0) * num)
agent.update_dep_interval(tt)
intvl = A.cast_minute_to_interval(link.get_period_fftt(0))
agent.update_dep_interval(intvl)

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

if agent.reached_last_link():
link.cum_dep[i] += 1
# link.cum_dep[i] += 1
cum_dep += 1
else:
link_no = agent.get_next_link_no()
Expand All @@ -107,10 +109,11 @@ def perform_simple_simulation(ui, loading_profile='uniform'):
# set up arrival time for the next link, i.e., next_link
agent.set_arr_interval(i, 1)

actual_tt = i - agent.get_arr_interval()
waiting_t = actual_tt - link.get_period_travel_time(0)
minute = agent.get_arr_interval() // A.get_simu_resolution()
link.update_waiting_time(minute, waiting_t)
travel_intvl = i - agent.get_arr_interval()
waiting_intvl = travel_intvl - A.cast_minute_to_interval(link.get_period_fftt(0))
# arrival time in minutes
arr_minute = A.cast_interval_to_minute(agent.get_arr_interval())
link.update_waiting_time(arr_minute, waiting_intvl)
# link.cum_dep[i] += 1
# next_link.cum_arr[i] += 1

Expand Down
21 changes: 9 additions & 12 deletions path4gmns/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1433,19 +1433,17 @@ def output_agent_trajectory(ui, output_dir='.'):
'travel_time_in_min',
'PCE',
'distance',
'number_of_nodes',
'node_sequence',
'geometry',
'time_sequence',
'time_sequence_hhmmss']

writer.writerow(line)

base = ui._base_assignment
nodes = base.get_nodes()
agents = base.get_agents()
r = base.get_simu_resolution()
st = base.get_simu_start_time()
A = ui._base_assignment
nodes = A.get_nodes()
agents = A.get_agents()
st = A.get_simu_start_time()

pre_dt = -1
pre_od = -1, -1
Expand All @@ -1460,8 +1458,8 @@ def output_agent_trajectory(ui, output_dir='.'):
pre_dt = a.get_dep_time()
pre_od = a.get_od()

at = a.link_arr_interval[-1] * r / 60 + st
dt = a.link_dep_interval[-1] * r / 60 + st
at = A.cast_interval_to_minute_float(a.link_arr_interval[-1]) + st
dt = A.cast_interval_to_minute_float(a.link_dep_interval[-1]) + st
time_seq1 = [at, dt]
time_seq2 = [_get_time_stamp(at), _get_time_stamp(dt)]

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

dt_ = k * r / 60 + st
dt_ = A.cast_interval_to_minute_float(k) + st
time_seq1.append(dt_)
time_seq2.append(_get_time_stamp(dt_))

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

node_path_str = base.get_agent_node_path(a.get_id(), True)
node_path_str = A.get_agent_node_path(a.get_id(), True)
geometry = ', '.join(
nodes[x].get_coordinate() for x in reversed(a.get_node_path())
)
Expand All @@ -1504,7 +1502,6 @@ def output_agent_trajectory(ui, output_dir='.'):
tt,
a.PCE_factor,
a.get_path_cost(),
len(a.get_node_path()),
node_path_str,
geometry,
time_seq1_str,
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name="path4gmns",
version="0.9.4a1",
version="0.9.4",
author="Dr. Xuesong Zhou, Dr. Peiheng Li",
author_email="[email protected], [email protected]",
description="An open-source, cross-platform, lightweight, and fast Python\
Expand Down
10 changes: 5 additions & 5 deletions tests/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,13 +224,13 @@ def test_loading_synthesized_zones_demand():
def test_simulation():
network = pg.read_network(load_demand=True)

column_gen_num = 10
column_update_num = 10
pg.perform_column_generation(column_gen_num, column_update_num, network)
column_gen_num = 20
column_update_num = 20
# pg.perform_column_generation(column_gen_num, column_update_num, network)

# you can bypass the above perform_column_generation() and call
# load_columns(network) if you have agent.csv
# pg.load_columns(network)
pg.load_columns(network)
pg.perform_simple_simulation(network, 'uniform')
print('complete simple simulation.\n')

Expand Down Expand Up @@ -285,4 +285,4 @@ def demo_mode(mode):

if __name__=="__main__":

demo_mode(4)
demo_mode(5)
Loading

0 comments on commit 500dcea

Please sign in to comment.