├── .gitignore ├── .readthedocs.yaml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── data ├── ASU │ ├── demand.csv │ ├── link.csv │ ├── node.csv │ ├── settings.csv │ └── settings.yml ├── Braess_Paradox │ ├── demand.csv │ ├── link.csv │ ├── node.csv │ ├── settings.csv │ └── settings.yml ├── Chicago_Sketch │ ├── demand.csv │ ├── link.csv │ ├── measurement.csv │ ├── node.csv │ ├── settings.csv │ └── settings.yml ├── Lima_Network │ ├── demand.csv │ ├── link.csv │ ├── node.csv │ ├── settings.csv │ └── settings.yml ├── Sioux_Falls │ ├── demand.csv │ ├── link.csv │ ├── node.csv │ ├── settings.csv │ └── settings.yml └── Two_Corridor │ ├── demand.csv │ ├── link.csv │ ├── node.csv │ ├── settings.csv │ └── settings.yml ├── docs ├── Makefile ├── make.bat └── source │ ├── api.rst │ ├── conf.py │ ├── imgs │ └── architecture.png │ ├── implnotes.md │ ├── index.rst │ ├── installation.rst │ ├── requirements.in │ ├── requirements.txt │ ├── updates.md │ └── usecases.md ├── engine ├── CMakeLists.txt ├── path_engine.cpp └── path_engine.h ├── path4gmns ├── __init__.py ├── accessibility.py ├── bin │ ├── DTALite.dll │ ├── DTALite.so │ ├── DTALiteMM.dll │ ├── DTALiteMM.so │ ├── DTALiteMM_arm.dylib │ ├── DTALiteMM_x86.dylib │ ├── DTALite_arm.dylib │ ├── DTALite_x86.dylib │ ├── path_engine.dll │ ├── path_engine.so │ ├── path_engine_arm.dylib │ └── path_engine_x86.dylib ├── classes.py ├── colgen.py ├── consts.py ├── dtaapi.py ├── io.py ├── odme.py ├── path.py ├── simulation.py ├── utils.py └── zonesyn.py ├── setup.py ├── tests ├── __init__.py ├── conftest.py ├── test_a11y.py ├── test_dtalite.py ├── test_odme.py ├── test_sim.py ├── test_sp.py ├── test_ue.py └── test_utils.py └── tutorial └── tutorial.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | *egg-info/ 4 | .vscode/ 5 | .ipynb_checkpoints 6 | __pycache__/ 7 | engine_cpp/build/ 8 | tests/data/ 9 | .DS_Store 10 | *route_assignment.csv 11 | *link_performance.csv 12 | *agent.csv 13 | *agent_paths.csv 14 | *demand.csv 15 | *equity*.csv 16 | *zone.csv 17 | *syn_demand.csv 18 | *trajectory.csv 19 | *accessibility.csv 20 | *final_summary.csv 21 | **log* 22 | *.coverage* 23 | *.code-workspace -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: "ubuntu-20.04" 5 | tools: 6 | python: "3.7" 7 | 8 | # Build from the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: docs/source/conf.py 11 | 12 | # Explicitly set the version of Python and its requirements 13 | python: 14 | install: 15 | - requirements: docs/source/requirements.txt -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(path4gmns) 3 | 4 | set(VER "" CACHE STRING "specific version number") 5 | set(INIT_FILE "${CMAKE_SOURCE_DIR}/${PROJECT_NAME}/__init__.py") 6 | 7 | if (VER STREQUAL "") 8 | set(PIP_INSTALL python -m pip install ${PROJECT_NAME}) 9 | else() 10 | set(PIP_INSTALL python -m pip install ${PROJECT_NAME}==${VER}) 11 | endif() 12 | 13 | function(get_version OUTPUT_VAR) 14 | file(READ "${INIT_FILE}" CONTENT) 15 | string(REGEX MATCH "__version__ = ['\"]([^'\"]*)['\"]" _ ${CONTENT}) 16 | set(${OUTPUT_VAR} ${CMAKE_MATCH_1} PARENT_SCOPE) 17 | endfunction() 18 | 19 | get_version(LOCAL_VER) 20 | 21 | add_custom_target(build 22 | COMMAND ${CMAKE_COMMAND} -E echo "-- building ${PROJECT_NAME}-${LOCAL_VER}..." 23 | COMMAND ${CMAKE_COMMAND} -E env python setup.py sdist bdist_wheel 24 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 25 | ) 26 | 27 | add_custom_target(dev-install ALL 28 | COMMAND ${CMAKE_COMMAND} -E echo "-- development install: ${PROJECT_NAME}-${LOCAL_VER}..." 29 | COMMAND ${CMAKE_COMMAND} -E env python -m pip install -e ${CMAKE_SOURCE_DIR} 30 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 31 | ) 32 | 33 | add_custom_target(publish 34 | COMMAND ${CMAKE_COMMAND} -E echo "-- publish the package on PyPI: ${PROJECT_NAME}-${LOCAL_VER}..." 35 | COMMAND ${CMAKE_COMMAND} -E env python -m twine upload --repository pypi ${CMAKE_BINARY_DIR}/${PROJECT_NAME}-${LOCAL_VER}* 36 | DEPENDS build 37 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 38 | ) 39 | 40 | add_custom_target(pypi-install 41 | COMMAND ${CMAKE_COMMAND} -E echo "-- PyPI install: ${PROJECT_NAME}..." 42 | COMMAND ${CMAKE_COMMAND} -E env python -m pip uninstall -y ${PROJECT_NAME} 43 | COMMAND ${CMAKE_COMMAND} -E env ${PIP_INSTALL} 44 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 45 | ) 46 | 47 | add_custom_target(reg-install 48 | COMMAND ${CMAKE_COMMAND} -E echo "-- regular install: ${PROJECT_NAME}-${LOCAL_VER}..." 49 | COMMAND ${CMAKE_COMMAND} -E env python -m pip uninstall -y ${PROJECT_NAME} 50 | COMMAND ${CMAKE_COMMAND} -E env python -m pip install ${CMAKE_SOURCE_DIR} 51 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 52 | ) 53 | 54 | add_custom_target(test 55 | COMMAND ${CMAKE_COMMAND} -E echo "-- conduct unit test: ${PROJECT_NAME}-${LOCAL_VER}..." 56 | COMMAND ${CMAKE_COMMAND} -E env python -m pytest 57 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 58 | ) 59 | 60 | add_custom_target(test-coverage 61 | COMMAND ${CMAKE_COMMAND} -E echo "-- conduct unit test with coverage: ${PROJECT_NAME}-${LOCAL_VER}..." 62 | COMMAND ${CMAKE_COMMAND} -E env python -m pytest --cov=${PROJECT_NAME} 63 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 64 | ) 65 | 66 | add_custom_target(upgrade 67 | COMMAND ${CMAKE_COMMAND} -E echo "-- upgrade ${PROJECT_NAME} to the lastest from PyPI..." 68 | COMMAND ${CMAKE_COMMAND} -E env python -m pip install --upgrade ${PROJECT_NAME} 69 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 70 | ) 71 | 72 | add_custom_target(clean-cache 73 | COMMAND ${CMAKE_COMMAND} -E echo "-- cleaning up CMake temporary dir and files..." 74 | COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_BINARY_DIR}/CMakeFiles" 75 | COMMAND ${CMAKE_COMMAND} -E remove "${CMAKE_BINARY_DIR}/CMakeCache.txt" 76 | COMMAND ${CMAKE_COMMAND} -E remove "${CMAKE_BINARY_DIR}/cmake_install.cmake" 77 | COMMAND ${CMAKE_COMMAND} -E remove "${CMAKE_BINARY_DIR}/MakeFile" 78 | COMMAND ${CMAKE_COMMAND} -E echo "-- clean complete." 79 | ) 80 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Path4GMNS 2 | [![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://static.pepy.tech/badge/path4gmns)](https://pepy.tech/project/path4gmns) [![GitHub release](https://img.shields.io/badge/release-v0.9.10-brightgreen)](https://img.shields.io/badge/release-v0.8.2-brightgreen) ![Read the Docs](https://img.shields.io/readthedocs/path4gmns) 4 | [![](https://dcbadge.vercel.app/api/server/JGFMta7kxZ?style=flast)](https://discord.gg/JGFMta7kxZ) 5 | 6 | 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, 7 | 8 | 1. finding (static) shortest path between two nodes, 9 | 2. performing path-based User-Equilibrium (UE) traffic assignment, 10 | 3. conducting dynamic traffic assignment (DTA) after UE. 11 | 4. evaluating multimodal accessibility and equity, 12 | 5. making the Origin-Destination (OD) demand matrix estimation (ODME), 13 | 6. synthesizing zones and Origin-Destination (OD) demand. 14 | 15 | Path4GMNS also serves as an API to the C++-based [DTALite](https://github.com/jdlph/DTALite) to conduct various multimodal traffic assignments including, 16 | * Link-based UE, 17 | * Path-based UE, 18 | * UE + DTA, 19 | * ODME. 20 | 21 | ![Architecture](/docs/source/imgs/architecture.png) 22 | 23 | ## Quick Start 24 | 25 | 1. **[Tutorial](https://github.com/jdlph/Path4GMNS/blob/master/tutorial/tutorial.ipynb)** written in Jupyter notebook with step-by-step demonstration. 26 | 2. **[Documentation](https://path4gmns.readthedocs.io/en/stable/)** on Installation, Use Cases, Public API, and more. 27 | 3. **[TransOMS](https://github.com/jdlph/TransOMS)** on the C++ equivalent. 28 | 29 | We highly recommend that you go through the above [Tutorial](https://github.com/jdlph/Path4GMNS/blob/master/tutorial/tutorial.ipynb), no matter you are one of the existing users or new to Path4GMNS. 30 | 31 | ## Installation 32 | Path4GMNS has been published on [PyPI](https://pypi.org/project/path4gmns/0.9.10/), and can be installed using 33 | ``` 34 | $ pip install path4gmns 35 | ``` 36 | 37 | > [!IMPORTANT] 38 | > v0.9.10 comes with bug fix on [find_shortest_path()](https://github.com/jdlph/Path4GMNS/issues/58), new functionality on obtaining the shortest path tree, and performance improvement on the UE module. Please **discard all old versions**. 39 | 40 | > [!WARNING] 41 | > find_shortest_path() computes the shortest path per travel time rather than distance for v0.9.9.post1 and any earlier versions. See [Issue #58](https://github.com/jdlph/Path4GMNS/issues/58) for details. v0.9.10 offers the correct implementation with the flexibility to switch between time and distance. 42 | 43 | > [!WARNING] 44 | > Invoking find_shortest_path() and find_ue() in the same code snippet will lead to [OSError: exception: access violation reading ...](https://github.com/jdlph/Path4GMNS/issues/51#issue-2601430024) for v0.9.9 and any version before. 45 | 46 | > [!CAUTION] 47 | > Any version prior to v0.9.4 will generate INCORRECT simulation results. 48 | 49 | > [!CAUTION] 50 | > Calling DTALite and synthesizing zones and OD demand are not functioning for [v0.9.5 and v0.9.6](https://github.com/jdlph/Path4GMNS/issues/41). 51 | 52 | > [!CAUTION] 53 | > Zone and demand synthesis is PROBLEMATIC for any version before v0.9.9. 54 | 55 | > [!NOTE] 56 | > ODME is available with v0.9.9 and higher. 57 | 58 | ### Dependency 59 | 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/stable/)**. 60 | 61 | ## How to Cite 62 | 63 | Li, P. and Zhou, X. (2025, March 16). *Path4GMNS*. Retrieved from https://github.com/jdlph/Path4GMNS 64 | 65 | ## Please Contribute 66 | 67 | Any contributions are welcomed including advise new applications of Path4GMNS, enhance documentation and [docstrings](https://docs.python-guide.org/writing/documentation/#writing-docstrings) in the source code, refactor and/or optimize the source code, report and/or resolve potential issues/bugs, suggest and/or add new functionalities, etc. 68 | 69 | Path4GMNS has a very simple workflow setup, i.e., **master for release (on both GitHub and PyPI)** and **dev for development**. If you would like to work directly on the source code (and probably the documentation), please make sure that **the destination branch of your pull request is dev**, i.e., all potential changes/updates shall go to the dev branch before merging into master for release. 70 | 71 | You are encouraged to join our **[Discord Channel](https://discord.gg/JGFMta7kxZ)** for the latest update and more discussions. 72 | 73 | ## References 74 | Lu, C. C., Mahmassani, H. S., Zhou, X. (2009). [Equivalent gap function-based reformulation and solution algorithm for the dynamic user equilibrium problem](https://www.sciencedirect.com/science/article/abs/pii/S0191261508000829). Transportation Research Part B: Methodological, 43, 345-364. 75 | 76 | Jayakrishnan, R., Tsai, W. K., Prashker, J. N., Rajadyaksha, S. (1994). [A Faster Path-Based Algorithm for Traffic Assignment](https://escholarship.org/uc/item/2hf4541x) (Working Paper UCTC No. 191). The University of California Transportation Center. 77 | 78 | Bertsekas, D., Gafni, E. (1983). [Projected Newton methods and optimization of multicommodity flows](https://web.mit.edu/dimitrib/www/Gafni_Newton.pdf). IEEE Transactions on Automatic Control, 28(12), 1090–1096. -------------------------------------------------------------------------------- /data/ASU/settings.csv: -------------------------------------------------------------------------------- 1 | [assignment],,assignment_mode,number_of_iterations,column_updating_iterations,signal_updating_iterations,signal_updating_output,remarks 2 | ,,ue,40,40,-1,0,"assignment_mode can be ue, dta or odme" 3 | ,,,,,,, 4 | [agent_type],agent_type,name,,VOT,flow_type,PCE, 5 | ,p,passenger,,10,0,1, 6 | ,,,,,,, 7 | [link_type],link_type,link_type_name,,agent_type_blocklist,type_code,traffic_flow_code, 8 | ,1,Highway/Expressway,,,f,0, 9 | ,2,Major arterial,,,a,0, 10 | ,,,,,,, 11 | [demand_period],demand_period_id,demand_period,,time_period,,, 12 | ,1,AM,,0700_0800,,, 13 | ,,,,,,, 14 | [demand_file_list],file_sequence_no,file_name,,format_type,demand_period,agent_type, 15 | ,1,demand.csv,,column,AM,p, 16 | ,,,,,,, 17 | [capacity_scenario],,from_node_id,to_node_id,time_window,time_interval,travel_time_delta,capacity 18 | -------------------------------------------------------------------------------- /data/ASU/settings.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # settings for Path4GMNS 3 | agents: 4 | - type: a 5 | name: auto 6 | vot: 10 7 | flow_type: 0 8 | pce: 1 9 | free_speed: 60 10 | use_link_ffs: true 11 | - type: w 12 | name: walk 13 | vot: 10 14 | flow_type: 0 15 | pce: 1 16 | free_speed: 10 17 | use_link_ffs: false 18 | 19 | demand_periods: 20 | - period: AM 21 | time_period: 0700-0800 22 | 23 | demand_files: 24 | - file_name: demand.csv 25 | period: AM 26 | agent_type: a -------------------------------------------------------------------------------- /data/Braess_Paradox/demand.csv: -------------------------------------------------------------------------------- 1 | o_zone_id,d_zone_id,volume 2 | 1,2,4000 3 | -------------------------------------------------------------------------------- /data/Braess_Paradox/link.csv: -------------------------------------------------------------------------------- 1 | name,link_id,from_node_id,to_node_id,facility_type,dir_flag,length,lanes,capacity,free_speed,link_type,cost,VDF_fftt1,VDF_cap1,VDF_alpha1,VDF_beta1,VDF_PHF1,VDF_gamma1,VDF_mu1 2 | (null),1003,1,3,Freeway,1,0.01,1,100,60,1,0,1,100,1,1,1,1,100 3 | (null),3002,3,2,Freeway,1,45,3,5700,60,1,0,45,5700,0,1,1,1,100 4 | (null),1004,1,4,Freeway,1,45,3,5700,60,1,0,45,5700,0,1,1,1,100 5 | (null),4002,4,2,Freeway,1,0.01,1,100,60,1,0,1,100,1,1,1,1,100 6 | -------------------------------------------------------------------------------- /data/Braess_Paradox/node.csv: -------------------------------------------------------------------------------- 1 | name,node_id,zone_id,node_type,control_type,x_coord,y_coord,geometry 2 | ,1,1,,,0.017882,-0.125179, 3 | ,2,2,,,40.253933,0.053648, 4 | ,3,3,,,19.778254,14.806867, 5 | ,4,4,,,19.688841,-9.692418, 6 | -------------------------------------------------------------------------------- /data/Braess_Paradox/settings.csv: -------------------------------------------------------------------------------- 1 | [assignment],,assignment_mode,number_of_iterations,column_updating_iterations,signal_updating_iterations,signal_updating_output,remarks 2 | ,,ue,20,20,-1,0,"assignment_mode can be ue, dta or odme" 3 | ,,,,,,, 4 | [agent_type],agent_type,name,,VOT,flow_type,PCE, 5 | ,p,passenger,,10,0,1, 6 | ,,,,,,, 7 | [link_type],link_type,link_type_name,,agent_type_blocklist,type_code,traffic_flow_code, 8 | ,1,Highway/Expressway,,,f,0, 9 | ,2,Major arterial,,,a,0, 10 | ,,,,,,, 11 | [demand_period],demand_period_id,demand_period,,time_period,,, 12 | ,1,AM,,0700_0800,,, 13 | ,,,,,,, 14 | [demand_file_list],file_sequence_no,file_name,,format_type,demand_period,agent_type, 15 | ,1,demand.csv,,column,AM,p, 16 | ,,,,,,, 17 | [capacity_scenario],,from_node_id,to_node_id,time_window,time_interval,travel_time_delta,capacity 18 | ,,1,3,0700:00_0900:11,-1,-1,5 19 | -------------------------------------------------------------------------------- /data/Braess_Paradox/settings.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # settings for Path4GMNS 3 | agents: 4 | - type: a 5 | name: auto 6 | vot: 10 7 | flow_type: 0 8 | pce: 1 9 | free_speed: 60 10 | use_link_ffs: true 11 | - type: w 12 | name: walk 13 | vot: 10 14 | flow_type: 0 15 | pce: 1 16 | free_speed: 10 17 | use_link_ffs: false 18 | 19 | demand_periods: 20 | - period: AM 21 | time_period: 0700-0800 22 | 23 | demand_files: 24 | - file_name: demand.csv 25 | period: AM 26 | agent_type: a -------------------------------------------------------------------------------- /data/Chicago_Sketch/measurement.csv: -------------------------------------------------------------------------------- 1 | measurement_id,measurement_type,o_zone_id,d_zone_id,upper_bound_flag,link_id,from_node_id,to_node_id,lanes,count 2 | 1,link,,,0,76,483,481,1,3000.975 3 | 2,link,,,0,76,550,553,1,3012.533 4 | 3,link,,,0,76,479,629,1,3015.195 5 | 4,link,,,0,76,487,682,1,3018.077 6 | 5,link,,,0,76,552,619,1,3030.237 7 | 6,link,,,0,76,490,631,1,3053.734 8 | 7,link,,,0,76,635,634,1,3057.301 9 | 8,link,,,0,76,553,550,1,3057.861 10 | 9,link,,,0,76,623,627,1,3105.867 11 | 10,link,,,0,76,618,548,1,3113.615 12 | 11,link,,,0,76,559,557,1,3116.907 13 | 12,link,,,0,76,612,614,1,3134.295 14 | 13,link,,,0,76,550,560,1,3153.763 15 | 14,link,,,0,76,490,557,1,3173.211 16 | 15,link,,,0,76,634,637,1,3206.335 17 | 16,link,,,0,76,580,575,1,3211.439 18 | 17,link,,,0,76,622,555,1,3214.332 19 | 18,link,,,0,76,635,705,1,3222.971 20 | 19,link,,,0,76,575,530,1,3226.951 21 | 20,link,,,0,76,609,608,1,3227.155 22 | 21,link,,,0,76,621,620,1,3236.866 23 | 22,link,,,0,76,503,477,1,3256.041 24 | 23,link,,,0,76,599,432,1,3260.515 25 | 24,link,,,0,76,486,691,1,3265.392 26 | 25,link,,,0,76,614,619,1,3272.715 27 | 26,link,,,0,76,619,614,1,3274.603 28 | 27,link,,,0,76,621,547,1,3302.645 29 | 28,link,,,0,76,596,612,1,3305.8 30 | 29,link,,,0,76,431,428,1,3309.411 31 | 30,link,,,0,76,551,495,1,3314.216 32 | 31,link,,,0,76,633,478,1,3318.191 33 | 32,link,,,0,76,614,554,1,3323.351 34 | 33,link,,,0,76,643,576,1,3347.134 35 | 34,link,,,0,76,545,577,1,3347.171 36 | 35,link,,,0,76,646,645,1,3381.031 37 | 36,link,,,0,76,555,556,1,3394.714 38 | 37,link,,,0,76,554,614,1,3400.061 39 | 38,link,,,0,76,617,612,1,3401.984 40 | 39,link,,,0,76,634,635,1,3448.014 41 | 40,link,,,0,76,636,501,1,3461.05 42 | 41,link,,,0,76,575,581,1,3462.076 43 | 42,link,,,0,76,643,578,1,3493.136 44 | 43,link,,,0,76,629,479,1,3495.626 45 | 44,link,,,0,76,507,508,1,3505.478 46 | 45,link,,,0,76,554,435,1,3520.763 47 | 46,link,,,0,76,550,549,1,3537.133 48 | 47,link,,,0,76,547,621,1,3573.59 49 | 48,link,,,0,76,435,554,1,3576.289 50 | 49,link,,,0,76,548,550,1,3577.394 51 | 50,link,,,0,76,629,628,1,3580.067 52 | 51,link,,,0,76,618,552,1,3582.218 53 | 52,link,,,0,76,596,595,1,3585.323 54 | 53,link,,,0,76,495,551,1,3592.759 55 | 54,link,,,0,76,561,560,1,3615.554 56 | 55,link,,,0,76,609,399,1,3621.577 57 | 56,link,,,0,76,572,576,1,3643.615 58 | 57,link,,,0,76,529,530,1,3651.43 59 | 58,link,,,0,76,478,703,1,3654.473 60 | 59,link,,,0,76,427,428,1,3657.608 61 | 60,link,,,0,76,574,575,1,3660.097 62 | 61,link,,,0,76,611,680,1,3672.726 63 | 62,link,,,0,76,619,617,1,3678.438 64 | 63,link,,,0,76,481,483,1,3681.043 65 | 64,link,,,0,76,622,623,1,3693.583 66 | 65,link,,,0,76,528,529,1,3701.86 67 | 66,link,,,0,76,591,589,1,3739.26 68 | 67,link,,,0,76,622,615,1,3740.177 69 | 68,link,,,0,76,571,572,1,3747.345 70 | 69,link,,,0,76,549,550,1,3757.042 71 | 70,link,,,0,76,631,490,1,3767.098 72 | 71,link,,,0,76,545,580,1,3781.704 73 | 72,link,,,0,76,559,562,1,3788.427 74 | 73,link,,,0,76,549,547,1,3837.217 75 | 74,link,,,0,76,618,616,1,3852.601 76 | 75,link,,,0,76,559,566,1,3860.2 77 | 76,link,,,0,76,432,599,1,3867.428 78 | 77,link,,,0,76,691,692,1,3880.123 79 | 78,link,,,0,76,558,491,1,3880.66 80 | 79,link,,,0,76,622,540,1,3886.625 81 | 80,link,,,0,76,501,502,1,3886.934 82 | 81,link,,,0,76,530,523,1,3918.869 83 | 82,link,,,0,76,623,681,1,3919.239 84 | 83,link,,,0,76,558,557,1,3931.897 85 | 84,link,,,0,76,533,568,1,3960.54 86 | 85,link,,,0,76,580,545,1,3973.107 87 | 86,link,,,0,76,502,503,1,3976.652 88 | 87,link,,,0,76,548,618,1,3982.56 89 | 88,link,,,0,76,551,550,1,3989.697 90 | 89,link,,,0,76,623,622,1,3992.18 91 | 90,link,,,0,76,646,644,1,3996.77 92 | 91,link,,,0,76,705,635,1,4009.195 93 | 92,link,,,0,76,633,629,1,4009.341 94 | 93,link,,,0,76,644,643,1,4017.538 95 | 94,link,,,0,76,679,681,1,4025.056 96 | 95,link,,,0,76,528,526,1,4051.695 97 | 96,link,,,0,76,427,426,1,4058.449 98 | 97,link,,,0,76,478,633,1,4062.425 99 | 98,link,,,0,76,592,608,1,4086.385 100 | 99,link,,,0,76,632,628,1,4113.83 101 | 100,link,,,0,76,572,569,1,4115.67 102 | 101,link,,,0,76,506,644,1,4126.823 103 | 102,link,,,0,76,557,559,1,4144.837 104 | 103,link,,,0,76,560,556,1,4145.32 105 | 104,link,,,0,76,574,532,1,4145.53 106 | 105,link,,,0,76,633,632,1,4169.762 107 | 106,link,,,0,76,552,618,1,4201.679 108 | 107,link,,,0,76,440,441,1,4202.805 109 | 108,link,,,0,76,637,571,1,4215.518 110 | 109,link,,,0,76,560,553,1,4258.697 111 | 110,link,,,0,76,557,630,1,4274.488 112 | 111,link,,,0,76,487,681,1,4281.94 113 | 112,link,,,0,76,630,557,1,4293.553 114 | 113,link,,,0,76,523,545,1,4295.93 115 | 114,link,,,0,76,433,616,1,4329.261 116 | 115,link,,,0,76,426,427,1,4349.194 117 | 116,link,,,0,76,625,554,1,4358.498 118 | 117,link,,,0,76,616,433,1,4367.044 119 | 118,link,,,0,76,616,618,1,4375.574 120 | 119,link,,,0,76,572,570,1,4383.534 121 | 120,link,,,0,76,426,425,1,4386.691 122 | 121,link,,,0,76,438,439,1,4387.291 123 | 122,link,,,0,76,493,492,1,4443.781 124 | 123,link,,,0,76,487,488,1,4454.187 125 | 124,link,,,0,76,590,589,1,4460.32 126 | 125,link,,,0,76,496,556,1,4466.591 127 | 126,link,,,0,76,485,628,1,4476.603 128 | 127,link,,,0,76,535,487,1,4487.79 129 | 128,link,,,0,76,479,693,1,4499.108 130 | 129,link,,,0,76,506,507,1,4500.274 131 | 130,link,,,0,76,643,644,1,4502.181 132 | 131,link,,,0,76,626,630,1,4530.287 133 | 132,link,,,0,76,639,505,1,4532.867 134 | 133,link,,,0,76,631,559,1,4554.36 135 | 134,link,,,0,76,537,399,1,4577.433 136 | 135,link,,,0,76,438,535,1,4602.84 137 | 136,link,,,0,76,547,549,1,4606.258 138 | 137,link,,,0,76,572,573,1,4607.19 139 | 138,link,,,0,76,566,559,1,4616.563 140 | 139,link,,,0,76,441,591,1,4624.903 141 | 140,link,,,0,76,552,553,1,4625.556 142 | 141,link,,,0,76,568,533,1,4640.988 143 | 142,link,,,0,76,592,591,1,4649.552 144 | 143,link,,,0,76,681,679,1,4670.339 145 | 144,link,,,0,76,427,594,1,4706.039 146 | 145,link,,,0,76,555,622,1,4717.752 147 | 146,link,,,0,76,615,622,1,4722.654 148 | 147,link,,,0,76,498,499,1,4746.62 149 | 148,link,,,0,76,626,627,1,4749.819 150 | 149,link,,,0,76,530,575,1,4754.075 151 | 150,link,,,0,76,614,439,1,4763.481 152 | 151,link,,,0,76,627,623,1,4782.344 153 | 152,link,,,0,76,492,561,1,4797.016 154 | 153,link,,,0,76,566,500,1,4816.018 155 | 154,link,,,0,76,477,503,1,4819.473 156 | 155,link,,,0,76,503,633,1,4840.054 157 | 156,link,,,0,76,691,486,1,4846.359 158 | 157,link,,,0,76,555,625,1,4853.837 159 | 158,link,,,0,76,556,555,1,4855.217 160 | 159,link,,,0,76,569,565,1,4855.852 161 | 160,link,,,0,76,556,560,1,4856.564 162 | 161,link,,,0,76,612,617,1,4858.307 163 | 162,link,,,0,76,483,539,1,4860.104 164 | 163,link,,,0,76,568,574,1,4873.422 165 | 164,link,,,0,76,703,478,1,4884.088 166 | 165,link,,,0,76,553,552,1,4888.541 167 | 166,link,,,0,76,627,626,1,4902.313 168 | 167,link,,,0,76,627,484,1,4913.963 169 | 168,link,,,0,76,532,569,1,4916.861 170 | 169,link,,,0,76,679,610,1,4936.614 171 | 170,link,,,0,76,439,614,1,4938.827 172 | 171,link,,,0,76,426,441,1,4941.045 173 | 172,link,,,0,76,491,558,1,4951.603 174 | 173,link,,,0,76,551,549,1,4966.03 175 | 174,link,,,0,76,494,561,1,4968.006 176 | 175,link,,,0,76,578,577,1,4969.517 177 | 176,link,,,0,76,500,570,1,4979.529 178 | 177,link,,,0,76,529,528,1,4986.043 179 | 178,link,,,0,76,681,623,1,4987.519 180 | 179,link,,,0,76,536,537,1,5001.911 181 | 180,link,,,0,76,625,555,1,5018.652 182 | 181,link,,,0,76,610,679,1,5036.516 183 | 182,link,,,0,76,434,433,1,5047.817 184 | 183,link,,,0,76,644,637,1,5078.285 185 | 184,link,,,0,76,624,626,1,5090.69 186 | 185,link,,,0,76,572,571,1,5101.805 187 | 186,link,,,0,76,505,634,1,5107.373 188 | 187,link,,,0,76,554,437,1,5131.553 189 | 188,link,,,0,76,554,625,1,5145.354 190 | 189,link,,,0,76,644,506,1,5169.999 191 | 190,link,,,0,76,433,432,1,5171.861 192 | 191,link,,,0,76,577,530,1,5190.451 193 | 192,link,,,0,76,438,540,1,5223.803 194 | 193,link,,,0,76,435,434,1,5233.388 195 | 194,link,,,0,76,476,477,1,5242.717 196 | 195,link,,,0,76,630,626,1,5247.228 197 | 196,link,,,0,76,560,558,1,5250.904 198 | 197,link,,,0,76,432,431,1,5251.143 199 | 198,link,,,0,76,681,691,1,5252.157 200 | 199,link,,,0,76,590,592,1,5253.085 201 | 200,link,,,0,76,610,537,1,5256.034 202 | 201,link,,,0,76,437,554,1,5266.93 203 | 202,link,,,0,76,557,558,1,5269.403 204 | 203,link,,,0,76,553,496,1,5278.991 205 | 204,link,,,0,76,591,613,1,5288.482 206 | 205,link,,,0,76,496,553,1,5293.299 207 | 206,link,,,0,76,553,560,1,5318.712 208 | 207,link,,,0,76,500,501,1,5334.545 209 | 208,link,,,0,76,504,635,1,5339.624 210 | 209,link,,,0,76,589,591,1,5351.623 211 | 210,link,,,0,76,504,505,1,5365.581 212 | 211,link,,,0,76,499,498,1,5398.799 213 | 212,link,,,0,76,558,561,1,5402.546 214 | 213,link,,,0,76,627,486,1,5419.613 215 | 214,link,,,0,76,439,438,1,5457.44 216 | 215,link,,,0,76,608,537,1,5486.45 217 | 216,link,,,0,76,558,560,1,5500.139 218 | 217,link,,,0,76,571,501,1,5528.95 219 | 218,link,,,0,76,434,435,1,5531.274 220 | 219,link,,,0,76,502,501,1,5532.787 221 | 220,link,,,0,76,495,560,1,5533.213 222 | 221,link,,,0,76,545,523,1,5533.503 223 | 222,link,,,0,76,556,496,1,5564.621 224 | 223,link,,,0,76,528,575,1,5595.885 225 | 224,link,,,0,76,484,480,1,5612.893 226 | 225,link,,,0,76,552,435,1,5634.871 227 | 226,link,,,0,76,439,440,1,5660.477 228 | 227,link,,,0,76,499,500,1,5661.538 229 | 228,link,,,0,76,636,631,1,5671.974 230 | 229,link,,,0,76,591,441,1,5688.975 231 | 230,link,,,0,76,441,440,1,5692.237 232 | 231,link,,,0,76,505,506,1,5721.452 233 | 232,link,,,0,76,590,401,1,5741.471 234 | 233,link,,,0,76,507,506,1,5758.624 235 | 234,link,,,0,76,577,573,1,5789.555 236 | 235,link,,,0,76,573,572,1,5806.17 237 | 236,link,,,0,76,556,557,1,5819.464 238 | 237,link,,,0,76,477,504,1,5820.566 239 | 238,link,,,0,76,617,619,1,5821.529 240 | 239,link,,,0,76,500,566,1,5864.161 241 | 240,link,,,0,76,573,569,1,5869.044 242 | 241,link,,,0,76,428,431,1,5887.018 243 | 242,link,,,0,76,435,552,1,5895.938 244 | 243,link,,,0,76,480,484,1,5909.135 245 | 244,link,,,0,76,578,643,1,5928.326 246 | 245,link,,,0,76,628,485,1,5957.569 247 | 246,link,,,0,76,492,493,1,5978.411 248 | 247,link,,,0,76,550,551,1,6006.631 249 | 248,link,,,0,76,503,502,1,6022.648 250 | 249,link,,,0,76,439,615,1,6023.608 251 | 250,link,,,0,76,557,556,1,6037.897 252 | 251,link,,,0,76,504,503,1,6043.279 253 | 252,link,,,0,76,563,494,1,6044.518 254 | 253,link,,,0,76,619,434,1,6067.18 255 | 254,link,,,0,76,611,610,1,6068.097 256 | 255,link,,,0,76,535,438,1,6075.458 257 | 256,link,,,0,76,596,441,1,6090.817 258 | 257,link,,,0,76,433,434,1,6109.681 259 | 258,link,,,0,76,485,484,1,6110.354 260 | 259,link,,,0,76,615,439,1,6123.915 261 | 260,link,,,0,76,570,500,1,6154.753 262 | 261,link,,,0,76,487,535,1,6168.453 263 | 262,link,,,0,76,438,536,1,6224.69 264 | 263,link,,,0,76,498,533,1,6249.021 265 | 264,link,,,0,76,501,636,1,6290.14 266 | 265,link,,,0,76,567,499,1,6296.044 267 | 266,link,,,0,76,693,483,1,6318.449 268 | 267,link,,,0,76,432,433,1,6321.661 269 | 268,link,,,0,76,537,536,1,6331.15 270 | 269,link,,,0,76,626,485,1,6383.041 271 | 270,link,,,0,76,484,485,1,6399.896 272 | 271,link,,,0,76,581,575,1,6431.378 273 | 272,link,,,0,76,478,477,1,6443.778 274 | 273,link,,,0,76,481,691,1,6444.418 275 | 274,link,,,0,76,490,489,1,6451.841 276 | 275,link,,,0,76,504,477,1,6564.794 277 | 276,link,,,0,76,576,572,1,6568.12 278 | 277,link,,,0,76,560,561,1,6601.251 279 | 278,link,,,0,76,435,436,1,6609.22 280 | 279,link,,,0,76,480,483,1,6619.805 281 | 280,link,,,0,76,436,435,1,6627.961 282 | 281,link,,,0,76,613,615,1,6636.522 283 | 282,link,,,0,76,615,610,1,6642.208 284 | 283,link,,,0,76,437,438,1,6658.398 285 | 284,link,,,0,76,438,437,1,6662.886 286 | 285,link,,,0,76,483,480,1,6695.034 287 | 286,link,,,0,76,486,480,1,6704.072 288 | 287,link,,,0,76,431,432,1,6744.052 289 | 288,link,,,0,76,484,627,1,6799.902 290 | 289,link,,,0,76,440,439,1,6805.718 291 | 290,link,,,0,76,523,530,1,6838.846 292 | 291,link,,,0,76,499,569,1,6852.96 293 | 292,link,,,0,76,491,490,1,6868.387 294 | 293,link,,,0,76,610,615,1,6871.972 295 | 294,link,,,0,76,624,555,1,6923.875 296 | 295,link,,,0,76,480,486,1,6925.347 297 | 296,link,,,0,76,501,500,1,6949.345 298 | 297,link,,,0,76,560,495,1,6991.443 299 | 298,link,,,0,76,562,567,1,7031.151 300 | 299,link,,,0,76,691,481,1,7066.362 301 | 300,link,,,0,76,540,583,1,7136.801 302 | 301,link,,,0,76,555,624,1,7205.146 303 | 302,link,,,0,76,479,478,1,7221.626 304 | 303,link,,,0,76,561,492,1,7240.518 305 | 304,link,,,0,76,530,529,1,7275.223 306 | 305,link,,,0,76,536,438,1,7292.74 307 | 306,link,,,0,76,489,485,1,7299.867 308 | 307,link,,,0,76,691,681,1,7380.003 309 | 308,link,,,0,76,500,499,1,7403.409 310 | 309,link,,,0,76,533,498,1,7410.077 311 | 310,link,,,0,76,486,627,1,7429.871 312 | 311,link,,,0,76,561,494,1,7437.826 313 | 312,link,,,0,76,485,489,1,7439.152 314 | 313,link,,,0,76,436,437,1,7443.373 315 | 314,link,,,0,76,437,436,1,7473.621 316 | 315,link,,,0,76,533,532,1,7480.428 317 | 316,link,,,0,76,562,492,1,7503.179 318 | 317,link,,,0,76,480,479,1,7543.442 319 | 318,link,,,0,76,626,624,1,7556.38 320 | 319,link,,,0,76,562,493,1,7582.639 321 | 320,link,,,0,76,434,619,1,7591.743 322 | 321,link,,,0,76,505,504,1,7627.825 323 | 322,link,,,0,76,635,504,1,7656.921 324 | 323,link,,,0,76,493,497,1,7687.979 325 | 324,link,,,0,76,497,498,1,7687.979 326 | 325,link,,,0,76,489,490,1,7694.406 327 | 326,link,,,0,76,549,551,1,7784.642 328 | 327,link,,,0,76,485,626,1,8014.26 329 | 328,link,,,0,76,477,478,1,8025.18 330 | 329,link,,,0,76,479,480,1,8043.807 331 | 330,link,,,0,76,441,596,1,8085.556 332 | 331,link,,,0,76,493,494,1,8144.547 333 | 332,link,,,0,76,506,505,1,8202.337 334 | 333,link,,,0,76,490,491,1,8288.433 335 | 334,link,,,0,76,491,559,1,8431.78 336 | 335,link,,,0,76,492,491,1,8442.279 337 | 336,link,,,0,76,494,493,1,8509.48 338 | 337,link,,,0,76,532,531,1,8585.667 339 | 338,link,,,0,76,531,529,1,8591.978 340 | 339,link,,,0,76,483,693,1,8685.561 341 | 340,link,,,0,76,559,491,1,8737.951 342 | 341,link,,,0,76,532,533,1,8741.804 343 | 342,link,,,0,76,565,568,1,8831.898 344 | 343,link,,,0,76,537,610,1,9028.51 345 | 344,link,,,0,76,492,562,1,9067.313 346 | 345,link,,,0,76,491,492,1,9097.55 347 | 346,link,,,0,76,493,562,1,9168.695 348 | 347,link,,,0,76,478,479,1,9288.406 349 | 348,link,,,0,76,569,499,1,9496.902 350 | 349,link,,,0,76,497,493,1,9501.232 351 | 350,link,,,0,76,498,497,1,9501.232 352 | 351,link,,,0,76,531,532,1,9513.134 353 | 352,link,,,0,76,486,535,1,9943.035 354 | 353,link,,,0,76,499,567,1,10029.671 355 | 354,link,,,0,76,535,486,1,10151.104 356 | 355,link,,,0,76,494,495,1,10294.563 357 | 356,link,,,0,76,494,563,1,10424.316 358 | 357,link,,,0,76,495,496,1,10832.087 359 | 358,link,,,0,76,529,531,1,10931.622 360 | 359,link,,,0,76,564,565,1,11072.338 361 | 360,link,,,0,76,496,495,1,11927.276 362 | 361,link,,,0,76,567,562,1,12172.108 363 | 362,link,,,0,76,563,551,1,12555.435 364 | 363,link,,,0,76,495,494,1,12569.463 365 | 364,link,,,0,76,574,568,1,13397.604 366 | 365,link,,,0,76,496,436,1,14071.304 367 | 366,link,,,0,76,436,496,1,14082.794 368 | 367,link,,,0,76,575,574,1,14527.311 369 | 368,link,,,0,76,564,493,1,14959.912 370 | 369,link,,,0,76,568,565,1,15213.554 371 | 370,link,,,0,76,493,564,1,17086.674 372 | 371,link,,,0,76,563,564,1,18383.445 373 | 372,link,,,0,76,565,564,1,19223.25 374 | 373,link,,,0,76,551,563,1,19536.203 375 | 374,link,,,0,76,564,563,1,20160.752 376 | -------------------------------------------------------------------------------- /data/Chicago_Sketch/settings.csv: -------------------------------------------------------------------------------- 1 | [assignment],,assignment_mode,number_of_iterations,column_updating_iterations,signal_updating_iterations,signal_updating_output,remarks 2 | ,,ue,40,40,-1,0,"assignment_mode can be ue, dta or odme" 3 | ,,,,,,, 4 | [agent_type],agent_type,name,,VOT,flow_type,PCE, 5 | ,p,passenger,,10,0,1, 6 | ,,,,,,, 7 | [link_type],link_type,link_type_name,,agent_type_blocklist,type_code,traffic_flow_code, 8 | ,1,Highway/Expressway,,,f,0, 9 | ,2,Major arterial,,,a,0, 10 | ,,,,,,, 11 | [demand_period],demand_period_id,demand_period,,time_period,,, 12 | ,1,AM,,0700_0800,,, 13 | ,,,,,,, 14 | [demand_file_list],file_sequence_no,file_name,,format_type,demand_period,agent_type, 15 | ,1,demand.csv,,column,AM,p, 16 | ,,,,,,, 17 | [capacity_scenario],,from_node_id,to_node_id,time_window,time_interval,travel_time_delta,capacity 18 | -------------------------------------------------------------------------------- /data/Chicago_Sketch/settings.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # settings for Path4GMNS 3 | agents: 4 | - type: a 5 | name: auto 6 | vot: 10 7 | flow_type: 0 8 | pce: 1 9 | free_speed: 60 10 | use_link_ffs: true 11 | - type: w 12 | name: walk 13 | vot: 10 14 | flow_type: 0 15 | pce: 1 16 | free_speed: 10 17 | use_link_ffs: false 18 | - type: b 19 | name: bike 20 | vot: 10 21 | flow_type: 0 22 | pce: 1 23 | free_speed: 20 24 | use_link_ffs: false 25 | 26 | demand_periods: 27 | - period: AM 28 | time_period: 0700-0800 29 | 30 | demand_files: 31 | - file_name: demand.csv 32 | period: AM 33 | agent_type: a -------------------------------------------------------------------------------- /data/Lima_Network/settings.csv: -------------------------------------------------------------------------------- 1 | [assignment],,assignment_mode,number_of_iterations,column_updating_iterations,signal_updating_iterations,signal_updating_output,remarks 2 | ,,ue,40,40,-1,0,"assignment_mode can be ue, dta or odme" 3 | ,,,,,,, 4 | [agent_type],agent_type,name,,VOT,flow_type,PCE, 5 | ,p,passenger,,10,0,1, 6 | ,,,,,,, 7 | [link_type],link_type,link_type_name,,agent_type_blocklist,type_code,traffic_flow_code, 8 | ,1,Highway/Expressway,,,f,0, 9 | ,2,Major arterial,,,a,0, 10 | ,99,Centroid connector,,,c,0, 11 | ,,,,,,, 12 | [demand_period],demand_period_id,demand_period,,time_period,,, 13 | ,1,AM,,0700_0800,,, 14 | ,,,,,,, 15 | [demand_file_list],file_sequence_no,file_name,,format_type,demand_period,agent_type, 16 | ,1,demand.csv,,column,AM,p, 17 | ,,,,,,, 18 | [capacity_scenario],,from_node_id,to_node_id,time_window,time_interval,travel_time_delta,capacity 19 | -------------------------------------------------------------------------------- /data/Lima_Network/settings.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # settings for Path4GMNS 3 | agents: 4 | - type: a 5 | name: auto 6 | vot: 10 7 | flow_type: 0 8 | pce: 1 9 | free_speed: 60 10 | use_link_ffs: true 11 | 12 | demand_periods: 13 | - period: AM 14 | time_period: 0700-0800 15 | 16 | demand_files: 17 | - file_name: demand.csv 18 | period: AM 19 | agent_type: a -------------------------------------------------------------------------------- /data/Sioux_Falls/demand.csv: -------------------------------------------------------------------------------- 1 | o_zone_id,d_zone_id,volume 2 | 1,1,0 3 | 1,2,100 4 | 1,3,100 5 | 1,4,500 6 | 1,5,200 7 | 1,6,300 8 | 1,7,500 9 | 1,8,800 10 | 1,9,500 11 | 1,10,1300 12 | 1,11,500 13 | 1,12,200 14 | 1,13,500 15 | 1,14,300 16 | 1,15,500 17 | 1,16,500 18 | 1,17,400 19 | 1,18,100 20 | 1,19,300 21 | 1,20,300 22 | 1,21,100 23 | 1,22,400 24 | 1,23,300 25 | 1,24,100 26 | 2,1,100 27 | 2,2,0 28 | 2,3,100 29 | 2,4,200 30 | 2,5,100 31 | 2,6,400 32 | 2,7,200 33 | 2,8,400 34 | 2,9,200 35 | 2,10,600 36 | 2,11,200 37 | 2,12,100 38 | 2,13,300 39 | 2,14,100 40 | 2,15,100 41 | 2,16,400 42 | 2,17,200 43 | 2,18,0 44 | 2,19,100 45 | 2,20,100 46 | 2,21,0 47 | 2,22,100 48 | 2,23,0 49 | 2,24,0 50 | 3,1,100 51 | 3,2,100 52 | 3,3,0 53 | 3,4,200 54 | 3,5,100 55 | 3,6,300 56 | 3,7,100 57 | 3,8,200 58 | 3,9,100 59 | 3,10,300 60 | 3,11,300 61 | 3,12,200 62 | 3,13,100 63 | 3,14,100 64 | 3,15,100 65 | 3,16,200 66 | 3,17,100 67 | 3,18,0 68 | 3,19,0 69 | 3,20,0 70 | 3,21,0 71 | 3,22,100 72 | 3,23,100 73 | 3,24,0 74 | 4,1,500 75 | 4,2,200 76 | 4,3,200 77 | 4,4,0 78 | 4,5,500 79 | 4,6,400 80 | 4,7,400 81 | 4,8,700 82 | 4,9,700 83 | 4,10,1200 84 | 4,11,1400 85 | 4,12,600 86 | 4,13,600 87 | 4,14,500 88 | 4,15,500 89 | 4,16,800 90 | 4,17,500 91 | 4,18,100 92 | 4,19,200 93 | 4,20,300 94 | 4,21,200 95 | 4,22,400 96 | 4,23,500 97 | 4,24,200 98 | 5,1,200 99 | 5,2,100 100 | 5,3,100 101 | 5,4,500 102 | 5,5,0 103 | 5,6,200 104 | 5,7,200 105 | 5,8,500 106 | 5,9,800 107 | 5,10,1000 108 | 5,11,500 109 | 5,12,200 110 | 5,13,200 111 | 5,14,100 112 | 5,15,200 113 | 5,16,500 114 | 5,17,200 115 | 5,18,0 116 | 5,19,100 117 | 5,20,100 118 | 5,21,100 119 | 5,22,200 120 | 5,23,100 121 | 5,24,0 122 | 6,1,300 123 | 6,2,400 124 | 6,3,300 125 | 6,4,400 126 | 6,5,200 127 | 6,6,0 128 | 6,7,400 129 | 6,8,800 130 | 6,9,400 131 | 6,10,800 132 | 6,11,400 133 | 6,12,200 134 | 6,13,200 135 | 6,14,100 136 | 6,15,200 137 | 6,16,900 138 | 6,17,500 139 | 6,18,100 140 | 6,19,200 141 | 6,20,300 142 | 6,21,100 143 | 6,22,200 144 | 6,23,100 145 | 6,24,100 146 | 7,1,500 147 | 7,2,200 148 | 7,3,100 149 | 7,4,400 150 | 7,5,200 151 | 7,6,400 152 | 7,7,0 153 | 7,8,1000 154 | 7,9,600 155 | 7,10,1900 156 | 7,11,500 157 | 7,12,700 158 | 7,13,400 159 | 7,14,200 160 | 7,15,500 161 | 7,16,1400 162 | 7,17,1000 163 | 7,18,200 164 | 7,19,400 165 | 7,20,500 166 | 7,21,200 167 | 7,22,500 168 | 7,23,200 169 | 7,24,100 170 | 8,1,800 171 | 8,2,400 172 | 8,3,200 173 | 8,4,700 174 | 8,5,500 175 | 8,6,800 176 | 8,7,1000 177 | 8,8,0 178 | 8,9,800 179 | 8,10,1600 180 | 8,11,800 181 | 8,12,600 182 | 8,13,600 183 | 8,14,400 184 | 8,15,600 185 | 8,16,2200 186 | 8,17,1400 187 | 8,18,300 188 | 8,19,700 189 | 8,20,900 190 | 8,21,400 191 | 8,22,500 192 | 8,23,300 193 | 8,24,200 194 | 9,1,500 195 | 9,2,200 196 | 9,3,100 197 | 9,4,700 198 | 9,5,800 199 | 9,6,400 200 | 9,7,600 201 | 9,8,800 202 | 9,9,0 203 | 9,10,2800 204 | 9,11,1400 205 | 9,12,600 206 | 9,13,600 207 | 9,14,600 208 | 9,15,900 209 | 9,16,1400 210 | 9,17,900 211 | 9,18,200 212 | 9,19,400 213 | 9,20,600 214 | 9,21,300 215 | 9,22,700 216 | 9,23,500 217 | 9,24,200 218 | 10,1,1300 219 | 10,2,600 220 | 10,3,300 221 | 10,4,1200 222 | 10,5,1000 223 | 10,6,800 224 | 10,7,1900 225 | 10,8,1600 226 | 10,9,2800 227 | 10,10,0 228 | 10,11,4000 229 | 10,12,2000 230 | 10,13,1900 231 | 10,14,2100 232 | 10,15,4000 233 | 10,16,4400 234 | 10,17,3900 235 | 10,18,700 236 | 10,19,1800 237 | 10,20,2500 238 | 10,21,1200 239 | 10,22,2600 240 | 10,23,1800 241 | 10,24,800 242 | 11,1,500 243 | 11,2,200 244 | 11,3,300 245 | 11,4,1500 246 | 11,5,500 247 | 11,6,400 248 | 11,7,500 249 | 11,8,800 250 | 11,9,1400 251 | 11,10,3900 252 | 11,11,0 253 | 11,12,1400 254 | 11,13,1000 255 | 11,14,1600 256 | 11,15,1400 257 | 11,16,1400 258 | 11,17,1000 259 | 11,18,100 260 | 11,19,400 261 | 11,20,600 262 | 11,21,400 263 | 11,22,1100 264 | 11,23,1300 265 | 11,24,600 266 | 12,1,200 267 | 12,2,100 268 | 12,3,200 269 | 12,4,600 270 | 12,5,200 271 | 12,6,200 272 | 12,7,700 273 | 12,8,600 274 | 12,9,600 275 | 12,10,2000 276 | 12,11,1400 277 | 12,12,0 278 | 12,13,1300 279 | 12,14,700 280 | 12,15,700 281 | 12,16,700 282 | 12,17,600 283 | 12,18,200 284 | 12,19,300 285 | 12,20,400 286 | 12,21,300 287 | 12,22,700 288 | 12,23,700 289 | 12,24,500 290 | 13,1,500 291 | 13,2,300 292 | 13,3,100 293 | 13,4,600 294 | 13,5,200 295 | 13,6,200 296 | 13,7,400 297 | 13,8,600 298 | 13,9,600 299 | 13,10,1900 300 | 13,11,1000 301 | 13,12,1300 302 | 13,13,0 303 | 13,14,600 304 | 13,15,700 305 | 13,16,600 306 | 13,17,500 307 | 13,18,100 308 | 13,19,300 309 | 13,20,600 310 | 13,21,600 311 | 13,22,1300 312 | 13,23,800 313 | 13,24,800 314 | 14,1,300 315 | 14,2,100 316 | 14,3,100 317 | 14,4,500 318 | 14,5,100 319 | 14,6,100 320 | 14,7,200 321 | 14,8,400 322 | 14,9,600 323 | 14,10,2100 324 | 14,11,1600 325 | 14,12,700 326 | 14,13,600 327 | 14,14,0 328 | 14,15,1300 329 | 14,16,700 330 | 14,17,700 331 | 14,18,100 332 | 14,19,300 333 | 14,20,500 334 | 14,21,400 335 | 14,22,1200 336 | 14,23,1100 337 | 14,24,400 338 | 15,1,500 339 | 15,2,100 340 | 15,3,100 341 | 15,4,500 342 | 15,5,200 343 | 15,6,200 344 | 15,7,500 345 | 15,8,600 346 | 15,9,1000 347 | 15,10,4000 348 | 15,11,1400 349 | 15,12,700 350 | 15,13,700 351 | 15,14,1300 352 | 15,15,0 353 | 15,16,1200 354 | 15,17,1500 355 | 15,18,200 356 | 15,19,800 357 | 15,20,1100 358 | 15,21,800 359 | 15,22,2600 360 | 15,23,1000 361 | 15,24,400 362 | 16,1,500 363 | 16,2,400 364 | 16,3,200 365 | 16,4,800 366 | 16,5,500 367 | 16,6,900 368 | 16,7,1400 369 | 16,8,2200 370 | 16,9,1400 371 | 16,10,4400 372 | 16,11,1400 373 | 16,12,700 374 | 16,13,600 375 | 16,14,700 376 | 16,15,1200 377 | 16,16,0 378 | 16,17,2800 379 | 16,18,500 380 | 16,19,1300 381 | 16,20,1600 382 | 16,21,600 383 | 16,22,1200 384 | 16,23,500 385 | 16,24,300 386 | 17,1,400 387 | 17,2,200 388 | 17,3,100 389 | 17,4,500 390 | 17,5,200 391 | 17,6,500 392 | 17,7,1000 393 | 17,8,1400 394 | 17,9,900 395 | 17,10,3900 396 | 17,11,1000 397 | 17,12,600 398 | 17,13,500 399 | 17,14,700 400 | 17,15,1500 401 | 17,16,2800 402 | 17,17,0 403 | 17,18,600 404 | 17,19,1700 405 | 17,20,1700 406 | 17,21,600 407 | 17,22,1700 408 | 17,23,600 409 | 17,24,300 410 | 18,1,100 411 | 18,2,0 412 | 18,3,0 413 | 18,4,100 414 | 18,5,0 415 | 18,6,100 416 | 18,7,200 417 | 18,8,300 418 | 18,9,200 419 | 18,10,700 420 | 18,11,200 421 | 18,12,200 422 | 18,13,100 423 | 18,14,100 424 | 18,15,200 425 | 18,16,500 426 | 18,17,600 427 | 18,18,0 428 | 18,19,300 429 | 18,20,400 430 | 18,21,100 431 | 18,22,300 432 | 18,23,100 433 | 18,24,0 434 | 19,1,300 435 | 19,2,100 436 | 19,3,0 437 | 19,4,200 438 | 19,5,100 439 | 19,6,200 440 | 19,7,400 441 | 19,8,700 442 | 19,9,400 443 | 19,10,1800 444 | 19,11,400 445 | 19,12,300 446 | 19,13,300 447 | 19,14,300 448 | 19,15,800 449 | 19,16,1300 450 | 19,17,1700 451 | 19,18,300 452 | 19,19,0 453 | 19,20,1200 454 | 19,21,400 455 | 19,22,1200 456 | 19,23,300 457 | 19,24,100 458 | 20,1,300 459 | 20,2,100 460 | 20,3,0 461 | 20,4,300 462 | 20,5,100 463 | 20,6,300 464 | 20,7,500 465 | 20,8,900 466 | 20,9,600 467 | 20,10,2500 468 | 20,11,600 469 | 20,12,500 470 | 20,13,600 471 | 20,14,500 472 | 20,15,1100 473 | 20,16,1600 474 | 20,17,1700 475 | 20,18,400 476 | 20,19,1200 477 | 20,20,0 478 | 20,21,1200 479 | 20,22,2400 480 | 20,23,700 481 | 20,24,400 482 | 21,1,100 483 | 21,2,0 484 | 21,3,0 485 | 21,4,200 486 | 21,5,100 487 | 21,6,100 488 | 21,7,200 489 | 21,8,400 490 | 21,9,300 491 | 21,10,1200 492 | 21,11,400 493 | 21,12,300 494 | 21,13,600 495 | 21,14,400 496 | 21,15,800 497 | 21,16,600 498 | 21,17,600 499 | 21,18,100 500 | 21,19,400 501 | 21,20,1200 502 | 21,21,0 503 | 21,22,1800 504 | 21,23,700 505 | 21,24,500 506 | 22,1,400 507 | 22,2,100 508 | 22,3,100 509 | 22,4,400 510 | 22,5,200 511 | 22,6,200 512 | 22,7,500 513 | 22,8,500 514 | 22,9,700 515 | 22,10,2600 516 | 22,11,1100 517 | 22,12,700 518 | 22,13,1300 519 | 22,14,1200 520 | 22,15,2600 521 | 22,16,1200 522 | 22,17,1700 523 | 22,18,300 524 | 22,19,1200 525 | 22,20,2400 526 | 22,21,1800 527 | 22,22,0 528 | 22,23,2100 529 | 22,24,1100 530 | 23,1,300 531 | 23,2,0 532 | 23,3,100 533 | 23,4,500 534 | 23,5,100 535 | 23,6,100 536 | 23,7,200 537 | 23,8,300 538 | 23,9,500 539 | 23,10,1800 540 | 23,11,1300 541 | 23,12,700 542 | 23,13,800 543 | 23,14,1100 544 | 23,15,1000 545 | 23,16,500 546 | 23,17,600 547 | 23,18,100 548 | 23,19,300 549 | 23,20,700 550 | 23,21,700 551 | 23,22,2100 552 | 23,23,0 553 | 23,24,700 554 | 24,1,100 555 | 24,2,0 556 | 24,3,0 557 | 24,4,200 558 | 24,5,0 559 | 24,6,100 560 | 24,7,100 561 | 24,8,200 562 | 24,9,200 563 | 24,10,800 564 | 24,11,600 565 | 24,12,500 566 | 24,13,700 567 | 24,14,400 568 | 24,15,400 569 | 24,16,300 570 | 24,17,300 571 | 24,18,0 572 | 24,19,100 573 | 24,20,400 574 | 24,21,500 575 | 24,22,1100 576 | 24,23,700 577 | 24,24,0 578 | -------------------------------------------------------------------------------- /data/Sioux_Falls/link.csv: -------------------------------------------------------------------------------- 1 | name,link_id,from_node_id,to_node_id,facility_type,dir_flag,length,lanes,capacity,free_speed,link_type,cost,VDF_fftt1,VDF_cap1,VDF_alpha1,VDF_beta1,VDF_PHF1,VDF_gamma1,VDF_mu1 2 | ,1,1,2,freeway,1,6,1,25900.20064,60,1,0,6,25900.20064,0.15,4,1,1,100 3 | ,2,1,3,freeway,1,4,1,23403.47319,60,1,0,4,23403.47319,0.15,4,1,1,100 4 | ,3,2,1,freeway,1,6,1,25900.20064,60,1,0,6,25900.20064,0.15,4,1,1,100 5 | ,4,2,6,freeway,1,5,1,4958.180928,60,1,0,5,4958.180928,0.15,4,1,1,100 6 | ,5,3,1,freeway,1,4,1,23403.47319,60,1,0,4,23403.47319,0.15,4,1,1,100 7 | ,6,3,4,freeway,1,4,1,17110.52372,60,1,0,4,17110.52372,0.15,4,1,1,100 8 | ,7,3,12,freeway,1,4,1,23403.47319,60,1,0,4,23403.47319,0.15,4,1,1,100 9 | ,8,4,3,freeway,1,4,1,17110.52372,60,1,0,4,17110.52372,0.15,4,1,1,100 10 | ,9,4,5,freeway,1,2,1,17782.7941,60,1,0,2,17782.7941,0.15,4,1,1,100 11 | ,10,4,11,freeway,1,6,1,4908.82673,60,1,0,6,4908.82673,0.15,4,1,1,100 12 | ,11,5,4,freeway,1,2,1,17782.7941,60,1,0,2,17782.7941,0.15,4,1,1,100 13 | ,12,5,6,freeway,1,4,1,4947.995469,60,1,0,4,4947.995469,0.15,4,1,1,100 14 | ,13,5,9,freeway,1,5,1,10000,60,1,0,5,10000,0.15,4,1,1,100 15 | ,14,6,2,freeway,1,5,1,4958.180928,60,1,0,5,4958.180928,0.15,4,1,1,100 16 | ,15,6,5,freeway,1,4,1,4947.995469,60,1,0,4,4947.995469,0.15,4,1,1,100 17 | ,16,6,8,freeway,1,2,1,4898.587646,60,1,0,2,4898.587646,0.15,4,1,1,100 18 | ,17,7,8,freeway,1,3,1,7841.81131,60,1,0,3,7841.81131,0.15,4,1,1,100 19 | ,18,7,18,freeway,1,2,1,23403.47319,60,1,0,2,23403.47319,0.15,4,1,1,100 20 | ,19,8,6,freeway,1,2,1,4898.587646,60,1,0,2,4898.587646,0.15,4,1,1,100 21 | ,20,8,7,freeway,1,3,1,7841.81131,60,1,0,3,7841.81131,0.15,4,1,1,100 22 | ,21,8,9,freeway,1,10,1,5050.193156,60,1,0,10,5050.193156,0.15,4,1,1,100 23 | ,22,8,16,freeway,1,5,1,5045.822583,60,1,0,5,5045.822583,0.15,4,1,1,100 24 | ,23,9,5,freeway,1,5,1,10000,60,1,0,5,10000,0.15,4,1,1,100 25 | ,24,9,8,freeway,1,10,1,5050.193156,60,1,0,10,5050.193156,0.15,4,1,1,100 26 | ,25,9,10,freeway,1,3,1,13915.78842,60,1,0,3,13915.78842,0.15,4,1,1,100 27 | ,26,10,9,freeway,1,3,1,13915.78842,60,1,0,3,13915.78842,0.15,4,1,1,100 28 | ,27,10,11,freeway,1,5,1,10000,60,1,0,5,10000,0.15,4,1,1,100 29 | ,28,10,15,freeway,1,6,1,13512.00155,60,1,0,6,13512.00155,0.15,4,1,1,100 30 | ,29,10,16,freeway,1,4,1,4854.917717,60,1,0,4,4854.917717,0.15,4,1,1,100 31 | ,30,10,17,freeway,1,8,1,4993.510694,60,1,0,8,4993.510694,0.15,4,1,1,100 32 | ,31,11,4,freeway,1,6,1,4908.82673,60,1,0,6,4908.82673,0.15,4,1,1,100 33 | ,32,11,10,freeway,1,5,1,10000,60,1,0,5,10000,0.15,4,1,1,100 34 | ,33,11,12,freeway,1,6,1,4908.82673,60,1,0,6,4908.82673,0.15,4,1,1,100 35 | ,34,11,14,freeway,1,4,1,4876.508287,60,1,0,4,4876.508287,0.15,4,1,1,100 36 | ,35,12,3,freeway,1,4,1,23403.47319,60,1,0,4,23403.47319,0.15,4,1,1,100 37 | ,36,12,11,freeway,1,6,1,4908.82673,60,1,0,6,4908.82673,0.15,4,1,1,100 38 | ,37,12,13,freeway,1,3,1,25900.20064,60,1,0,3,25900.20064,0.15,4,1,1,100 39 | ,38,13,12,freeway,1,3,1,25900.20064,60,1,0,3,25900.20064,0.15,4,1,1,100 40 | ,39,13,24,freeway,1,4,1,5091.256152,60,1,0,4,5091.256152,0.15,4,1,1,100 41 | ,40,14,11,freeway,1,4,1,4876.508287,60,1,0,4,4876.508287,0.15,4,1,1,100 42 | ,41,14,15,freeway,1,5,1,5127.526119,60,1,0,5,5127.526119,0.15,4,1,1,100 43 | ,42,14,23,freeway,1,4,1,4924.790605,60,1,0,4,4924.790605,0.15,4,1,1,100 44 | ,43,15,10,freeway,1,6,1,13512.00155,60,1,0,6,13512.00155,0.15,4,1,1,100 45 | ,44,15,14,freeway,1,5,1,5127.526119,60,1,0,5,5127.526119,0.15,4,1,1,100 46 | ,45,15,19,freeway,1,3,1,14564.75315,60,1,0,3,14564.75315,0.15,4,1,1,100 47 | ,46,15,22,freeway,1,3,1,9599.180565,60,1,0,3,9599.180565,0.15,4,1,1,100 48 | ,47,16,8,freeway,1,5,1,5045.822583,60,1,0,5,5045.822583,0.15,4,1,1,100 49 | ,48,16,10,freeway,1,4,1,4854.917717,60,1,0,4,4854.917717,0.15,4,1,1,100 50 | ,49,16,17,freeway,1,2,1,5229.910063,60,1,0,2,5229.910063,0.15,4,1,1,100 51 | ,50,16,18,freeway,1,3,1,19679.89671,60,1,0,3,19679.89671,0.15,4,1,1,100 52 | ,51,17,10,freeway,1,8,1,4993.510694,60,1,0,8,4993.510694,0.15,4,1,1,100 53 | ,52,17,16,freeway,1,2,1,5229.910063,60,1,0,2,5229.910063,0.15,4,1,1,100 54 | ,53,17,19,freeway,1,2,1,4823.950831,60,1,0,2,4823.950831,0.15,4,1,1,100 55 | ,54,18,7,freeway,1,2,1,23403.47319,60,1,0,2,23403.47319,0.15,4,1,1,100 56 | ,55,18,16,freeway,1,3,1,19679.89671,60,1,0,3,19679.89671,0.15,4,1,1,100 57 | ,56,18,20,freeway,1,4,1,23403.47319,60,1,0,4,23403.47319,0.15,4,1,1,100 58 | ,57,19,15,freeway,1,3,1,14564.75315,60,1,0,3,14564.75315,0.15,4,1,1,100 59 | ,58,19,17,freeway,1,2,1,4823.950831,60,1,0,2,4823.950831,0.15,4,1,1,100 60 | ,59,19,20,freeway,1,4,1,5002.607563,60,1,0,4,5002.607563,0.15,4,1,1,100 61 | ,60,20,18,freeway,1,4,1,23403.47319,60,1,0,4,23403.47319,0.15,4,1,1,100 62 | ,61,20,19,freeway,1,4,1,5002.607563,60,1,0,4,5002.607563,0.15,4,1,1,100 63 | ,62,20,21,freeway,1,6,1,5059.91234,60,1,0,6,5059.91234,0.15,4,1,1,100 64 | ,63,20,22,freeway,1,5,1,5075.697193,60,1,0,5,5075.697193,0.15,4,1,1,100 65 | ,64,21,20,freeway,1,6,1,5059.91234,60,1,0,6,5059.91234,0.15,4,1,1,100 66 | ,65,21,22,freeway,1,2,1,5229.910063,60,1,0,2,5229.910063,0.15,4,1,1,100 67 | ,66,21,24,freeway,1,3,1,4885.357564,60,1,0,3,4885.357564,0.15,4,1,1,100 68 | ,67,22,15,freeway,1,3,1,9599.180565,60,1,0,3,9599.180565,0.15,4,1,1,100 69 | ,68,22,20,freeway,1,5,1,5075.697193,60,1,0,5,5075.697193,0.15,4,1,1,100 70 | ,69,22,21,freeway,1,2,1,5229.910063,60,1,0,2,5229.910063,0.15,4,1,1,100 71 | ,70,22,23,freeway,1,4,1,5000,60,1,0,4,5000,0.15,4,1,1,100 72 | ,71,23,14,freeway,1,4,1,4924.790605,60,1,0,4,4924.790605,0.15,4,1,1,100 73 | ,72,23,22,freeway,1,4,1,5000,60,1,0,4,5000,0.15,4,1,1,100 74 | ,73,23,24,freeway,1,2,1,5078.508436,60,1,0,2,5078.508436,0.15,4,1,1,100 75 | ,74,24,13,freeway,1,4,1,5091.256152,60,1,0,4,5091.256152,0.15,4,1,1,100 76 | ,75,24,21,freeway,1,3,1,4885.357564,60,1,0,3,4885.357564,0.15,4,1,1,100 77 | ,76,24,23,freeway,1,2,1,5078.508436,60,1,0,2,5078.508436,0.15,4,1,1,100 78 | -------------------------------------------------------------------------------- /data/Sioux_Falls/node.csv: -------------------------------------------------------------------------------- 1 | name,node_id,zone_id,node_type,control_type,x_coord,y_coord,geometry 2 | ,1,1,,,-96.77041974,43.61282792, 3 | ,2,2,,,-96.71125063,43.60581298, 4 | ,3,3,,,-96.77430341,43.5729616, 5 | ,4,4,,,-96.74716843,43.56365362, 6 | ,5,5,,,-96.73156909,43.56403357, 7 | ,6,6,,,-96.71164389,43.58758553, 8 | ,7,7,,,-96.69342281,43.5638436, 9 | ,8,8,,,-96.71138171,43.56232379, 10 | ,9,9,,,-96.73124137,43.54859634, 11 | ,10,10,,,-96.73143801,43.54527088, 12 | ,11,11,,,-96.74684071,43.54413068, 13 | ,12,12,,,-96.78013678,43.54394065, 14 | ,13,13,,,-96.79337655,43.49070718, 15 | ,14,14,,,-96.75103549,43.52930613, 16 | ,15,15,,,-96.73150355,43.52940117, 17 | ,16,16,,,-96.71138171,43.54674361, 18 | ,17,17,,,-96.71138171,43.54128009, 19 | ,18,18,,,-96.69407825,43.54674361, 20 | ,19,19,,,-96.71131617,43.52959125, 21 | ,20,20,,,-96.71118508,43.5153335, 22 | ,21,21,,,-96.7309792,43.51048509, 23 | ,22,22,,,-96.73124137,43.51485818, 24 | ,23,23,,,-96.75090441,43.51485818, 25 | ,24,24,,,-96.74920028,43.50316422, 26 | -------------------------------------------------------------------------------- /data/Sioux_Falls/settings.csv: -------------------------------------------------------------------------------- 1 | [assignment],,assignment_mode,number_of_iterations,column_updating_iterations,signal_updating_iterations,signal_updating_output,remarks 2 | ,,ue,40,40,-1,0,"assignment_mode can be ue, dta or odme" 3 | ,,,,,,, 4 | [agent_type],agent_type,name,,VOT,flow_type,PCE, 5 | ,p,passenger,,10,0,1, 6 | ,,,,,,, 7 | [link_type],link_type,link_type_name,,agent_type_blocklist,type_code,traffic_flow_code, 8 | ,1,Highway/Expressway,,,f,0, 9 | ,2,Major arterial,,,a,0, 10 | ,,,,,,, 11 | [demand_period],demand_period_id,demand_period,,time_period,,, 12 | ,1,AM,,0700_0800,,, 13 | ,,,,,,, 14 | [demand_file_list],file_sequence_no,file_name,,format_type,demand_period,agent_type, 15 | ,1,demand.csv,,column,AM,p, 16 | ,,,,,,, 17 | [capacity_scenario],,from_node_id,to_node_id,time_window,time_interval,travel_time_delta,capacity 18 | -------------------------------------------------------------------------------- /data/Sioux_Falls/settings.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # settings for Path4GMNS 3 | agents: 4 | - type: a 5 | name: auto 6 | vot: 10 7 | flow_type: 0 8 | pce: 1 9 | free_speed: 60 10 | use_link_ffs: true 11 | - type: w 12 | name: walk 13 | vot: 10 14 | flow_type: 0 15 | pce: 1 16 | free_speed: 10 17 | use_link_ffs: false 18 | 19 | demand_periods: 20 | - period: AM 21 | time_period: 0700-0800 22 | 23 | demand_files: 24 | - file_name: demand.csv 25 | period: AM 26 | agent_type: a -------------------------------------------------------------------------------- /data/Two_Corridor/demand.csv: -------------------------------------------------------------------------------- 1 | o_zone_id,d_zone_id,volume 2 | 1,2,7000 3 | -------------------------------------------------------------------------------- /data/Two_Corridor/link.csv: -------------------------------------------------------------------------------- 1 | link_id,name,from_node_id,to_node_id,facility_type,link_type,dir_flag,length,lanes,free_speed,capacity,geometry 2 | 1,(null),1,3,Freeway,1,1,10,1,60,4000,"LINESTRING (0.017882 -0.125179 ,19.778254 14.806867 )" 3 | 2,(null),3,2,Freeway,1,1,10,1,60,4000,"LINESTRING (19.778254 14.806867 ,40.253933 0.053648 )" 4 | 3,(null),1,4,arterial,2,1,15,1,60,3000,"LINESTRING (0.017882 -0.125179 ,19.688841 -9.692418 )" 5 | 4,(null),4,2,arterial,2,1,15,1,60,3000,"LINESTRING (19.688841 -9.692418 ,40.253933 0.053648 )" 6 | -------------------------------------------------------------------------------- /data/Two_Corridor/node.csv: -------------------------------------------------------------------------------- 1 | node_id,name,x_coord,y_coord,node_type,ctrl_type,zone_id,geometry 2 | 1,,0.017882,-0.125179,,,1,POINT (0.017882 -0.125179) 3 | 2,,40.253933,0.053648,,,2,POINT (40.253933 0.053648) 4 | 3,,19.778254,14.806867,,,0,POINT (19.778254 14.806867) 5 | 4,,19.688841,-9.692418,,,0,POINT (19.688841 -9.692418) 6 | -------------------------------------------------------------------------------- /data/Two_Corridor/settings.csv: -------------------------------------------------------------------------------- 1 | [assignment],,assignment_mode,number_of_iterations,column_updating_iterations,,signal_updating_iterations,signal_updating_output,,remarks 2 | ,,dta,2,0,2,2,2,,"assignment_mode can be ue, dta or odme" 3 | ,,,,,,,,, 4 | [agent_type],,agent_type,name,,VOT,flow_type,PCE,, 5 | ,,p,passenger,,10,0,1,, 6 | ,,,,,,,,, 7 | [link_type],,link_type,link_type_name,,agent_type_blocklist,type_code,traffic_flow_code,, 8 | ,,1,Highway/Expressway,,,f,kw,, 9 | ,,2,Major arterial,,,a,spatial_queue,, 10 | ,,3,Minor arterial,,,m,spatial_queue,, 11 | ,,4,Local,,,l,spatial_queue,, 12 | ,,5,minor arterial,,,m,point_queue,, 13 | ,,20,Movement,,,mov,point_queue,, 14 | ,,99,Connetor,,,c,point_queue,, 15 | [demand_period],,demand_period_id,demand_period,,time_period,,,, 16 | ,,1,AM,,0700_0800,,,, 17 | ,,,,,,,,, 18 | [demand_file_list],,file_sequence_no,file_name,,format_type,demand_period,agent_type,, 19 | ,,1,demand.csv,,column,AM,p,, 20 | -------------------------------------------------------------------------------- /data/Two_Corridor/settings.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # settings for Path4GMNS 3 | agents: 4 | - type: a 5 | name: auto 6 | vot: 10 7 | flow_type: 0 8 | pce: 1 9 | free_speed: 60 10 | use_link_ffs: true 11 | - type: w 12 | name: walk 13 | vot: 10 14 | flow_type: 0 15 | pce: 1 16 | free_speed: 10 17 | use_link_ffs: false 18 | 19 | demand_periods: 20 | - period: AM 21 | time_period: 0700-0800 22 | 23 | demand_files: 24 | - file_name: demand.csv 25 | period: AM 26 | agent_type: a -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/source/api.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Public API 3 | ========== 4 | 5 | 6 | path4gmns.accessibility 7 | ======================= 8 | .. autofunction:: path4gmns.accessibility.evaluate_accessibility 9 | .. autofunction:: path4gmns.accessibility.evaluate_equity 10 | 11 | 12 | path4gmns.classes 13 | ================= 14 | .. autoclass:: path4gmns.classes.UI 15 | :members: 16 | 17 | 18 | path4gmns.dtaapi 19 | ================ 20 | .. autofunction:: path4gmns.dtaapi.perform_network_assignment_DTALite 21 | .. autofunction:: path4gmns.dtaapi.run_DTALite 22 | 23 | 24 | path4gmns.conduct_odme 25 | ====================== 26 | .. autofunction:: path4gmns.odme.conduct_odme 27 | 28 | 29 | path4gmns.colgen 30 | ================ 31 | .. autofunction:: path4gmns.colgen.find_ue 32 | 33 | 34 | path4gmns.io 35 | ============ 36 | .. autofunction:: path4gmns.io.read_demand 37 | .. autofunction:: path4gmns.io.read_measurements 38 | .. autofunction:: path4gmns.io.read_network 39 | .. autofunction:: path4gmns.io.load_demand 40 | .. autofunction:: path4gmns.io.load_columns 41 | .. autofunction:: path4gmns.io.output_columns 42 | .. autofunction:: path4gmns.io.output_link_performance 43 | .. autofunction:: path4gmns.io.output_agent_paths 44 | .. autofunction:: path4gmns.io.output_agent_trajectory 45 | .. autofunction:: path4gmns.io.output_synthetic_zones 46 | .. autofunction:: path4gmns.io.output_synthetic_demand 47 | 48 | 49 | path4gmns.simulation 50 | ==================== 51 | .. autofunction:: path4gmns.simulation.perform_simple_simulation 52 | 53 | 54 | path4gmns.utils 55 | =============== 56 | .. autofunction:: path4gmns.utils.download_sample_data_sets 57 | .. autofunction:: path4gmns.utils.download_sample_setting_file 58 | 59 | 60 | path4gmns.zonesyn 61 | ================= 62 | .. autofunction:: path4gmns.zonesyn.network_to_zones -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | 16 | 17 | sys.path.insert(0, os.path.abspath('../../')) 18 | 19 | autodoc_mock_imports = [ 20 | 'numpy' 21 | ] 22 | 23 | 24 | # -- Project information ----------------------------------------------------- 25 | 26 | project = 'Path4GMNS' 27 | copyright = '2021 - 2024, Dr. Peiheng Li and Dr. Xuesong (Simon) Zhou' 28 | author = 'Dr. Peiheng Li and Dr. Xuesong (Simon) Zhou' 29 | 30 | # The full version, including alpha/beta/rc tags 31 | release = 'v0.9.10' 32 | 33 | 34 | # -- General configuration --------------------------------------------------- 35 | 36 | # Add any Sphinx extension module names here, as strings. They can be 37 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 38 | # ones. 39 | extensions = [ 40 | 'myst_parser', 41 | 'sphinx_rtd_theme', 42 | 'sphinx.ext.mathjax', 43 | 'sphinx.ext.autodoc', 44 | 'sphinx.ext.viewcode', 45 | 'sphinx.ext.napoleon' 46 | ] 47 | 48 | source_suffix = [ 49 | '.rst', 50 | '.md' 51 | ] 52 | 53 | # Add any paths that contain templates here, relative to this directory. 54 | templates_path = ['_templates'] 55 | 56 | # The language for content autogenerated by Sphinx. Refer to documentation 57 | # for a list of supported languages. 58 | # 59 | # This is also used if you do content translation via gettext catalogs. 60 | # Usually you set "language" from the command line for these cases. 61 | language = 'python' 62 | 63 | # List of patterns, relative to source directory, that match files and 64 | # directories to ignore when looking for source files. 65 | # This pattern also affects html_static_path and html_extra_path. 66 | exclude_patterns = [] 67 | 68 | 69 | # -- Options for HTML output ------------------------------------------------- 70 | 71 | # The theme to use for HTML and HTML Help pages. See the documentation for 72 | # a list of builtin themes. 73 | # 74 | html_theme = 'sphinx_rtd_theme' 75 | 76 | # Add any paths that contain custom static files (such as style sheets) here, 77 | # relative to this directory. They are copied after the builtin static files, 78 | # so a file named "default.css" will overwrite the builtin "default.css". 79 | html_static_path = ['_static'] -------------------------------------------------------------------------------- /docs/source/imgs/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdlph/Path4GMNS/e9ed456cbaa72da41b5849e7aacce553d10fc3e1/docs/source/imgs/architecture.png -------------------------------------------------------------------------------- /docs/source/implnotes.md: -------------------------------------------------------------------------------- 1 | # Implementation Notes 2 | 3 | The column generation scheme in Path4GMNS is an equivalent **single-processing implementation** as its [DTALite](https://github.com/jdlph/DTALite/tree/main/src_cpp) multiprocessing counterpart. **Note that** the results (i.e., column pool and trajectory for each agent) from Path4GMNS and DTALite are comparable but likely not identical as the shortest paths are usually not unique and subjected to implementations. This difference shall be subtle and the link performances shall be consistent if the iterations of column generation and column update are both large enough. You can always compare the results (i.e., link_performance.csv) from Path4GMNS and DTALite given the same network and demand. 4 | 5 | ## Design Goals 6 | 7 | The whole package is implemented towards **high performance**. The core shortest-path engine is implemented in C++ (deque implementation of the modified label correcting algorithm) along with the equivalent Python implementations for demonstration. To achieve the maximum efficiency, we use a fixed-length array as the deque (rather than the STL deque) and combine the scan eligible list (represented as deque) with the node presence status. Along with the minimum and fast argument interfacing between the underlying C++ path engine and the upper Python modules, its running time is comparable to the pure C++-based DTALite for small- and medium-size networks (e.g., the Chicago Sketch Network) without multiprocessing. If you have an extremely large network and/or have requirement on CPU time, we recommend using DTALite to fully utilize its parallel computing feature. 8 | 9 | An easy and smooth installation process by **low dependency** is one of our major design goals. The core Python modules in Path4GMNS only require a handful of components from the Python standard library (e.g., csv, ctypes, and so on) with no any third-party libraries/packages. On the C++ side, the precompiled path engines as shared libraries are embedded to make this package portable across three major desktop environments (i.e., Windows, macOS, and Linux) and its source is implemented in C++11 with no dependency. Users can easily build the path engine from the source code towards their target system if it is not listed above as one of the three. 10 | 11 | ## More on the Column-Generation Module 12 | 13 | **The column generation module first identifies new columns (i.e., paths) between each OD pair at each iteration and adds them into the column pool before optimizing (i.e., shifting flows among columns to achieve the equilibrium state)**. The original implementations in both DTALite and Path4GMNS (prior to v0.8.0) rely on node sum as the unique key (or hash index) to differentiate columns, which is simply the summation of node sequence numbers along a column. However, it cannot guarantee that a non-existing column will always be added to the column pool as different columns may share the same node sum (and we presume a one-to-one mapping from node sum to column rather than an array of slots for different columns with the same node sum). An example would be 0->1->4->5 and 0->2->3->5, where 0 to 5 are node sequence numbers. One of the columns will be precluded from the column pool. 14 | 15 | In order to resolve this issue, we have deprecated node sum and introduced a side-by-side column comparison in Path4GMNS only. As columns between an OD pair are largely different in number of nodes, this comparison can be very efficiently. Slight improvements are actually observed in both running time and convergence gap over the original implementation. 16 | 17 | DTALite uses arrays rather than STL containers to store columns. These arrays are fixed in size (1,000), which prevents a fast filtering using the number of nodes as described above. For two (long) columns only different in the last few nodes, this side-by-side comparison has to be continued until the very end and ruins the performance. Thus, we decide **NOT TO ADOPT** this updated implementation to DTALite and leave it to **[TransOMS](https://github.com/jdlph/TransOMS)**. -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. Path4GMNS documentation master file, created by 2 | sphinx-quickstart on Tue Oct 4 16:19:25 2022. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Path4GMNS's documentation! 7 | ===================================== 8 | 9 | `Path4GMNS `_ is an open-source, cross-platform, 10 | lightweight, and fast Python path engine for networks encoded in 11 | `GMNS `_. Besides finding static 12 | shortest paths for simple analyses, its main functionality is to provide an 13 | efficient and flexible framework for column-based (path-based) modeling and 14 | applications in transportation (e.g., activity-based demand modeling). 15 | Path4GMNS supports, in short, 16 | 17 | 1. finding (static) shortest path between two nodes, 18 | 2. performing path-based User-Equilibrium (UE) traffic assignment, 19 | 3. conducting dynamic traffic assignment (DTA) after UE. 20 | 4. evaluating multimodal accessibility and equity, 21 | 5. making the Origin-Destination (OD) demand matrix estimation (ODME), 22 | 6. synthesizing zones and Origin-Destination (OD) demand. 23 | 24 | Path4GMNS also serves as an API to the C++-based 25 | `DTALite `_ to conduct various multimodal 26 | traffic assignments including, 27 | 28 | * Link-based UE, 29 | * Path-based UE, 30 | * UE + DTA, 31 | * ODME. 32 | 33 | .. figure:: imgs/architecture.png 34 | 35 | 36 | Quick Start 37 | ----------- 38 | 39 | We highly recommend that you go through 40 | `this tutorial `_ 41 | written in Jupyter notebook with step-by-step demonstration using the latest 42 | version, no matter you are one of the existing users or new to Path4GMNS. 43 | 44 | 45 | Contents 46 | ======== 47 | 48 | .. toctree:: 49 | :maxdepth: 2 50 | 51 | installation 52 | usecases 53 | implnotes 54 | api 55 | updates -------------------------------------------------------------------------------- /docs/source/installation.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Installation 3 | ============ 4 | 5 | Path4GMNS has been published on `PyPI `_, and can be installed using 6 | 7 | .. code-block:: bash 8 | 9 | $ pip install path4gmns 10 | 11 | If you need a specific version of Path4GMNS, say, 0.9.10, 12 | 13 | .. code-block:: bash 14 | 15 | $ pip install path4gmns==0.9.10 16 | 17 | 18 | Dependency 19 | ---------- 20 | 21 | The Python modules are written in Python 3.x, which is the minimum requirement to explore the most of Path4GMNS. 22 | Some of its functions require further run-time support, which we will go through along with the use cases. 23 | 24 | Note that DTALite requires OpenMP as the run-time support, which would be checked during the first time import (as part 25 | of the compilation of source code to byte code). Make sure you install OpenMP before using Path4GMNS. See 26 | :ref:`OpenMP Installation ` for details. 27 | 28 | Build Path4GMNS from Source 29 | --------------------------- 30 | 31 | If you would like to test the latest features of Path4GMNS or have a compatible version to a specific operating system or an architecture, you can build the package from source and install it offline, where Python 3.x is required. 32 | 33 | **1. Build the Shared Libraries** 34 | 35 | The shared libraries of DTALite and path_engine for Path4GMNS can be built with a C++ compiler supporting C++11 and higher, where we use CMake to define the building process. Take path_engine for example, 36 | 37 | .. code-block:: bash 38 | 39 | # from the root directory of engine 40 | $ mkdir build 41 | $ cd build 42 | $ cmake .. 43 | $ cmake --build . 44 | 45 | The last command can be replaced with $ make if your target system has Make installed. See here for details on how to build DTALite. After they are successfully compiled, move them to Path4GMNS/path4gmns/bin. 46 | 47 | **Caveat** 48 | 49 | As CMAKE_BUILD_TYPE will be IGNORED for IDE (Integrated Development Environment) generators, e.g., Visual Studio and Xcode, you will need to manually update the build type from debug to release in your IDE and build your target from there. 50 | 51 | **2. Build and Install the Python Package** 52 | 53 | .. code-block:: bash 54 | 55 | # from the root directory of PATH4GMNS 56 | $ python -m pip install . -------------------------------------------------------------------------------- /docs/source/requirements.in: -------------------------------------------------------------------------------- 1 | attrs 2 | myst-parser 3 | sphinx~=4.3.0 4 | sphinx_rtd_theme -------------------------------------------------------------------------------- /docs/source/requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.11 3 | # by the following command: 4 | # 5 | # pip-compile requirements.in 6 | # 7 | alabaster==0.7.12 8 | # via sphinx 9 | attrs==23.1.0 10 | # via -r requirements.in 11 | babel==2.10.3 12 | # via sphinx 13 | certifi==2022.9.24 14 | # via requests 15 | charset-normalizer==2.1.1 16 | # via requests 17 | colorama==0.4.6 18 | # via sphinx 19 | docutils==0.17.1 20 | # via 21 | # myst-parser 22 | # sphinx 23 | # sphinx-rtd-theme 24 | idna==3.4 25 | # via requests 26 | imagesize==1.4.1 27 | # via sphinx 28 | jinja2==3.1.2 29 | # via 30 | # myst-parser 31 | # sphinx 32 | markdown-it-py==2.1.0 33 | # via 34 | # mdit-py-plugins 35 | # myst-parser 36 | markupsafe==2.1.1 37 | # via jinja2 38 | mdit-py-plugins==0.3.1 39 | # via myst-parser 40 | mdurl==0.1.2 41 | # via markdown-it-py 42 | myst-parser==0.16.0 43 | # via -r requirements.in 44 | packaging==21.3 45 | # via sphinx 46 | pygments==2.13.0 47 | # via sphinx 48 | pyparsing==3.0.9 49 | # via packaging 50 | pytz==2022.4 51 | # via babel 52 | pyyaml==6.0 53 | # via myst-parser 54 | requests==2.28.1 55 | # via sphinx 56 | snowballstemmer==2.2.0 57 | # via sphinx 58 | sphinx==4.3.0 59 | # via 60 | # -r requirements.in 61 | # myst-parser 62 | # sphinx-rtd-theme 63 | # sphinxcontrib-jquery 64 | sphinx-rtd-theme==1.3.0 65 | # via -r requirements.in 66 | sphinxcontrib-applehelp==1.0.2 67 | # via sphinx 68 | sphinxcontrib-devhelp==1.0.2 69 | # via sphinx 70 | sphinxcontrib-htmlhelp==2.0.0 71 | # via sphinx 72 | sphinxcontrib-jquery==4.1 73 | # via sphinx-rtd-theme 74 | sphinxcontrib-jsmath==1.0.1 75 | # via sphinx 76 | sphinxcontrib-qthelp==1.0.3 77 | # via sphinx 78 | sphinxcontrib-serializinghtml==1.1.5 79 | # via sphinx 80 | urllib3==1.26.12 81 | # via requests 82 | 83 | # The following packages are considered to be unsafe in a requirements file: 84 | # setuptools 85 | -------------------------------------------------------------------------------- /docs/source/updates.md: -------------------------------------------------------------------------------- 1 | # Major Updates 2 | 3 | 1. Read and output node and link geometries (v0.6.0) 4 | 2. Set up individual agents from aggregated OD demand only when it is needed (v0.6.0) 5 | 3. Provide a setting file in yaml to let users control key parameters (v0.6.0) 6 | 4. Support for multi-demand-period and multi-agent-type (v0.6.0) 7 | 5. Load columns/paths from existing runs and continue path-base UE (v0.7.0a1) 8 | 6. Download the predefined GMNS test data sets to users' local machines when needed (v0.7.0a1) 9 | 7. Add allowed use in terms of agent type (i.e., transportation mode) for links (v0.7.0a1) 10 | 8. Calculate and show up multimodal accessibility (v0.7.0a1) 11 | 9. Apply lightweight and faster implementation on accessibility evaluation using virtual centroids and connectors (v0.7.0) 12 | 10. Get accessible nodes and links given mode and time budget (v0.7.0) 13 | 11. Retrieve shortest paths under multimodal allowed uses (v0.7.2) 14 | 12. Time-dependent accessibility evaluation (v0.7.3) 15 | 13. Fix crucial bug in accessibility evaluation (v0.7.5) 16 | 14. Deprecate node_sum as hash index in column generation (v0.8.0) 17 | 15. Optimize class ColumnVec, setup_agents() in class Network, and column generation module (i.e., colgen.py) (v0.8.1) 18 | 16. Deep code optimization in column generation module with significant performance improvement (v0.8.2) 19 | 17. Let users choose which speed to use in accessibility evaluation (either the free speed of an agent specified in settings.yml or the link free flow speed defined in link.csv) (v0.8.3) 20 | 18. Transportation equity evaluation (v0.8.3) 21 | 19. Introduce special events with affected links and capacity reductions (v0.8.4) 22 | 20. Synthesize zones and demands (v0.8.5) 23 | 21. Add support for Apple Silicon (v0.8.5) 24 | 22. More robust parsing functions (v0.8.6) 25 | 23. Fix crucial bug in column generation module which will lead to wrong results if a zone has multiple nodes (v0.8.6) 26 | 24. Fix crucial bug in setting up the capacity of each VDFPeriod instance if the input is missing from link.csv (v0.8.6) 27 | 25. Add backwards compatibility on deprecated default agent type of p or passenger (v0.8.7a1) 28 | 26. Fix potential issue in setup_spnetwork() which requires zone id's are in ascending order (v0.8.7a1) 29 | 27. Fix potential issue that bin_index might not start from zero along with potential zero division issue when all zones have the same number of nodes in _synthesize_bin_index() (v0.8.7a1) 30 | 28. Enhance the tutorial with elaboration on the legacy way of loading demand and zone information and some caveats. (v0.8.7a1) 31 | 29. Calculate and print out relative gap of UE as convergency measure (v0.8.7) 32 | 30. Support the most common length and speed units. See [tutorial](https://github.com/jdlph/Path4GMNS/tree/dev/tests/tutorial.ipynb) for details (v0.8.7) 33 | 31. Introduce the simulation module along with a simple traffic simulator using the point queue model and shortest paths (v0.9.0) 34 | 32. Fully optimize the C++ routing engine (v0.9.1) 35 | 33. Use the UE result as routing decisions for simulation (v0.9.1) 36 | 34. Optimize the column generation module with faster and better UE convergency (v0.9.2) 37 | 35. Fix the bug on updating the total system travel time (v0.9.2) 38 | 36. Resolve the potential issue on traversing the last through node in path engine (v0.9.2) 39 | 37. Fix the bug on loading columns where link path and node paths are not in the proper order (v0.9.2) 40 | 38. Fix the bug on handling link capacity reduction in traffic assignment (v0.9.3) 41 | 39. Remove dependency on demand.csv for loading columns (v0.9.3) 42 | 40. Deprecate find_path_for_agents() (v0.9.3) 43 | 41. Remove beg_iteration and end_iteration from setting up a special event (v0.9.4) 44 | 42. Enhance DemandPeriod setup on time_period (v0.9.4) 45 | 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) 46 | 44. Remove memory_blocks and its implementations (which were intended for multiprocessing) (v0.9.4) 47 | 45. Bring back the postprocessing after UE in case users do not do column updating (i.e., column_upd_num = 0) (v0.9.4) 48 | 46. Drop the requirement that node id must be integer (v0.9.5) 49 | 47. Drop the requirement that zone id must be integer (v0.9.5) 50 | 48. Eliminate ultra-low-volume columns from assignment (v0.9.6) 51 | 49. Calculate and print out the final UE convergency after the postprocessing (v0.9.6) 52 | 50. Embed and support the latest [DTALite](https://github.com/asu-trans-ai-lab/DTALite) in addition to the existing [classic version](https://github.com/jdlph/DTALite) (v0.9.6) 53 | 51. Complete update 47 introduced in v0.9.5 (v0.9.7) 54 | 52. Introduce unit testing (v0.9.8) 55 | 53. Support recurring calls of run_DTALite() in terminal and fix issues regarding accessibility output (v0.9.8) 56 | 54. Introduce ODME (v0.9.9) 57 | 55. Fix bugs in loading synthetic zones and synthesizing demand (v0.9.9) 58 | 56. Separate demand loading from read_network() (v0.9.9) 59 | 57. Introduce a new public API read_demand() to unify demand and zone synthesis, and demand (and synthetic zone) loading (v0.9.9) 60 | 58. Deprecate confusing perform_column_generation() and replace it with find_ue() (v0.9.9) 61 | 59. Add CMakeFiles.txt to better manage local builds and installs (v0.9.9) 62 | 60. Reestablish cross-validation of zone id in node.csv and demand.csv (which was dropped in v0.9.9) (v0.9.9.post1) 63 | 61. Fix potential inconsistency in essential arrays (between the underlying physical network and the shortest path network (i.e., SPNetwork)) for shortest path calculation, which would lead to [OSError](https://github.com/jdlph/Path4GMNS/issues/51) (v0.9.9.post1) 64 | 62. Reinstate warning for missing pyyaml (v0.9.10) 65 | 63. Optimize the UE module by precluding zones with no valid demand and moving the check on each centroid into SPNetwork setup (v0.9.10) 66 | 64. Optimize the UE module on computing the relative gap in the postprocessing (v0.9.10) 67 | 65. Include rel_gap_tolerance as an additional argument to find_ue() and return the final relative gap (v0.9.10) 68 | 66. Fix bug on path cost unit for find_shortest_path() reported in Issue #58 (v0.9.10) 69 | 67. Find the shortest path in either travel distance or travel time (v0.9.10) 70 | 68. Adaptively display the path distance per the length unit passed to read_network() (v0.9.10) 71 | 69. Introduce get_shortest_path_tree() to return the shortest path tree from a given source node (v0.9.10) 72 | 70. Remove deprecated APIs including perform_column_generation(), perform_network_assignment(), perform_network_assignment(), and perform_network_assignment_DTALite() (v0.9.10) 73 | 74 | Detailed update information can be found in [Releases](https://github.com/jdlph/Path4GMNS/releases). -------------------------------------------------------------------------------- /engine/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | project ("path_engine") 4 | 5 | if(WIN32) 6 | message("Building dll file on Windows") 7 | elseif(UNIX) 8 | message("Building so or dylib file on ${CMAKE_SYSTEM_NAME}") 9 | endif() 10 | 11 | set(CMAKE_CXX_STANDARD 11) 12 | set(CMAKE_OSX_ARCHITECTURES "arm64") 13 | 14 | # note CMAKE_BUILD_TYPE WILL BE INGNORED for IDE generators, like Visual Studio and Xcode 15 | # it only works for single-configuration generators, e.g., make and Ninja 16 | # see https://cmake.org/cmake/help/v3.0/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_types for details 17 | set(CMAKE_BUILD_TYPE "Release") 18 | set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin) 19 | 20 | add_library(path_engine SHARED path_engine.cpp) -------------------------------------------------------------------------------- /engine/path_engine.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * The following deque implementations are inspired by the efficient implementation 3 | * from Dr. Hillel Bar-Gera 4 | * 5 | * http://www.bgu.ac.il/~bargera/tntp/ 6 | * http://www.bgu.ac.il/~bargera/tntp/FW.zip 7 | * 8 | * Similar implementations can be also found in DYNASMART system designed by Dr. Hani Mahmassani and 9 | * DTALite by Dr. Xuesong Zhou. 10 | * 11 | * shortest_path() and shortest_path_n() enhance Bar-Gera's implementation by removing its duplicate 12 | * checks on empty deque. shortest_path_n() further improves shortest_path() by making better use of 13 | * stack memory (i.e., define cur_node, deque_head, and deque_tail within the for loop), which features 14 | * THE MOST efficient deque implementation of the modified label correcting (MLC) algorithm. 15 | * 16 | * With constexpr, it is a C++ function (which requires C++11 or higher) rather than a pure C function. 17 | */ 18 | 19 | // #define SPECIAL_DEQUE 20 | 21 | #include "path_engine.h" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | using std::wcsstr; 28 | using std::wcscmp; 29 | 30 | // keep it as legacy support for other packages using this old engine 31 | void shortest_path(int orig_node, 32 | int node_size, 33 | const int* from_nodes, 34 | const int* to_nodes, 35 | const int* first_link_from, 36 | const int* last_link_from, 37 | const int* sorted_links, 38 | const wchar_t** allowed_uses, 39 | const double* link_cost, 40 | double* label_costs, 41 | int* node_preds, 42 | int* link_preds, 43 | int* deque_next, 44 | const char mode, 45 | int depart_time, 46 | int first_thru_node) 47 | { 48 | // construct and initialize the following two on the first call only 49 | static constexpr int nullnode = -1, was_in_deque = -3; 50 | 51 | for (int node_no = 0; node_no < node_size; ++node_no) 52 | { 53 | // dueue_next is the scan eligible list for active nodes in label correcting 54 | deque_next[node_no] = nullnode; 55 | label_costs[node_no] = INT_MAX; 56 | link_preds[node_no] = nullnode; 57 | node_preds[node_no] = nullnode; 58 | } 59 | 60 | int cur_node = orig_node; 61 | int deque_head = nullnode; 62 | int deque_tail = nullnode; 63 | label_costs[cur_node] = depart_time; 64 | deque_next[cur_node] = was_in_deque; 65 | 66 | // label correcting 67 | while (true) 68 | { 69 | // filter out the TAZ-based centroids 70 | if (cur_node > first_thru_node || cur_node == orig_node) 71 | { 72 | for (int k = first_link_from[cur_node]; k < last_link_from[cur_node]; ++k) 73 | { 74 | int link = sorted_links[k]; 75 | /** 76 | * if mode == 'a', we are doing static shortest path calculation using distance and 77 | * all links shall be considered; otherwise, mode shall be in link's allowed uses or 78 | * the allowed uses are for all modes (i.e., a) 79 | */ 80 | if (mode != 'a' 81 | && !wcschr(allowed_uses[link], mode) 82 | && !wcschr(allowed_uses[link], 'a')) 83 | continue; 84 | 85 | int new_node = to_nodes[link]; 86 | double new_cost = label_costs[cur_node] + link_cost[link]; 87 | if (label_costs[new_node] > new_cost) 88 | { 89 | label_costs[new_node] = new_cost; 90 | link_preds[new_node] = link; 91 | node_preds[new_node] = from_nodes[link]; 92 | 93 | /** 94 | * three cases 95 | * 96 | * case i: new_node was in deque before, add it to the begin of deque 97 | * case ii: new_node is not in deque, and wasn't there before, add it to the end of deque 98 | * case iii: new_node is in deque, do nothing 99 | */ 100 | if (deque_next[new_node] == was_in_deque) 101 | { 102 | deque_next[new_node] = deque_head; 103 | deque_head = new_node; 104 | 105 | // deque is empty, initialize it. 106 | if (deque_tail == nullnode) 107 | deque_tail = new_node; 108 | } 109 | else if (deque_next[new_node] == nullnode && new_node != deque_tail) 110 | { 111 | if (deque_tail == nullnode) 112 | { 113 | deque_head = deque_tail = new_node; 114 | deque_next[deque_tail] = nullnode; 115 | } 116 | else 117 | { 118 | deque_next[deque_tail] = new_node; 119 | deque_tail = new_node; 120 | } 121 | } 122 | } 123 | } 124 | } 125 | 126 | // deque is empty, terminate the process 127 | if (deque_head < 0) 128 | break; 129 | 130 | // get the first node out of deque and use it as the current node 131 | cur_node = deque_head; 132 | deque_head = deque_next[cur_node]; 133 | deque_next[cur_node] = was_in_deque; 134 | if (deque_tail == cur_node) 135 | deque_tail = nullnode; 136 | } 137 | } 138 | 139 | #ifndef SPECIAL_DEQUE 140 | 141 | void shortest_path_n(int orig_node, 142 | int node_size, 143 | const int* from_nodes, 144 | const int* to_nodes, 145 | const int* first_link_from, 146 | const int* last_link_from, 147 | const int* sorted_links, 148 | const wchar_t** allowed_uses, 149 | const double* link_costs, 150 | double* label_costs, 151 | int* node_preds, 152 | int* link_preds, 153 | int* deque_next, 154 | const wchar_t* mode, 155 | int max_label_cost, 156 | int last_thru_node, 157 | int depart_time) 158 | { 159 | // construct and initialize the following three on the first call only 160 | static constexpr int nullnode = -1, was_in_deque = -3; 161 | static constexpr wchar_t all_mode[] = L"all"; 162 | 163 | for (int node_no = 0; node_no < node_size; ++node_no) 164 | { 165 | // dueue_next is the scan eligible list for active nodes in label correcting 166 | deque_next[node_no] = nullnode; 167 | label_costs[node_no] = max_label_cost; 168 | link_preds[node_no] = nullnode; 169 | node_preds[node_no] = nullnode; 170 | } 171 | 172 | label_costs[orig_node] = depart_time; 173 | deque_next[orig_node] = was_in_deque; 174 | 175 | // label correcting 176 | for (int cur_node = orig_node, deque_head = nullnode, deque_tail = nullnode;;) 177 | { 178 | // filter out the TAZ-based centroids 179 | if (cur_node < last_thru_node || cur_node == orig_node) 180 | { 181 | for (int k = first_link_from[cur_node]; k < last_link_from[cur_node]; ++k) 182 | { 183 | int link = sorted_links[k]; 184 | /** 185 | * if mode == 'a', we are doing static shortest path calculation using distance and 186 | * all links shall be considered; otherwise, mode shall be in link's allowed uses or 187 | * the allowed uses are for all modes (i.e., a) 188 | */ 189 | if (wcscmp(mode, all_mode) != 0 190 | && !wcsstr(allowed_uses[link], mode) 191 | && !wcsstr(allowed_uses[link], all_mode)) 192 | continue; 193 | 194 | int new_node = to_nodes[link]; 195 | double new_cost = label_costs[cur_node] + link_costs[link]; 196 | 197 | if (label_costs[new_node] > new_cost) 198 | { 199 | label_costs[new_node] = new_cost; 200 | link_preds[new_node] = link; 201 | node_preds[new_node] = from_nodes[link]; 202 | 203 | /** 204 | * three cases 205 | * 206 | * case i: new_node was in deque before, add it to the begin of deque 207 | * case ii: new_node is not in deque, and wasn't there before, add it to the end of deque 208 | * case iii: new_node is in deque, do nothing 209 | */ 210 | if (deque_next[new_node] == was_in_deque) 211 | { 212 | deque_next[new_node] = deque_head; 213 | deque_head = new_node; 214 | 215 | // deque is empty, initialize it. 216 | if (deque_tail == nullnode) 217 | deque_tail = new_node; 218 | } 219 | else if (deque_next[new_node] == nullnode && new_node != deque_tail) 220 | { 221 | if (deque_tail == nullnode) 222 | { 223 | deque_head = deque_tail = new_node; 224 | deque_next[deque_tail] = nullnode; 225 | } 226 | else 227 | { 228 | deque_next[deque_tail] = new_node; 229 | deque_tail = new_node; 230 | } 231 | } 232 | } 233 | } 234 | } 235 | 236 | // deque is empty, terminate the process 237 | if (deque_head < 0) 238 | break; 239 | 240 | // get the first node out of deque and use it as the current node 241 | cur_node = deque_head; 242 | deque_head = deque_next[cur_node]; 243 | deque_next[cur_node] = was_in_deque; 244 | if (deque_tail == cur_node) 245 | deque_tail = nullnode; 246 | } 247 | } 248 | 249 | #else 250 | 251 | /** 252 | * @brief a special deque for the deque implementation of the MLC algorithm only 253 | * 254 | * Its implementation is still naive without any exception handlings. Caller is 255 | * responsible for creating an instance and updating it with proper argument(s) 256 | * passing to its constructor and interfaces (i.e., sz > 0, i >= 0). 257 | * 258 | * The full-fledged version will be implemented as part of the new DTALite. 259 | */ 260 | class SpecialDeque { 261 | public: 262 | SpecialDeque() = delete; 263 | 264 | explicit SpecialDeque(int sz) : nodes {new int[sz]} 265 | { 266 | for (int i = 0; i != sz; ++i) 267 | nodes[i] = nullnode; 268 | } 269 | 270 | SpecialDeque(int sz, int i) : SpecialDeque {sz} 271 | { 272 | push_back(i); 273 | } 274 | 275 | SpecialDeque(const SpecialDeque&) = delete; 276 | SpecialDeque& operator=(const SpecialDeque&) = delete; 277 | 278 | SpecialDeque(const SpecialDeque&&) = delete; 279 | SpecialDeque& operator=(const SpecialDeque&&) = delete; 280 | 281 | ~SpecialDeque() 282 | { 283 | delete[] nodes; 284 | } 285 | 286 | /** 287 | * @brief head can never be pastnode for the deque implementation of MLC 288 | * 289 | * It can be easily proved using contradiction. Therefore, the additional 290 | * check in the original implementation from Dr. Hillel Bar-Gera on 291 | * head == pastnode is NOT necessary. 292 | */ 293 | bool empty() const 294 | { 295 | return head == nullnode; 296 | } 297 | 298 | bool new_node(int i) const 299 | { 300 | return nodes[i] == nullnode && i != tail; 301 | } 302 | 303 | bool past_node(int i) const 304 | { 305 | return nodes[i] == pastnode; 306 | } 307 | 308 | void push_front(int i) 309 | { 310 | nodes[i] = head; 311 | head = i; 312 | 313 | if (head == nullnode) 314 | tail = i; 315 | } 316 | 317 | void push_back(int i) 318 | { 319 | if (head == nullnode) 320 | { 321 | head = tail = i; 322 | nodes[i] = nullnode; 323 | } 324 | else 325 | { 326 | nodes[tail] = i; 327 | nodes[i] = nullnode; 328 | tail = i; 329 | } 330 | } 331 | 332 | int pop_front() 333 | { 334 | int left = head; 335 | head = nodes[left]; 336 | nodes[left] = pastnode; 337 | return left; 338 | } 339 | 340 | private: 341 | static constexpr int nullnode = -1; 342 | static constexpr int pastnode = -3; 343 | int head = nullnode; 344 | int tail = nullnode; 345 | int* nodes; 346 | }; 347 | 348 | void shortest_path_n(int orig_node, 349 | int node_size, 350 | const int* from_nodes, 351 | const int* to_nodes, 352 | const int* first_link_from, 353 | const int* last_link_from, 354 | const int* sorted_links, 355 | const wchar_t** allowed_uses, 356 | const double* link_costs, 357 | double* label_costs, 358 | int* node_preds, 359 | int* link_preds, 360 | int* deque_next, 361 | const wchar_t* mode, 362 | int max_label_cost, 363 | int last_thru_node, 364 | int depart_time) 365 | { 366 | // construct and initialize the following one on the first call only 367 | static constexpr wchar_t all_mode[] = L"all"; 368 | 369 | for (int node_no = 0; node_no < node_size; ++node_no) 370 | { 371 | label_costs[node_no] = max_label_cost; 372 | link_preds[node_no] = -1; 373 | node_preds[node_no] = -1; 374 | } 375 | 376 | label_costs[orig_node] = depart_time; 377 | 378 | // label correcting 379 | for (SpecialDeque deq{node_size, orig_node}; !deq.empty();) 380 | { 381 | int cur_node = deq.pop_front(); 382 | // filter out the TAZ-based centroids 383 | if (cur_node >= last_thru_node && cur_node != orig_node) 384 | continue; 385 | 386 | for (int k = first_link_from[cur_node]; k < last_link_from[cur_node]; ++k) 387 | { 388 | int link = sorted_links[k]; 389 | /** 390 | * if mode == 'a', we are doing static shortest path calculation using distance and 391 | * all links shall be considered; otherwise, mode shall be in link's allowed uses or 392 | * the allowed uses are for all modes (i.e., a) 393 | */ 394 | if (wcscmp(mode, all_mode) != 0 395 | && !wcsstr(allowed_uses[link], mode) 396 | && !wcsstr(allowed_uses[link], all_mode)) 397 | continue; 398 | 399 | int new_node = to_nodes[link]; 400 | double new_cost = label_costs[cur_node] + link_costs[link]; 401 | 402 | if (label_costs[new_node] > new_cost) 403 | { 404 | label_costs[new_node] = new_cost; 405 | link_preds[new_node] = link; 406 | node_preds[new_node] = from_nodes[link]; 407 | /** 408 | * three cases 409 | * 410 | * case i: new_node was in deque before, add it to the begin of deque 411 | * case ii: new_node is not in deque, and wasn't there before, add it to the end of deque 412 | * case iii: new_node is in deque, do nothing 413 | */ 414 | if (deq.past_node(new_node)) 415 | deq.push_front(new_node); 416 | else if (deq.new_node(new_node)) 417 | deq.push_back(new_node); 418 | } 419 | } 420 | } 421 | } 422 | 423 | #endif -------------------------------------------------------------------------------- /engine/path_engine.h: -------------------------------------------------------------------------------- 1 | #ifndef GUARD_PATH_ENGINE_H 2 | #define GUARD_PATH_ENGINE_H 3 | 4 | #ifdef _WIN32 5 | #define PATH_ENGINE_API __declspec(dllexport) 6 | #else 7 | #define PATH_ENGINE_API 8 | #endif 9 | 10 | extern "C" PATH_ENGINE_API void shortest_path(const int orig_node, 11 | const int node_size, 12 | const int* from_nodes, 13 | const int* to_nodes, 14 | const int* first_link_from, 15 | const int* last_link_from, 16 | const int* sorted_links, 17 | const wchar_t** allowed_uses, 18 | const double* link_costs, 19 | double* label_costs, 20 | int* node_preds, 21 | int* link_preds, 22 | int* deque_next, 23 | const char mode = 'a', 24 | int depart_time = 0, 25 | int first_thru_node = 0); 26 | 27 | extern "C" PATH_ENGINE_API void shortest_path_n(int orig_node, 28 | int node_size, 29 | const int* from_nodes, 30 | const int* to_nodes, 31 | const int* first_link_from, 32 | const int* last_link_from, 33 | const int* sorted_links, 34 | const wchar_t** allowed_uses, 35 | const double* link_costs, 36 | double* label_costs, 37 | int* node_preds, 38 | int* link_preds, 39 | int* deque_next, 40 | const wchar_t* mode, 41 | int max_label_cost, 42 | int last_thru_node, 43 | int depart_time = 0); 44 | 45 | #endif -------------------------------------------------------------------------------- /path4gmns/__init__.py: -------------------------------------------------------------------------------- 1 | from .accessibility import * 2 | from .colgen import * 3 | from .dtaapi import * 4 | from .io import * 5 | from .odme import * 6 | from .simulation import * 7 | from .utils import * 8 | from .zonesyn import * 9 | 10 | 11 | __version__ = '0.9.10' 12 | 13 | 14 | # print out the current version 15 | print(f'path4gmns, version {__version__}\n') -------------------------------------------------------------------------------- /path4gmns/accessibility.py: -------------------------------------------------------------------------------- 1 | import os 2 | import csv 3 | import threading 4 | 5 | from .classes import AccessNetwork 6 | from .path import single_source_shortest_path 7 | from .consts import MAX_LABEL_COST, MIN_TIME_BUDGET, \ 8 | BUDGET_TIME_INTVL, MAX_TIME_BUDGET 9 | 10 | 11 | __all__ = ['evaluate_accessibility', 'evaluate_equity'] 12 | 13 | 14 | def _get_interval_id(t): 15 | """ return interval id in predefined time budget intervals 16 | 17 | [0, MIN_TIME_BUDGET], 18 | 19 | (MIN_TIME_BUDGET + (i-1)*BUDGET_TIME_INTVL, MIN_TIME_BUDGET + i*BUDGET_TIME_INTVL] 20 | where, i is integer and i >= 1 21 | """ 22 | if t < MIN_TIME_BUDGET: 23 | return 0 24 | 25 | if ((t-MIN_TIME_BUDGET) % BUDGET_TIME_INTVL) == 0: 26 | return int((t-MIN_TIME_BUDGET) / BUDGET_TIME_INTVL) 27 | 28 | return int((t-MIN_TIME_BUDGET) / BUDGET_TIME_INTVL) + 1 29 | 30 | 31 | def _update_min_travel_time(an, at, min_travel_times, time_dependent, demand_period_id): 32 | an.update_generalized_link_cost(at, time_dependent, demand_period_id) 33 | 34 | at_str = at.get_type_str() 35 | max_min = 0 36 | for c in an.get_centroids(): 37 | node_id = c.get_node_id() 38 | zone_id = c.get_zone_id() 39 | single_source_shortest_path(an, node_id) 40 | for c_ in an.get_centroids(): 41 | if c_ == c: 42 | continue 43 | 44 | node_no = c_.get_node_no() 45 | to_zone_id = c_.get_zone_id() 46 | min_tt = an.get_node_label_cost(node_no) 47 | # this function will dramatically slow down the whole process 48 | min_dist = an.get_sp_distance(node_no) 49 | min_travel_times[(zone_id, to_zone_id, at_str)] = min_tt, min_dist 50 | 51 | if min_tt < MAX_LABEL_COST and max_min < min_tt: 52 | max_min = min_tt 53 | 54 | return max_min 55 | 56 | 57 | def _output_od_accessibility(min_travel_times, zones, mode, output_dir): 58 | """ output accessibility for each OD pair (i.e., travel time) """ 59 | with open(output_dir+'/od_accessibility.csv', 'w', newline='') as f: 60 | headers = ['o_zone_id', 'd_zone_id', 'accessibility', 'distance', 'geometry'] 61 | 62 | writer = csv.writer(f) 63 | writer.writerow(headers) 64 | 65 | # for multimodal case, find the minimum travel time 66 | # under mode 'a' (i.e., auto) 67 | for k, v in min_travel_times.items(): 68 | # k = (from_zone_id, to_zone_id, at_type_str) 69 | if k[2] != mode: 70 | continue 71 | 72 | # output accessibility 73 | # no exception handlings here as min_travel_times is constructed 74 | # directly using an.get_centroids() 75 | coord_oz = zones[k[0]].get_coordinate_str() 76 | coord_dz = zones[k[1]].get_coordinate_str() 77 | geo = 'LINESTRING ()' 78 | if coord_oz and coord_dz: 79 | geo = 'LINESTRING (' + coord_oz + ', ' + coord_dz + ')' 80 | 81 | tt = v[0] 82 | dis = v[1] 83 | if tt >= MAX_LABEL_COST: 84 | tt = 'N/A' 85 | dis = 'N/A' 86 | 87 | line = [k[0], k[1], tt, dis, geo] 88 | writer.writerow(line) 89 | 90 | if output_dir == '.': 91 | print(f'check od_accessibility.csv in {os.getcwd()} for OD accessibility') 92 | else: 93 | print( 94 | f'check od_accessibility.csv in {os.path.join(os.getcwd(), output_dir)}' 95 | ' for OD accessibility' 96 | ) 97 | 98 | 99 | def _output_zone_accessibility(min_travel_times, interval_num, 100 | zones, ats, output_dir): 101 | """ output zone accessibility matrix for each agent type """ 102 | 103 | with open(output_dir+'/zone_accessibility.csv', 'w', newline='') as f: 104 | time_budgets = [ 105 | 'TT_'+str(MIN_TIME_BUDGET+BUDGET_TIME_INTVL*i) for i in range(interval_num) 106 | ] 107 | 108 | headers = ['zone_id', 'geometry', 'mode'] 109 | headers.extend(time_budgets) 110 | 111 | writer = csv.writer(f) 112 | writer.writerow(headers) 113 | 114 | # calculate accessibility 115 | for oz, v in zones.items(): 116 | if not oz: 117 | continue 118 | 119 | for at in ats: 120 | at_str = at.get_type_str() 121 | # number of accessible zones from oz for each agent type 122 | counts = [0] * interval_num 123 | for dz in zones: 124 | if (oz, dz, at_str) not in min_travel_times: 125 | continue 126 | 127 | min_tt = min_travel_times[(oz, dz, at_str)][0] 128 | if min_tt >= MAX_LABEL_COST: 129 | continue 130 | 131 | id = _get_interval_id(min_tt) 132 | while id < interval_num: 133 | counts[id] += 1 134 | id += 1 135 | # output accessibility 136 | 137 | # output the zone coordinates rather than the boundaries for the 138 | # following two reasons: 139 | # 1. to be consistent with _output_od_accessibility() 140 | # 2. v.get_geo() is always empty as no boundary info is provided 141 | # in node.csv 142 | geo = 'LINESTRING ()' 143 | coord = v.get_coordinate_str() 144 | if coord: 145 | geo = 'LINESTRING (' + coord + ')' 146 | 147 | line = [oz, geo, at.get_type_str()] 148 | line.extend(counts) 149 | writer.writerow(line) 150 | 151 | if output_dir == '.': 152 | print(f'check zone_accessibility.csv in {os.getcwd()} for zone accessibility') 153 | else: 154 | print( 155 | f'check zone_accessibility.csv in {os.path.join(os.getcwd(), output_dir)}' 156 | ' for zone accessibility' 157 | ) 158 | 159 | 160 | def _output_equity(output_dir, time_budget, equity_metrics, equity_zones): 161 | with open(output_dir+'/equity_'+str(time_budget)+'min.csv', 'w', newline='') as f: 162 | headers = ['bin_index', 'mode', 'zones', 163 | 'min_accessibility', 'zone_id', 164 | 'max_accessibility', 'zone_id', 165 | 'mean_accessibility'] 166 | writer = csv.writer(f) 167 | writer.writerow(headers) 168 | 169 | for k, v in sorted(equity_metrics.items()): 170 | try: 171 | avg = round(v[4] / len(equity_zones[k]), 2) 172 | zones = ', '.join(str(x) for x in equity_zones[k]) 173 | line = [k[0], k[1], zones, v[0], v[1], v[2], v[3], avg] 174 | except ZeroDivisionError: 175 | continue 176 | 177 | writer.writerow(line) 178 | 179 | if output_dir == '.': 180 | print( 181 | f'\ncheck equity_{time_budget} min.csv in {os.getcwd()} for equity evaluation') 182 | else: 183 | print( 184 | f'\ncheck equity_{time_budget} min.csv in {os.path.join(os.getcwd(), output_dir)}' 185 | ' for equity evaluation') 186 | 187 | 188 | def evaluate_accessibility(ui, 189 | single_mode=False, 190 | mode='auto', 191 | time_dependent=False, 192 | demand_period_id=0, 193 | output_dir='.'): 194 | """ perform accessibility evaluation for a target mode or more 195 | 196 | Parameters 197 | ---------- 198 | ui 199 | network object generated by pg.read_network() 200 | 201 | single_mode 202 | True or False. Its default value is False. It will only affect the 203 | output to zone_accessibility.csv. 204 | 205 | If False, the accessibility evaluation will be conducted 206 | for all the modes defined in settings.yml. The number of accessible 207 | zones from each zone under each defined mode given a budget time (up 208 | to 240 minutes) will be outputted to zone_accessibility.csv. 209 | 210 | If True, the accessibility evaluation will be only conducted against the 211 | target mode. The number of accessible zones from each zone under the 212 | target mode given a budget time (up to 240 minutes) will be outputted 213 | to zone_accessibility.csv. 214 | 215 | mode 216 | target mode with its default value as 'auto'. It can be 217 | either agent type or its name. For example, 'w' and 'walk' are 218 | equivalent inputs. 219 | 220 | time_dependent 221 | True or False. Its default value is False. 222 | 223 | If True, the accessibility will be evaluated using the period link 224 | free-flow travel time (i.e., VDF_fftt). In other words, the 225 | accessibility is time-dependent. 226 | 227 | If False, the accessibility will be evaluated using the link length and 228 | the free flow travel speed of each mode. 229 | 230 | demand_period_id 231 | The sequence number of demand period listed in demand_periods in 232 | settings.yml. demand_period_id of the first demand_period is 0. 233 | 234 | Use it with time_dependent when there are multiple demand periods. Its 235 | default value is 0. 236 | 237 | output_dir 238 | The directory path where zone_accessibility.csv and od_accessibility.csv 239 | are output. The default is the current working directory (CDW). 240 | 241 | Returns 242 | ------- 243 | None 244 | 245 | Note 246 | ---- 247 | The following files will be output. 248 | 249 | zone_accessibility.csv 250 | accessibility as the number of accessible zones from each 251 | zone for a target mode or any mode defined in settings.yml given a 252 | budget time (up to 240 minutes). 253 | 254 | od_accessibility.csv: 255 | accessibility between each OD pair in terms of free flow travel time. 256 | """ 257 | base = ui._base_assignment 258 | an = AccessNetwork(base.network) 259 | ats = None 260 | 261 | zones = base.network.zones 262 | 263 | max_min = 0 264 | min_travel_times = {} 265 | at_name, at_str = base._convert_mode(mode) 266 | if not single_mode: 267 | ats = base.get_agent_types() 268 | for at in ats: 269 | an.set_target_mode(at.get_name()) 270 | max_min_ = _update_min_travel_time(an, 271 | at, 272 | min_travel_times, 273 | time_dependent, 274 | demand_period_id) 275 | if max_min_ > max_min: 276 | max_min = max_min_ 277 | else: 278 | an.set_target_mode(at_name) 279 | at = base.get_agent_type(at_str) 280 | max_min = _update_min_travel_time(an, 281 | at, 282 | min_travel_times, 283 | time_dependent, 284 | demand_period_id) 285 | ats = [at] 286 | 287 | interval_num = _get_interval_id(min(max_min, MAX_TIME_BUDGET)) + 1 288 | 289 | # multithreading to reduce output time 290 | t = threading.Thread( 291 | target=_output_od_accessibility, 292 | args=(min_travel_times, zones, at_str, output_dir) 293 | ) 294 | t.start() 295 | 296 | t = threading.Thread( 297 | target=_output_zone_accessibility, 298 | args=(min_travel_times, interval_num, zones, ats, output_dir) 299 | ) 300 | t.start() 301 | 302 | 303 | def evaluate_equity(ui, single_mode=False, mode='auto', time_dependent=False, 304 | demand_period_id=0, time_budget=60, output_dir='.'): 305 | """ evaluate equity for each zone under a time budget 306 | 307 | Parameters 308 | ---------- 309 | ui 310 | network object generated by pg.read_network() 311 | 312 | single_mode 313 | True or False. Its default value is False. It will only affect the 314 | output to zone_accessibility.csv. 315 | 316 | If False, the equity evaluation will be conducted for all the modes defined 317 | in settings.yml. 318 | 319 | If True, the equity evaluation will be only conducted against the 320 | target mode. 321 | 322 | mode 323 | target mode with its default value as 'auto'. It can be 324 | either agent type or its name. For example, 'w' and 'walk' are 325 | equivalent inputs. 326 | 327 | time_dependent 328 | True or False. Its default value is False. 329 | 330 | If True, the accessibility will be evaluated using the period link 331 | free-flow travel time (i.e., VDF_fftt). In other words, the 332 | accessibility is time-dependent. 333 | 334 | If False, the accessibility will be evaluated using the link length and 335 | the free flow travel speed of each mode. 336 | 337 | demand_period_id 338 | The sequence number of demand period listed in demand_periods in 339 | settings.yml. demand_period_id of the first demand_period is 0. 340 | 341 | Use it with time_dependent when there are multiple demand periods. Its 342 | default value is 0. 343 | 344 | time_budget 345 | the amount of time to travel in minutes 346 | 347 | output_dir 348 | The directory path where the evaluation result is output. The default 349 | is the current working directory (CDW). 350 | 351 | Returns 352 | ------- 353 | None 354 | 355 | Note 356 | ---- 357 | The following file will be output. 358 | 359 | equity_str.csv 360 | equity statistics including minimum accessibility (and the corresponding 361 | zone), maximum accessibility (and the corresponding zone), and mean 362 | accessibility for each bin_index. The accessible zones will be output 363 | as well. 364 | 365 | str in the file name refers to the time budget. For example, the file 366 | name will be equity_60min.csv if the time budget is 60 min. 367 | """ 368 | base = ui._base_assignment 369 | an = AccessNetwork(base.network) 370 | zones = an.base.zones 371 | ats = None 372 | 373 | min_travel_times = {} 374 | equity_metrics = {} 375 | equity_zones = {} 376 | 377 | if not single_mode: 378 | ats = base.get_agent_types() 379 | for at in ats: 380 | an.set_target_mode(at.get_name()) 381 | _update_min_travel_time(an, 382 | at, 383 | min_travel_times, 384 | time_dependent, 385 | demand_period_id) 386 | else: 387 | at_name, at_str = base._convert_mode(mode) 388 | an.set_target_mode(at_name) 389 | at = base.get_agent_type(at_str) 390 | _update_min_travel_time(an, 391 | at, 392 | min_travel_times, 393 | time_dependent, 394 | demand_period_id) 395 | ats = [at] 396 | 397 | # v is zone object 398 | for oz, v in zones.items(): 399 | if not oz: 400 | continue 401 | 402 | bin_index = v.get_bin_index() 403 | for at in ats: 404 | at_str = at.get_type_str() 405 | 406 | count = 0 407 | for dz in zones: 408 | if (oz, dz, at_str) not in min_travel_times: 409 | continue 410 | 411 | min_tt = min_travel_times[(oz, dz, at_str)][0] 412 | if min_tt > time_budget: 413 | continue 414 | 415 | count += 1 416 | 417 | if (bin_index, at_str) not in equity_metrics: 418 | equity_metrics[(bin_index, at_str)] = [count, oz, count, oz, 0] 419 | equity_zones[(bin_index, at_str)] = [] 420 | equity_zones[(bin_index, at_str)].append(oz) 421 | 422 | # 0: min_accessibility, 1: zone_id, 2: max_accessibility, 423 | # 3: zone_id, 4: cumulative count, 424 | # where 0 to 4 are indices of each element of equity_metrics. 425 | if count < equity_metrics[(bin_index, at_str)][0]: 426 | equity_metrics[(bin_index, at_str)][0] = count 427 | equity_metrics[(bin_index, at_str)][1] = oz 428 | elif count > equity_metrics[(bin_index, at_str)][2]: 429 | equity_metrics[(bin_index, at_str)][2] = count 430 | equity_metrics[(bin_index, at_str)][3] = oz 431 | 432 | equity_metrics[(bin_index, at_str)][4] += count 433 | 434 | _output_equity(output_dir, time_budget, equity_metrics, equity_zones) -------------------------------------------------------------------------------- /path4gmns/bin/DTALite.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdlph/Path4GMNS/e9ed456cbaa72da41b5849e7aacce553d10fc3e1/path4gmns/bin/DTALite.dll -------------------------------------------------------------------------------- /path4gmns/bin/DTALite.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdlph/Path4GMNS/e9ed456cbaa72da41b5849e7aacce553d10fc3e1/path4gmns/bin/DTALite.so -------------------------------------------------------------------------------- /path4gmns/bin/DTALiteMM.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdlph/Path4GMNS/e9ed456cbaa72da41b5849e7aacce553d10fc3e1/path4gmns/bin/DTALiteMM.dll -------------------------------------------------------------------------------- /path4gmns/bin/DTALiteMM.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdlph/Path4GMNS/e9ed456cbaa72da41b5849e7aacce553d10fc3e1/path4gmns/bin/DTALiteMM.so -------------------------------------------------------------------------------- /path4gmns/bin/DTALiteMM_arm.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdlph/Path4GMNS/e9ed456cbaa72da41b5849e7aacce553d10fc3e1/path4gmns/bin/DTALiteMM_arm.dylib -------------------------------------------------------------------------------- /path4gmns/bin/DTALiteMM_x86.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdlph/Path4GMNS/e9ed456cbaa72da41b5849e7aacce553d10fc3e1/path4gmns/bin/DTALiteMM_x86.dylib -------------------------------------------------------------------------------- /path4gmns/bin/DTALite_arm.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdlph/Path4GMNS/e9ed456cbaa72da41b5849e7aacce553d10fc3e1/path4gmns/bin/DTALite_arm.dylib -------------------------------------------------------------------------------- /path4gmns/bin/DTALite_x86.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdlph/Path4GMNS/e9ed456cbaa72da41b5849e7aacce553d10fc3e1/path4gmns/bin/DTALite_x86.dylib -------------------------------------------------------------------------------- /path4gmns/bin/path_engine.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdlph/Path4GMNS/e9ed456cbaa72da41b5849e7aacce553d10fc3e1/path4gmns/bin/path_engine.dll -------------------------------------------------------------------------------- /path4gmns/bin/path_engine.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdlph/Path4GMNS/e9ed456cbaa72da41b5849e7aacce553d10fc3e1/path4gmns/bin/path_engine.so -------------------------------------------------------------------------------- /path4gmns/bin/path_engine_arm.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdlph/Path4GMNS/e9ed456cbaa72da41b5849e7aacce553d10fc3e1/path4gmns/bin/path_engine_arm.dylib -------------------------------------------------------------------------------- /path4gmns/bin/path_engine_x86.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdlph/Path4GMNS/e9ed456cbaa72da41b5849e7aacce553d10fc3e1/path4gmns/bin/path_engine_x86.dylib -------------------------------------------------------------------------------- /path4gmns/colgen.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | 3 | from .path import single_source_shortest_path 4 | from .classes import Column 5 | from .consts import EPSILON, MAX_LABEL_COST, MIN_COL_VOL 6 | 7 | 8 | __all__ = ['find_ue'] 9 | 10 | 11 | def _update_link_cost_array(spnetworks): 12 | """ update generalized link costs for each SPNetwork """ 13 | for sp in spnetworks: 14 | tau = sp.get_demand_period().get_id() 15 | vot = sp.get_agent_type().get_vot() 16 | 17 | for link in sp.get_links(): 18 | if not link.length: 19 | break 20 | 21 | sp.link_cost_array[link.get_seq_no()] = ( 22 | link.get_generalized_cost(tau, vot) 23 | ) 24 | 25 | 26 | def _update_link_travel_time(links): 27 | for link in links: 28 | if not link.length: 29 | break 30 | 31 | link.calculate_td_vdf() 32 | 33 | 34 | def _update_link_and_column_volume(column_pool, links, iter_num, reduce_path_vol = True): 35 | if not iter_num: 36 | return 37 | 38 | for link in links: 39 | if not link.length: 40 | break 41 | 42 | link.reset_period_flow_vol() 43 | 44 | for k, cv in column_pool.items(): 45 | # k= (at, tau, oz_id, dz_id) 46 | tau = k[1] 47 | 48 | for col in cv.get_columns(): 49 | path_vol = col.get_volume() 50 | for i in col.links: 51 | # 04/10/22, remove it from now on as it is not in need 52 | # pce_ratio = 1 and path_vol * pce_ratio 53 | links[i].increase_period_flow_vol(tau, path_vol) 54 | 55 | # 06/11/24: if we check cv.is_route_fixed() here, 56 | # 1. shall we check it anywhere else?? 57 | # 2. is it really necessary? 58 | # if reduce_path_vol and not cv.is_route_fixed(): 59 | if reduce_path_vol: 60 | col.vol *= iter_num / (iter_num + 1) 61 | 62 | 63 | def _update_column_gradient_cost_and_flow(column_pool, links, agent_types, iter_num): 64 | total_gap = 0 65 | total_sys_travel_time = 0 66 | 67 | for k, cv in column_pool.items(): 68 | # k = (at, tau, oz_id, dz_id) 69 | vot = agent_types[k[0]].get_vot() 70 | tau = k[1] 71 | 72 | least_gradient_cost = MAX_LABEL_COST 73 | least_gradient_cost_path_id = -1 74 | 75 | for col in cv.get_columns(): 76 | # i is link sequence no 77 | path_gradient_cost = sum( 78 | links[i].get_generalized_cost(tau, vot) for i in col.links 79 | ) 80 | col.set_gradient_cost(path_gradient_cost) 81 | 82 | # col.get_volume() >= EPSILON is the key to eliminate ultra-low-volume 83 | # columns. along with the new_vol reset below if new_vol < MIN_COL_VOL, 84 | # it enables shifting volume from the shortest path to a non-shortest path. 85 | if path_gradient_cost < least_gradient_cost and col.get_volume() >= EPSILON: 86 | least_gradient_cost = path_gradient_cost 87 | least_gradient_cost_path_id = col.get_id() 88 | 89 | total_switched_out_path_vol = 0 90 | if cv.get_column_num() > 1: 91 | for col in cv.get_columns(): 92 | if col.get_id() == least_gradient_cost_path_id: 93 | continue 94 | 95 | if not col.get_volume(): 96 | continue 97 | 98 | col.update_gradient_cost_diffs(least_gradient_cost) 99 | 100 | # with the forgoing col.get_volume() >= EPSILON, 101 | # col.get_cap() could be negative 102 | total_gap += col.get_gap() 103 | total_sys_travel_time += col.get_sys_travel_time() 104 | 105 | step_size = 1 / (iter_num + 2) * cv.get_od_volume() 106 | cur_vol = col.get_volume() 107 | 108 | new_vol = cur_vol - step_size * col.get_gradient_cost_rel_diff() 109 | if new_vol < MIN_COL_VOL: 110 | new_vol = 0 111 | 112 | col.set_volume(new_vol) 113 | total_switched_out_path_vol += cur_vol - new_vol 114 | 115 | if least_gradient_cost_path_id != -1: 116 | col = cv.get_column(least_gradient_cost_path_id) 117 | total_sys_travel_time += col.get_sys_travel_time() 118 | col.increase_volume(total_switched_out_path_vol) 119 | 120 | rel_gap = total_gap / max(total_sys_travel_time, EPSILON) 121 | print(f'current iteration number in column update: {iter_num}\n' 122 | f'total gap: {total_gap:.4e}; relative gap: {rel_gap:.4%}') 123 | 124 | return rel_gap 125 | 126 | 127 | def _backtrace_shortest_path_tree(centroid, 128 | centroids, 129 | links, 130 | node_preds, 131 | link_preds, 132 | at_id, 133 | dp_id, 134 | column_pool, 135 | iter_num): 136 | 137 | oz_id = centroid.get_zone_id() 138 | k_path_prob = 1 / (iter_num + 1) 139 | 140 | for c in centroids: 141 | dz_id = c.get_zone_id() 142 | if dz_id == oz_id: 143 | continue 144 | 145 | if (at_id, dp_id, oz_id, dz_id) not in column_pool: 146 | continue 147 | 148 | cv = column_pool[(at_id, dp_id, oz_id, dz_id)] 149 | # disable it as it is always false 150 | # if cv.is_route_fixed(): 151 | # continue 152 | 153 | link_path = [] 154 | dist = 0 155 | 156 | curr_node_no = c.get_node_no() 157 | # retrieve the sequence backwards 158 | while curr_node_no >= 0: 159 | curr_link_no = link_preds[curr_node_no] 160 | if curr_link_no >= 0: 161 | link_path.append(curr_link_no) 162 | dist += links[curr_link_no].length 163 | 164 | curr_node_no = node_preds[curr_node_no] 165 | 166 | # make sure this is a valid path 167 | if not link_path: 168 | continue 169 | 170 | vol = k_path_prob * cv.get_od_volume() 171 | 172 | existing = False 173 | for col in cv.get_columns(): 174 | if col.get_distance() != dist: 175 | continue 176 | 177 | # the first and the last are connectors 178 | if col.get_links() == link_path[1:-1]: 179 | col.increase_volume(vol) 180 | existing = True 181 | break 182 | 183 | if not existing: 184 | path_id = cv.get_column_num() 185 | col = Column(path_id) 186 | col.set_volume(vol) 187 | col.set_distance(dist) 188 | col.links = [x for x in link_path[1:-1]] 189 | cv.add_new_column(col) 190 | 191 | 192 | def _update_column_attributes(column_pool, links, agent_types, spnetworks): 193 | """ update toll and travel time for each column, and compute final UE convergency """ 194 | total_gap = 0 195 | total_sys_travel_time = 0 196 | total_min_sys_travel_time = 0 197 | 198 | for k, cv in column_pool.items(): 199 | # k = (at, dp, oz, dz) 200 | dp = k[1] 201 | vot = agent_types[k[0]].get_vot() 202 | 203 | least_gradient_cost = MAX_LABEL_COST 204 | 205 | i = 0 206 | for col in cv.get_columns(): 207 | if not col.get_volume(): 208 | del col 209 | continue 210 | 211 | # reset column id 212 | col.set_id(i) 213 | i += 1 214 | 215 | nodes = [] 216 | path_toll = 0 217 | travel_time = 0 218 | 219 | path_gradient_cost = 0 220 | for j in col.links: 221 | link = links[j] 222 | nodes.append(link.to_node_no) 223 | travel_time += link.travel_time_by_period[dp] 224 | path_toll += link.get_toll() 225 | path_gradient_cost += link.get_generalized_cost(dp, vot) 226 | 227 | # last node 228 | nodes.append(links[col.links[-1]].from_node_no) 229 | 230 | col.set_gradient_cost(path_gradient_cost) 231 | col.set_travel_time(travel_time) 232 | col.set_toll(path_toll) 233 | col.nodes = [x for x in nodes] 234 | 235 | if path_gradient_cost < least_gradient_cost: 236 | least_gradient_cost = path_gradient_cost 237 | 238 | total_min_sys_travel_time += least_gradient_cost * cv.get_od_volume() 239 | 240 | # an equivalent but more efficient way to compute the total system travel 241 | # time when the column pool is large in size. 242 | for sp in spnetworks: 243 | tau = sp.get_demand_period().get_id() 244 | vot = sp.get_agent_type().get_vot() 245 | 246 | for link in sp.get_links(): 247 | if not link.length: 248 | break 249 | 250 | total_sys_travel_time += ( 251 | link.get_generalized_cost(tau, vot) * link.get_period_flow_vol(tau) 252 | ) 253 | 254 | total_gap = total_sys_travel_time - total_min_sys_travel_time 255 | rel_gap = total_gap / max(total_sys_travel_time, EPSILON) 256 | print('current iteration number in column update: postprocessing\n' 257 | f'total gap: {total_gap:.4e}; relative gap: {rel_gap:.4%}\n') 258 | 259 | return rel_gap 260 | 261 | 262 | def _generate(spn, column_pool, iter_num): 263 | for c in spn.get_orig_centroids(): 264 | single_source_shortest_path(spn, c.get_node_id()) 265 | 266 | _backtrace_shortest_path_tree(c, 267 | spn.get_centroids(), 268 | spn.get_links(), 269 | spn.get_node_preds(), 270 | spn.get_link_preds(), 271 | spn.get_agent_type().get_id(), 272 | spn.get_demand_period().get_id(), 273 | column_pool, 274 | iter_num) 275 | 276 | 277 | def _generate_column_pool(spnetworks, column_pool, iter_num): 278 | # single processing 279 | # it could be multiprocessing 280 | for spn in spnetworks: 281 | _generate(spn, column_pool, iter_num) 282 | 283 | 284 | def update_links_using_columns(network): 285 | """ a helper function for load_columns() """ 286 | A = network._base_assignment 287 | 288 | column_pool = A.get_column_pool() 289 | links = A.get_links() 290 | 291 | # do not update column volume 292 | _update_link_and_column_volume(column_pool, links, 1, False) 293 | _update_link_travel_time(links) 294 | 295 | 296 | def find_ue(ui, column_gen_num, column_upd_num, rel_gap_tolerance=0.0001): 297 | """ find user equilibrium (UE) 298 | 299 | WARNING 300 | ------- 301 | Only Path/Column-based User Equilibrium (UE) is implemented in Python. 302 | If you need other assignment modes or dynamic traffic assignment (DTA), 303 | please use perform_network_assignment_DTALite() 304 | 305 | Parameters 306 | ---------- 307 | ui 308 | network object generated by pg.read_network() 309 | 310 | column_gen_num 311 | number of iterations to be performed on generating column pool 312 | column pool. it also specifies the maximum number of routes / columns 313 | for each OD pair. 314 | 315 | column_upd_num 316 | number of iterations to be performed on optimizing column pool 317 | 318 | rel_gap_tolerance 319 | target relative gap. find_ue() stops when either column_upd_num or 320 | rel_gap_tolerance is reached. 321 | 322 | Returns 323 | ------- 324 | rel_gap 325 | relative GAP to measure the UE convergency 326 | 327 | Note 328 | ---- 329 | You will need to call output_columns() and output_link_performance() to 330 | get the assignment results, i.e., paths/columns (in route_assignment.csv) and 331 | volumes and other link attributes on each link (in link_performance.csv). 332 | """ 333 | # make sure iteration numbers are both non-negative 334 | assert(column_gen_num>=0) 335 | assert(column_upd_num>=0) 336 | 337 | # base assignment 338 | A = ui._base_assignment 339 | # set up SPNetwork 340 | A.setup_spnetwork(True) 341 | 342 | ats = A.get_agent_types() 343 | column_pool = A.get_column_pool() 344 | links = A.get_links() 345 | 346 | print('find user equilibrium (UE)') 347 | st = time() 348 | 349 | for i in range(column_gen_num): 350 | print(f'current iteration number in column generation: {i}') 351 | 352 | _update_link_and_column_volume(column_pool, links, i) 353 | _update_link_travel_time(links) 354 | # update generalized link cost before assignment 355 | _update_link_cost_array(A.get_spnetworks()) 356 | # loop through all centroids on the base network 357 | _generate_column_pool(A.get_spnetworks(), column_pool, i) 358 | 359 | print(f'\nprocessing time of generating columns: {time()-st:.2f}s\n') 360 | st = time() 361 | 362 | i = 0 363 | rel_gap = 1 364 | while i < column_upd_num and rel_gap_tolerance < rel_gap: 365 | _update_link_and_column_volume(column_pool, links, i, False) 366 | _update_link_travel_time(links) 367 | rel_gap = _update_column_gradient_cost_and_flow(column_pool, links, ats, i) 368 | i += 1 369 | 370 | # postprocessing 371 | _update_link_and_column_volume(column_pool, links, column_gen_num, False) 372 | _update_link_travel_time(links) 373 | rel_gap = _update_column_attributes(column_pool, links, ats, A.get_spnetworks()) 374 | 375 | print(f'processing time of updating columns and postprocessing: {time()-st:.2f}s\n') 376 | 377 | return rel_gap 378 | -------------------------------------------------------------------------------- /path4gmns/consts.py: -------------------------------------------------------------------------------- 1 | """ global constants """ 2 | # for shortest path calculation 3 | MAX_LABEL_COST = 2147483647 4 | EPSILON = 0.00001 5 | # for column generation 6 | MIN_COL_VOL = 0.1 7 | # for accessibility evaluation 8 | MIN_TIME_BUDGET = 10 9 | MAX_TIME_BUDGET = 240 10 | BUDGET_TIME_INTVL = 5 11 | # unit 12 | MILE_TO_METER = 1609.34 13 | MPH_TO_KPH = 1.60934 14 | # simulation 15 | SECONDS_IN_MINUTE = 60 16 | SECONDS_IN_HOUR = 3600 -------------------------------------------------------------------------------- /path4gmns/dtaapi.py: -------------------------------------------------------------------------------- 1 | import os 2 | import ctypes 3 | import platform 4 | from multiprocessing import Process 5 | from time import sleep 6 | 7 | 8 | __all__ = ['perform_network_assignment_DTALite', 'run_DTALite'] 9 | 10 | 11 | _os = platform.system() 12 | if _os.startswith('Windows'): 13 | _dtalite_dll = os.path.join(os.path.dirname(__file__), 'bin/DTALite.dll') 14 | _dtalitemm_dll = os.path.join(os.path.dirname(__file__), 'bin/DTALiteMM.dll') 15 | elif _os.startswith('Linux'): 16 | _dtalite_dll = os.path.join(os.path.dirname(__file__), 'bin/DTALite.so') 17 | _dtalitemm_dll = os.path.join(os.path.dirname(__file__), 'bin/DTALiteMM.so') 18 | elif _os.startswith('Darwin'): 19 | # check CPU is Intel or Apple Silicon 20 | if platform.machine().startswith('x86_64'): 21 | _dtalite_dll = os.path.join(os.path.dirname(__file__), 'bin/DTALite_x86.dylib') 22 | _dtalitemm_dll = os.path.join(os.path.dirname(__file__), 'bin/DTALiteMM_x86.dylib') 23 | else: 24 | _dtalite_dll = os.path.join(os.path.dirname(__file__), 'bin/DTALite_arm.dylib') 25 | _dtalitemm_dll = os.path.join(os.path.dirname(__file__), 'bin/DTALiteMM_arm.dylib') 26 | else: 27 | raise Exception( 28 | 'Please build the shared library compatible to your OS using source files' 29 | ) 30 | 31 | 32 | def _emit_log(log_file='log_main.txt'): 33 | with open(log_file, 'r') as fp: 34 | for line in fp: 35 | print(line) 36 | 37 | 38 | def perform_network_assignment_DTALite(assignment_mode, 39 | column_gen_num, 40 | column_upd_num): 41 | """ DEPRECATED Python interface to call DTALite (precompiled as shared library) 42 | 43 | perform network assignment using the selected assignment mode 44 | 45 | WARNING 46 | ------- 47 | MAKE SURE TO BACKUP route_assignment.csv and link_performance.csv if you have 48 | called find_ue() before. Otherwise, they will be overwritten by results 49 | generated by DTALite. 50 | 51 | Parameters 52 | ---------- 53 | assignment_mode 54 | 0: Link-based UE, only generates link performance file without agent path file 55 | 56 | 1: Path-based UE, generates link performance file and agent path file 57 | 58 | 2: UE + dynamic traffic assignment (DTA), generates link performance file and agent path file 59 | 60 | 3: ODME 61 | 62 | column_gen_num 63 | number of iterations to be performed before on generating column pool 64 | 65 | column_upd_num 66 | number of iterations to be performed on optimizing column pool 67 | 68 | Returns 69 | ------- 70 | None 71 | 72 | Note 73 | ---- 74 | The output will depend on the selected assignment_mode. 75 | 76 | Link-based UE: link_performance.csv 77 | 78 | Path-based UE: route_assignment.csv and link_performance.csv 79 | 80 | UE + DTA: route_assignment.csv and link_performance.csv 81 | 82 | route_assignment.csv: paths/columns 83 | 84 | link_performance.csv: assigned volumes and other link attributes 85 | on each link 86 | """ 87 | # make sure assignment_mode is right 88 | assert(assignment_mode in [0, 1, 2, 3]) 89 | # make sure iteration numbers are both non-negative 90 | assert(column_gen_num>=0) 91 | assert(column_upd_num>=0) 92 | 93 | _dtalite_engine = ctypes.cdll.LoadLibrary(_dtalite_dll) 94 | _dtalite_engine.network_assignment.argtypes = [ctypes.c_int, 95 | ctypes.c_int, 96 | ctypes.c_int] 97 | 98 | print('This function has been deprecated, and will be removed later!' 99 | 'Please use run_DTALite() instead!') 100 | print('\nDTALite run starts\n') 101 | 102 | if _os.startswith('Windows'): 103 | _dtalite_engine.network_assignment(assignment_mode, 104 | column_gen_num, 105 | column_upd_num) 106 | 107 | _emit_log() 108 | 109 | print('\nDTALite run completes\n') 110 | print( 111 | f'check link_performance.csv in {os.getcwd()} for link performance\n' 112 | f'check route_assignment.csv in {os.getcwd()} for unique agent paths\n' 113 | ) 114 | else: 115 | # the following multiprocessing call does not work for Windows, 116 | # and there is no solution. 117 | # OSError: [WinError 87] The parameter is incorrect 118 | proc_dta = Process( 119 | target=_dtalite_engine.network_assignment, 120 | args=(assignment_mode, column_gen_num, column_upd_num,) 121 | ) 122 | 123 | proc_print = Process(target=_emit_log) 124 | 125 | proc_dta.start() 126 | proc_dta.join() 127 | 128 | if proc_dta.exitcode is not None: 129 | sleep(0.1) 130 | proc_print.start() 131 | proc_print.join() 132 | if proc_dta.exitcode == 0: 133 | print('DTALite run completes!\n') 134 | print( 135 | f'check link_performance.csv in {os.getcwd()} for link performance\n' 136 | f'check route_assignment.csv in {os.getcwd()} for unique agent paths\n' 137 | ) 138 | else: 139 | print('DTALite run terminates!') 140 | 141 | 142 | def run_DTALite(): 143 | """ Python interface to call the latest DTALite 144 | 145 | This version of DTALite includes all-new Logbook, enhanced scenario handling, 146 | improved I/O functionality, and so on. 147 | 148 | Its source code can be found at https://github.com/asu-trans-ai-lab/DTALite/tree/feature/multimodal. 149 | 150 | Parameters 151 | ---------- 152 | None 153 | 154 | Returns 155 | ------- 156 | None 157 | 158 | Note 159 | ---- 160 | It is NOT compatible with the classic DTALite (i.e., perform_network_assignment_DTALite()). 161 | 162 | Only use the following data set from 163 | https://github.com/asu-trans-ai-lab/DTALite/tree/feature/multimodal/data. 164 | 165 | Please run the script calling this API using your system terminal rather than 166 | Python console for proper logging. 167 | """ 168 | _dtalitemm_engine = ctypes.cdll.LoadLibrary(_dtalitemm_dll) 169 | print('\nDTALite run starts\n') 170 | 171 | proc_dta = Process(target=_dtalitemm_engine.DTALiteAPI()) 172 | proc_print = Process(target=_emit_log, args=('log_DTA.txt',)) 173 | 174 | proc_dta.start() 175 | proc_dta.join() 176 | 177 | if proc_dta.exitcode is not None: 178 | sleep(0.1) 179 | proc_print.start() 180 | proc_print.join() 181 | if proc_dta.exitcode == 0: 182 | print('DTALite run completes!') 183 | else: 184 | print('DTALite run terminates!') -------------------------------------------------------------------------------- /path4gmns/odme.py: -------------------------------------------------------------------------------- 1 | __all__ = ['conduct_odme'] 2 | 3 | 4 | def conduct_odme(ui, odme_update_num, dp_id=0): 5 | """ calibrate traffic assignment results using traffic observations 6 | 7 | Parameters 8 | ---------- 9 | ui 10 | network object generated by pg.read_network() 11 | 12 | odme_update_num 13 | number of iterations to be performed on ODME 14 | 15 | dp_id 16 | The sequence number of demand period listed in demand_periods in 17 | settings.yml. Its default value is 0. 18 | 19 | The current implementation of ODME only supports one demand period. 20 | 21 | Returns 22 | ------- 23 | None 24 | 25 | Note 26 | ---- 27 | You will need to call output_columns() and output_link_performance() to 28 | get the calibrated assignment results (i.e., paths/columns) in 29 | route_assignment.csv and link volumes (and other link attributes) in 30 | link_performance.csv. 31 | """ 32 | print('conduct origin-destination demand matrix estimation (ODME)') 33 | 34 | # base assignment 35 | A = ui._base_assignment 36 | # check whether dp_id (demand period id) exists or not 37 | # exception will be thrown if dp_id does not exist 38 | A.get_demand_period_str(dp_id) 39 | # set up SPNetwork 40 | A.setup_spnetwork() 41 | 42 | # some constants 43 | delta = 0.05 44 | step_size = 0.01 45 | 46 | # the current implementation of ODME only supports one demand period 47 | column_pool = {k: v for k, v in A.get_column_pool().items() if k[1] == dp_id} 48 | links = A.get_links() 49 | zones = A.network.zones 50 | 51 | for j in range(odme_update_num): 52 | _update_link_volume(column_pool, links, zones, dp_id, j) 53 | 54 | # k = (at, dp, oz, dz) 55 | for k, cv in column_pool.items(): 56 | if cv.is_route_fixed(): 57 | continue 58 | 59 | for col in cv.get_columns(): 60 | vol = col.get_volume() 61 | if not vol: 62 | continue 63 | 64 | path_gradient_cost = 0 65 | 66 | orig_zone = zones[k[2]] 67 | if orig_zone.prod_obs > 0: 68 | if not orig_zone.is_prod_obs_upper_bounded: 69 | path_gradient_cost += orig_zone.prod_est_dev 70 | elif orig_zone.is_prod_obs_upper_bounded and orig_zone.prod_est_dev > 0: 71 | path_gradient_cost += orig_zone.prod_est_dev 72 | 73 | dest_zone = zones[k[3]] 74 | if dest_zone.attr_obs > 0: 75 | if not dest_zone.is_attr_obs_upper_bounded: 76 | path_gradient_cost += dest_zone.attr_est_dev 77 | elif dest_zone.is_attr_obs_upper_bounded and dest_zone.attr_est_dev > 0: 78 | path_gradient_cost += dest_zone.attr_est_dev 79 | 80 | for i in col.links: 81 | link = links[i] 82 | if link.obs >= 1: 83 | if not link.is_obs_upper_bounded: 84 | path_gradient_cost += link.est_dev 85 | elif link.is_obs_upper_bounded and link.est_dev > 0: 86 | path_gradient_cost += link.est_dev 87 | 88 | col.set_gradient_cost(path_gradient_cost) 89 | 90 | change_vol_ub = vol * delta 91 | change_vol_lb = -change_vol_ub 92 | 93 | change_vol = step_size * path_gradient_cost 94 | if change_vol < change_vol_lb: 95 | change_vol = change_vol_lb 96 | elif change_vol > change_vol_ub: 97 | change_vol = change_vol_ub 98 | 99 | col.set_volume(max(1, vol - change_vol)) 100 | 101 | 102 | def _update_link_volume(column_pool, links, zones, dp_id, iter_num): 103 | # reset the volume for each link 104 | for link in links: 105 | if not link.length: 106 | break 107 | 108 | link.reset_period_flow_vol() 109 | 110 | # reset the estimated attraction and production 111 | for z in zones.values(): 112 | z.attr_est = 0 113 | z.prod_est = 0 114 | 115 | # update estimations and link volume 116 | # k = (at, dp, oz, dz) 117 | for k, cv in column_pool.items(): 118 | for col in cv.get_columns(): 119 | vol = col.get_volume() 120 | if not vol: 121 | continue 122 | 123 | zones[k[2]].prod_est += vol 124 | zones[k[3]].attr_est += vol 125 | 126 | for i in col.links: 127 | # to be consistent with _update_link_and_column_volume() 128 | # pce_ratio = 1 and vol * pce_ratio 129 | links[i].increase_period_flow_vol(dp_id, vol) 130 | 131 | # ODME statistics 132 | total_abs_gap = 0 133 | total_attr_gap = 0 134 | total_prod_gap = 0 135 | total_link_gap = 0 136 | 137 | # calculate estimation deviation for each link 138 | for link in links: 139 | if not link.length: 140 | break 141 | 142 | link.calculate_td_vdf() 143 | 144 | if link.obs < 1: 145 | continue 146 | 147 | # problematic: est_dev is a scalar rather than a vector? 148 | # code optimization: wrap it as a member function for class Link 149 | link.est_dev = link.get_period_flow_vol(dp_id) - link.obs 150 | total_abs_gap += abs(link.est_dev) 151 | total_link_gap += link.est_dev / link.obs 152 | 153 | # calculate estimation deviations for each zone 154 | for z in zones.values(): 155 | if z.attr_obs >= 1: 156 | dev = z.attr_est - z.attr_obs 157 | z.attr_est_dev = dev 158 | 159 | total_abs_gap += abs(dev) 160 | total_attr_gap += dev / z.attr_obs 161 | 162 | if z.prod_obs >= 1: 163 | dev = z.prod_est - z.prod_obs 164 | z.prod_est_dev = dev 165 | 166 | total_abs_gap += abs(dev) 167 | total_prod_gap += dev / z.prod_obs 168 | 169 | print(f'current iteration number in ODME: {iter_num}\n' 170 | f'total absolute estimation gap: {total_abs_gap:,.2f}\n' 171 | f'total relative estimation gap (link): {total_link_gap:.2%}\n' 172 | f'total relative estimation gap (zone attraction): {total_attr_gap:.2%}\n' 173 | f'total relative estimation gap (zone production): {total_prod_gap:.2%}\n') -------------------------------------------------------------------------------- /path4gmns/path.py: -------------------------------------------------------------------------------- 1 | """ The Python interface connecting the C++ path engine and other Python APIs """ 2 | import ctypes 3 | import platform 4 | from os import path 5 | from time import time 6 | 7 | from .consts import MAX_LABEL_COST 8 | from .utils import _convert_str_to_int, InvalidRecord 9 | 10 | 11 | _os = platform.system() 12 | if _os.startswith('Windows'): 13 | _dll_file = path.join(path.dirname(__file__), 'bin/path_engine.dll') 14 | elif _os.startswith('Linux'): 15 | _dll_file = path.join(path.dirname(__file__), 'bin/path_engine.so') 16 | elif _os.startswith('Darwin'): 17 | # check CPU is Intel or Apple Silicon 18 | if platform.machine().startswith('x86_64'): 19 | _dll_file = path.join(path.dirname(__file__), 'bin/path_engine_x86.dylib') 20 | else: 21 | _dll_file = path.join(path.dirname(__file__), 'bin/path_engine_arm.dylib') 22 | else: 23 | raise Exception('Please build the shared library compatible to your OS\ 24 | using the source file in engine!') 25 | 26 | _cdll = ctypes.cdll.LoadLibrary(_dll_file) 27 | 28 | # set up the argument types for the shortest path function in dll. 29 | _cdll.shortest_path_n.argtypes = [ 30 | ctypes.c_int, 31 | ctypes.c_int, 32 | ctypes.POINTER(ctypes.c_int), 33 | ctypes.POINTER(ctypes.c_int), 34 | ctypes.POINTER(ctypes.c_int), 35 | ctypes.POINTER(ctypes.c_int), 36 | ctypes.POINTER(ctypes.c_int), 37 | ctypes.POINTER(ctypes.c_wchar_p), 38 | ctypes.POINTER(ctypes.c_double), 39 | ctypes.POINTER(ctypes.c_double), 40 | ctypes.POINTER(ctypes.c_int), 41 | ctypes.POINTER(ctypes.c_int), 42 | ctypes.POINTER(ctypes.c_int), 43 | ctypes.c_wchar_p, 44 | ctypes.c_int, 45 | ctypes.c_int, 46 | ctypes.c_int 47 | ] 48 | 49 | 50 | # simple caching for _single_source_shortest_path() 51 | _prev_cost_type = 'time' 52 | 53 | 54 | def _optimal_label_correcting_CAPI(G, origin_node_no, departure_time=0): 55 | """ call the deque implementation of MLC written in cpp 56 | 57 | node_label_cost, node_predecessor, and link_predecessor are still 58 | initialized in shortest_path_n() even the source node has no outgoing links. 59 | """ 60 | _cdll.shortest_path_n(origin_node_no, 61 | G.get_node_size(), 62 | G.get_from_node_no_arr(), 63 | G.get_to_node_no_arr(), 64 | G.get_first_links(), 65 | G.get_last_links(), 66 | G.get_sorted_link_no_arr(), 67 | G.get_allowed_uses(), 68 | G.get_link_costs(), 69 | G.get_node_label_costs(), 70 | G.get_node_preds(), 71 | G.get_link_preds(), 72 | G.get_queue_next(), 73 | G.get_agent_type_name(), 74 | MAX_LABEL_COST, 75 | G.get_last_thru_node(), 76 | departure_time) 77 | 78 | 79 | def single_source_shortest_path(G, orig_node_id, cost_type='time'): 80 | G.allocate_for_CAPI() 81 | 82 | global _prev_cost_type 83 | if _prev_cost_type != cost_type: 84 | G.init_link_costs(cost_type) 85 | _prev_cost_type = cost_type 86 | 87 | orig_node_no = G.get_node_no(orig_node_id) 88 | _optimal_label_correcting_CAPI(G, orig_node_no) 89 | 90 | 91 | def output_path_sequence(G, to_node_id, seq_type='node'): 92 | """ output shortest path in terms of node sequence or link sequence 93 | 94 | Note that this function returns GENERATOR rather than list. 95 | """ 96 | path = [] 97 | curr_node_no = G.map_id_to_no[to_node_id] 98 | 99 | if seq_type.startswith('node'): 100 | # retrieve the sequence backwards 101 | while curr_node_no >= 0: 102 | path.append(curr_node_no) 103 | curr_node_no = G.node_preds[curr_node_no] 104 | # reverse the sequence 105 | for node_no in reversed(path): 106 | yield G.map_no_to_id[node_no] 107 | else: 108 | # retrieve the sequence backwards 109 | curr_link_no = G.link_preds[curr_node_no] 110 | while curr_link_no >= 0: 111 | path.append(curr_link_no) 112 | curr_node_no = G.node_preds[curr_node_no] 113 | curr_link_no = G.link_preds[curr_node_no] 114 | # reverse the sequence 115 | for link_no in reversed(path): 116 | yield G.links[link_no].get_link_id() 117 | 118 | 119 | def find_shortest_path(G, from_node_id, to_node_id, seq_type, cost_type): 120 | if from_node_id not in G.map_id_to_no: 121 | raise Exception(f'Node ID: {from_node_id} not in the network') 122 | if to_node_id not in G.map_id_to_no: 123 | raise Exception(f'Node ID: {to_node_id} not in the network') 124 | 125 | single_source_shortest_path(G, from_node_id, cost_type) 126 | 127 | path_cost = G.get_path_cost(to_node_id, cost_type) 128 | if path_cost >= MAX_LABEL_COST: 129 | return f'path {cost_type}: infinity | path: ' 130 | 131 | path = _get_path_sequence_str(G, to_node_id, seq_type) 132 | 133 | unit = 'minutes' 134 | if cost_type.startswith('dis'): 135 | unit = G.get_length_unit() + 's' 136 | 137 | if seq_type.startswith('node'): 138 | return f'path {cost_type}: {path_cost:.4f} {unit} | node path: {path}' 139 | else: 140 | return f'path {cost_type}: {path_cost:.4f} {unit} | link path: {path}' 141 | 142 | 143 | def find_path_for_agents(G, column_pool, cost_type): 144 | """ find and set up shortest path for each agent 145 | 146 | the internal node and links will be used to set up the node sequence and 147 | link sequence respectively 148 | 149 | Note that we do not cache the predecessors and label cost even some agents 150 | may share the same origin and each call of the single-source path algorithm 151 | will calculate the shortest path tree from the source node. 152 | """ 153 | if G.get_agent_count() == 0: 154 | print('setting up individual agents') 155 | G.setup_agents(column_pool) 156 | 157 | from_node_id_prev = '' 158 | for agent in G.agents: 159 | from_node_id = agent.o_node_id 160 | to_node_id = agent.d_node_id 161 | 162 | # just in case agent has the same origin and destination 163 | if from_node_id == to_node_id: 164 | continue 165 | 166 | if from_node_id not in G.map_id_to_no: 167 | raise Exception(f'Node ID: {from_node_id} not in the network') 168 | if to_node_id not in G.map_id_to_no: 169 | raise Exception(f'Node ID: {to_node_id} not in the network') 170 | 171 | # simple caching strategy 172 | # if the current from_node_id is the same as from_node_id_prev, 173 | # then there is no need to redo shortest path calculation. 174 | if from_node_id != from_node_id_prev: 175 | from_node_id_prev = from_node_id 176 | single_source_shortest_path(G, from_node_id, cost_type) 177 | 178 | # set up the cost 179 | agent.path_cost = G.get_path_cost(to_node_id, cost_type) 180 | 181 | node_path = [] 182 | link_path = [] 183 | 184 | curr_node_no = G.map_id_to_no[to_node_id] 185 | # retrieve the sequence backwards 186 | while curr_node_no >= 0: 187 | node_path.append(curr_node_no) 188 | curr_link_no = G.link_preds[curr_node_no] 189 | if curr_link_no >= 0: 190 | link_path.append(curr_link_no) 191 | curr_node_no = G.node_preds[curr_node_no] 192 | 193 | # make sure it is a valid path 194 | if not link_path: 195 | continue 196 | 197 | agent.node_path = [x for x in node_path] 198 | agent.link_path = [x for x in link_path] 199 | 200 | 201 | def _get_path_sequence_str(G, to_node_id, seq_type): 202 | return ';'.join(str(x) for x in output_path_sequence(G, to_node_id, seq_type)) 203 | 204 | 205 | def get_shortest_path_tree(G, from_node_id, seq_type, cost_type, integer_node_id): 206 | """ compute the shortest path tree from the source node (from_node_id) 207 | 208 | it returns a dictionary, where key is to_node_id and value is the 209 | corresponding shortest path information (path cost and path details). 210 | 211 | Note that the source node itself is excluded from the dictionary keys. 212 | """ 213 | if from_node_id not in G.map_id_to_no: 214 | raise Exception(f'Node ID: {from_node_id} not in the network') 215 | 216 | single_source_shortest_path(G, from_node_id, cost_type) 217 | 218 | if integer_node_id: 219 | sp_tree = {} 220 | for to_node_id in G.map_id_to_no: 221 | if to_node_id == from_node_id: 222 | continue 223 | 224 | try: 225 | to_node_id_int = _convert_str_to_int(to_node_id) 226 | except InvalidRecord: 227 | to_node_id_int = to_node_id 228 | 229 | sp_tree[to_node_id_int] = ( 230 | G.get_path_cost(to_node_id, cost_type), 231 | _get_path_sequence_str(G, to_node_id, seq_type) 232 | ) 233 | 234 | return sp_tree 235 | else: 236 | return { 237 | to_node_id : ( 238 | G.get_path_cost(to_node_id, cost_type), 239 | _get_path_sequence_str(G, to_node_id, seq_type) 240 | ) 241 | for to_node_id in G.map_id_to_no if to_node_id != from_node_id 242 | } 243 | 244 | 245 | def benchmark_apsp(G): 246 | st = time() 247 | 248 | for k in G.map_id_to_no: 249 | # do not include centroids 250 | if G.map_id_to_no[k] >= G.get_last_thru_node(): 251 | break 252 | 253 | single_source_shortest_path(G, k) 254 | 255 | print(f'processing time of finding all-pairs shortest paths: {time()-st:.4f} s') 256 | -------------------------------------------------------------------------------- /path4gmns/simulation.py: -------------------------------------------------------------------------------- 1 | from math import ceil 2 | 3 | 4 | __all__ = ['perform_simple_simulation'] 5 | 6 | 7 | def perform_simple_simulation(ui, loading_profile='uniform'): 8 | """ perform simple traffic simulation using point queue model 9 | 10 | WARNING 11 | ------- 12 | The underlying routing decision is made through find_path_for_agents(), which 13 | must be called before this function. 14 | 15 | Parameters 16 | ---------- 17 | ui 18 | network object generated by pg.read_network() 19 | 20 | loading_profile 21 | demand loading profile, i.e., how agents are loaded to network with respect 22 | to their departure times. Three loading profiles are supported, which are 23 | uniform, random, and constant. 24 | 25 | With uniform loading profile, agents will be uniformly distributed between 26 | [simulation start time, simulation start time + simulation duration). 27 | 28 | With random loading profile, the departure time of each agent is randomly 29 | set up between [simulation start time, simulation start time + simulation duration). 30 | 31 | With constant loading profile, the departure times of all agents are the 32 | same, which are simulation start time. 33 | 34 | Returns 35 | ------- 36 | None 37 | 38 | Note 39 | ---- 40 | You will need to call output_agent_trajectory() to get the simulation 41 | results, i.e., trajectory of each agent (in trajectory.csv). 42 | """ 43 | A = ui._base_assignment 44 | A.initialize_simulation(loading_profile) 45 | if not A.get_agents(): 46 | return 47 | 48 | print('conduct dynamic traffic assignment (DTA)') 49 | 50 | links = A.get_links() 51 | nodes = A.get_nodes() 52 | 53 | cum_arr = cum_dep = 0 54 | 55 | # number of simulation intervals in one minute (60s) 56 | num = A.cast_minute_to_interval(1) 57 | for i in range(A.get_total_simu_intervals()): 58 | if i % num == 0: 59 | print(f'simu time = {i/num} min, CA = {cum_arr}, CD = {cum_dep}') 60 | 61 | # comment out from now on as link.cum_arr and link.cum_dep are not used 62 | # for critical calculation 63 | # if i > 0: 64 | # for link in links: 65 | # link.cum_arr[i] = link.cum_arr[i-1] 66 | # link.cum_dep[i] = link.cum_dep[i-1] 67 | 68 | if A.have_dep_agents(i): 69 | for a_no in A.get_td_agents(i): 70 | a = A.get_agent(a_no) 71 | if a.link_path is None: 72 | continue 73 | 74 | # retrieve the first link given link path is in reverse order 75 | link_no = a.link_path[-1] 76 | link = links[link_no] 77 | # link.cum_arr[i] += 1 78 | link.entr_queue.append(a_no) 79 | cum_arr += 1 80 | 81 | for link in links: 82 | while link.entr_queue: 83 | a_no = link.entr_queue.popleft() 84 | agent = A.get_agent(a_no) 85 | link.exit_queue.append(a_no) 86 | intvl = A.cast_minute_to_interval(link.get_period_fftt(0)) 87 | agent.update_dep_interval(intvl) 88 | 89 | for node in nodes: 90 | m = node.get_incoming_link_num() 91 | for j in range(m): 92 | # randomly select the first link 93 | pos = (i + j) % m 94 | link = node.incoming_links[pos] 95 | 96 | while link.outflow_cap[i] and link.exit_queue: 97 | a_no = link.exit_queue[0] 98 | agent = A.get_agent(a_no) 99 | 100 | if agent.get_curr_dep_interval() > i: 101 | break 102 | 103 | if agent.reached_last_link(): 104 | # link.cum_dep[i] += 1 105 | cum_dep += 1 106 | else: 107 | link_no = agent.get_next_link_no() 108 | next_link = links[link_no] 109 | next_link.entr_queue.append(a_no) 110 | agent.set_dep_interval(i) 111 | # set up arrival time for the next link, i.e., next_link 112 | agent.set_arr_interval(i, 1) 113 | 114 | travel_intvl = i - agent.get_arr_interval() 115 | waiting_intvl = travel_intvl - A.cast_minute_to_interval(link.get_period_fftt(0)) 116 | # arrival time in minutes 117 | arr_minute = A.cast_interval_to_minute(agent.get_arr_interval()) 118 | link.update_waiting_time(arr_minute, waiting_intvl) 119 | # link.cum_dep[i] += 1 120 | # next_link.cum_arr[i] += 1 121 | 122 | agent.increment_link_pos() 123 | # remove agent from exit queue 124 | link.exit_queue.popleft() 125 | link.outflow_cap[i] -= 1 -------------------------------------------------------------------------------- /path4gmns/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | from datetime import timedelta 3 | from threading import Thread 4 | 5 | from .consts import MILE_TO_METER, MPH_TO_KPH 6 | 7 | 8 | __all__ = ['download_sample_data_sets', 'download_sample_setting_file'] 9 | 10 | 11 | class InvalidRecord(Exception): 12 | """a custom exception for invalid input from parsing a csv file""" 13 | pass 14 | 15 | 16 | # a little bit ugly 17 | def _convert_str_to_int(s): 18 | if not s: 19 | raise InvalidRecord 20 | 21 | try: 22 | return int(s) 23 | except ValueError: 24 | # if s is not numeric, a ValueError will be then caught 25 | try: 26 | return int(float(s)) 27 | except ValueError: 28 | raise InvalidRecord 29 | except TypeError: 30 | raise InvalidRecord 31 | 32 | 33 | def _convert_str_to_float(s): 34 | if not s: 35 | raise InvalidRecord 36 | 37 | try: 38 | return float(s) 39 | except (TypeError, ValueError): 40 | raise InvalidRecord 41 | 42 | 43 | def _convert_boundaries(bs): 44 | """a helper function to facilitate read_zones()""" 45 | if not bs: 46 | raise InvalidRecord 47 | 48 | prefix = 'LINESTRING (' 49 | postfix = ')' 50 | 51 | try: 52 | b = bs.index(prefix) + len(prefix) 53 | e = bs.index(postfix) 54 | except ValueError: 55 | raise Exception(f'Invalid Zone Boundaries: {bs}') 56 | 57 | bs_ = bs[b:e] 58 | vs = [x for x in bs_.split(',')] 59 | 60 | # validation 61 | if len(vs) != 5: 62 | raise Exception(f'Invalid Zone Boundaries: {bs}') 63 | 64 | if vs[0] != vs[-1]: 65 | raise Exception(f'Invalid Zone Boundaries: {bs}') 66 | 67 | L, U = vs[0].split(' ') 68 | R, U_ = vs[1].split(' ') 69 | if U != U_: 70 | raise Exception( 71 | f'Invalid Zone Boundaries: inconsistent upper boundary {U}; {U_}' 72 | ) 73 | 74 | R_, D = vs[2].split(' ') 75 | if R != R_: 76 | raise Exception( 77 | 'Invalid Zone Boundaries: inconsistent right boundary {R}; {R_}' 78 | ) 79 | 80 | L_, D_ = vs[3].split(' ') 81 | if L != L_: 82 | raise Exception( 83 | 'Invalid Zone Boundaries: inconsistent left boundary {L}; {L_}' 84 | ) 85 | 86 | if D != D_: 87 | raise Exception( 88 | 'Invalid Zone Boundaries: inconsistent lower boundary {D}; {D_}' 89 | ) 90 | 91 | U = _convert_str_to_float(U) 92 | D = _convert_str_to_float(D) 93 | L = _convert_str_to_float(L) 94 | R = _convert_str_to_float(R) 95 | 96 | return U, D, L, R 97 | 98 | 99 | def _get_time_stamp(minute): 100 | """ covert minute into HH:MM:SS as string """ 101 | s = minute * 60 102 | return str(timedelta(seconds=s)) 103 | 104 | 105 | def _download_url(url, filename, loc_dir): 106 | try: 107 | import requests 108 | 109 | r = requests.get(url) 110 | r.raise_for_status() 111 | with open(loc_dir+filename, 'wb') as f: 112 | f.write(r.content) 113 | except ImportError: 114 | print('please install requests to proceed downloading!!') 115 | except requests.HTTPError: 116 | print(f'file not existing: {url}') 117 | except requests.ConnectionError: 118 | raise Exception('check your connection!!!') 119 | except Exception as e: 120 | raise e 121 | 122 | 123 | def download_sample_data_sets(): 124 | """ download sample data sets from the Github repo 125 | 126 | the following data sets will be downloaded: ASU, Braess Paradox, Chicago Sketch, 127 | Lima Network, Sioux Falls, and Two Corridors. 128 | """ 129 | url = 'https://raw.githubusercontent.com/jdlph/Path4GMNS/dev/data/' 130 | 131 | data_sets = [ 132 | "ASU", 133 | "Braess_Paradox", 134 | "Chicago_Sketch", 135 | "Lima_Network", 136 | "Sioux_Falls", 137 | "Two_Corridor" 138 | ] 139 | 140 | files = [ 141 | "node.csv", 142 | "link.csv", 143 | "demand.csv", 144 | "measurement.csv", 145 | "settings.csv", 146 | "settings.yml" 147 | ] 148 | 149 | print('downloading starts') 150 | 151 | # data folder under cdw 152 | loc_data_dir = 'data' 153 | if not os.path.isdir(loc_data_dir): 154 | os.mkdir(loc_data_dir) 155 | 156 | for ds in data_sets: 157 | web_dir = url + ds + '/' 158 | loc_sub_dir = os.path.join(loc_data_dir, ds) + '/' 159 | 160 | if not os.path.isdir(loc_sub_dir): 161 | os.mkdir(loc_sub_dir) 162 | 163 | # multi-threading 164 | threads = [] 165 | for x in files: 166 | t = Thread( 167 | target=_download_url, 168 | args=(web_dir+x, x, loc_sub_dir) 169 | ) 170 | t.start() 171 | threads.append(t) 172 | 173 | for t in threads: 174 | t.join() 175 | 176 | print('downloading completes') 177 | print(f'check {os.path.join(os.getcwd(), loc_data_dir)} for downloaded data sets') 178 | 179 | 180 | def download_sample_setting_file(): 181 | """ download the sample settings.yml from the Github repo """ 182 | url = 'https://raw.githubusercontent.com/jdlph/Path4GMNS/master/data/Chicago_Sketch/settings.yml' 183 | filename = 'settings.yml' 184 | loc_dir = './' 185 | 186 | _download_url(url, filename, loc_dir) 187 | 188 | print('downloading completes') 189 | print(f'check {os.getcwd()} for downloaded settings.yml') 190 | 191 | 192 | def get_len_unit_conversion_factor(unit): 193 | len_units = ['kilometer', 'km', 'meter', 'm', 'mile', 'mi'] 194 | 195 | # length unit check 196 | # linear search is OK for such small lists 197 | if unit not in len_units: 198 | units = ', '.join(len_units) 199 | raise Exception( 200 | f'Invalid length unit: {unit} !' 201 | f' Please choose one available unit from {units}' 202 | ) 203 | 204 | cf = 1 205 | if unit.startswith('meter') or unit == 'm': 206 | cf = MILE_TO_METER 207 | elif unit.startswith('kilometer') or unit.startswith('km'): 208 | cf = MPH_TO_KPH 209 | 210 | return cf 211 | 212 | 213 | def get_spd_unit_conversion_factor(unit): 214 | spd_units = ['kmh', 'kph', 'mph'] 215 | 216 | # speed unit check 217 | if unit not in spd_units: 218 | units = ', '.join(spd_units) 219 | raise Exception( 220 | f'Invalid speed unit: {unit} !' 221 | f' Please choose one available unit from {units}' 222 | ) 223 | 224 | if unit.startswith('kmh') or unit.startswith('kph'): 225 | return MPH_TO_KPH 226 | 227 | return 1 228 | -------------------------------------------------------------------------------- /path4gmns/zonesyn.py: -------------------------------------------------------------------------------- 1 | from math import ceil, floor 2 | import warnings 3 | 4 | from .accessibility import _update_min_travel_time 5 | from .classes import AccessNetwork, ColumnVec, Zone 6 | from .utils import _convert_str_to_float, InvalidRecord 7 | 8 | 9 | __all__ = ['network_to_zones'] 10 | 11 | 12 | def _get_grid_id(x, y, res): 13 | try: 14 | i = floor(x / res) 15 | j = floor(y / res) 16 | return i, j 17 | except ZeroDivisionError: 18 | raise Exception('ZERO Resolution. Please check your coordinate info!!') 19 | 20 | 21 | def _get_boundaries(nodes): 22 | try: 23 | L = R = _convert_str_to_float(nodes[0].coord_x) 24 | U = D = _convert_str_to_float(nodes[0].coord_y) 25 | except InvalidRecord: 26 | for i, node in enumerate(nodes): 27 | try: 28 | x = _convert_str_to_float(node.coord_x) 29 | except InvalidRecord: 30 | continue 31 | 32 | try: 33 | y = _convert_str_to_float(node.coord_y) 34 | except InvalidRecord: 35 | continue 36 | 37 | L = R = x 38 | U = D = y 39 | break 40 | 41 | if i == len(nodes) - 1 and (L is None or U is None): 42 | raise Exception('No Coordinate Info') 43 | 44 | for node in nodes: 45 | try: 46 | x = _convert_str_to_float(node.coord_x) 47 | except InvalidRecord: 48 | continue 49 | 50 | try: 51 | y = _convert_str_to_float(node.coord_y) 52 | except InvalidRecord: 53 | continue 54 | 55 | L = min(L, x) 56 | R = max(R, x) 57 | D = min(D, y) 58 | U = max(U, y) 59 | 60 | return (U, D, L, R) 61 | 62 | 63 | def _find_resolution(nodes, grid_dim): 64 | # adopt from NeXTA 65 | resolutions = [0.00005, 0.0001, 0.0002, 0.00025, 0.0005, 0.00075, 66 | 0.001, 0.002, 0.0025, 0.005, 0.0075, 0.01, 0.02, 67 | 0.025, 0.05, 0.075, 0.1, 0.2, 0.25, 0.5, 0.75, 68 | 1, 2, 2.5, 5, 7.5, 10, 20, 25, 50, 75] 69 | 70 | # if grid_dim is d, we will create a total of d * d grids 71 | (U, D, L, R) = _get_boundaries(nodes) 72 | res = ((R - L + U - D) / grid_dim) / 2 73 | for r in resolutions: 74 | if res < r: 75 | res = r 76 | break 77 | 78 | return res 79 | 80 | 81 | def _synthesize_bin_index(max_bin, zones): 82 | min_ = max_ = next(iter(zones.values())).get_activity_nodes_num() 83 | for z in zones.values(): 84 | n = z.get_activity_nodes_num() 85 | min_ = min(min_, n) 86 | max_ = max(max_, n) 87 | 88 | # just in case max_ and min_ are the same 89 | # max_ would not be ZERO as guaranteed by _synthesize_grid() 90 | bin_size = max_ 91 | if min_ != max_: 92 | bin_size = ceil((max_ - min_) / max_bin) 93 | 94 | for z in zones.values(): 95 | # make sure it starts from 0 96 | bi = (z.get_activity_nodes_num() - 1) // bin_size 97 | z.set_bin_index(bi) 98 | 99 | 100 | def _synthesize_grid(ui, grid_dim, max_bin): 101 | A = ui._base_assignment 102 | nodes = A.get_nodes() 103 | 104 | if not nodes: 105 | raise Exception('No Nodes found in the network') 106 | 107 | network = A.network 108 | zones = network.zones 109 | zones.clear() 110 | 111 | sample_rate = 0 112 | if network.activity_node_num == 0: 113 | sample_rate = 10 114 | # in case of reginal model 115 | if len(nodes) > 1000: 116 | sample_rate = int(len(nodes) / 100) 117 | 118 | # zone id starts from 1 119 | k = 1 120 | num = 0 121 | grids = {} 122 | res = _find_resolution(nodes, grid_dim) 123 | 124 | for m, node in enumerate(nodes): 125 | try: 126 | x = _convert_str_to_float(node.coord_x) 127 | except InvalidRecord: 128 | continue 129 | 130 | try: 131 | y = _convert_str_to_float(node.coord_y) 132 | except InvalidRecord: 133 | continue 134 | 135 | if not node.is_activity_node: 136 | if not sample_rate: 137 | continue 138 | elif m % sample_rate != 0: 139 | continue 140 | 141 | (i, j) = _get_grid_id(x, y, res) 142 | if (i, j) not in grids: 143 | grids[(i, j)] = str(k) 144 | z = Zone(k) 145 | # boundaries (roughly) 146 | L_ = i * res 147 | D_ = j * res 148 | R_ = L_ + res 149 | U_ = D_ + res 150 | # coordinates of the centroid, which are weighted by the first node 151 | cx = (2 * x + L_ + R_) / 4 152 | cy = (2 * y + U_ + D_) / 4 153 | z.set_coord(cx, cy) 154 | z.set_geo(U_, D_, L_, R_) 155 | zones[str(k)] = z 156 | k += 1 157 | 158 | zones[grids[(i, j)]].add_activity_node(node.get_node_id()) 159 | # this is needed for _update_min_travel_time() 160 | zones[grids[(i, j)]].add_node(node.get_node_id()) 161 | num += 1 162 | 163 | # update it only when the original network do not have activity nodes 164 | if sample_rate: 165 | network.activity_node_num = num 166 | 167 | _synthesize_bin_index(max_bin, zones) 168 | 169 | 170 | def _synthesize_demand(ui, total_demand, time_budget, mode): 171 | A = ui._base_assignment 172 | network = A.network 173 | 174 | column_pool = A.column_pool 175 | zones = network.zones 176 | num = network.activity_node_num 177 | 178 | # calculate accessibility 179 | an = AccessNetwork(network) 180 | at_name, at_str = A._convert_mode(mode) 181 | an.set_target_mode(at_name) 182 | at = A.get_agent_type(at_str) 183 | 184 | min_travel_times = {} 185 | _update_min_travel_time(an, at, min_travel_times, False, 0) 186 | 187 | # allocate trips proportionally to each zone 188 | trip_rate = total_demand / num 189 | for z in zones.values(): 190 | z.set_production(round(z.get_activity_nodes_num() * trip_rate)) 191 | 192 | dp_id = 0 193 | at_id = at.get_id() 194 | # allocate trips proportionally to each OD pair 195 | for z, v in zones.items(): 196 | if v.get_production() == 0: 197 | continue 198 | 199 | total_attr = sum( 200 | v_.get_production() for z_, v_ in zones.items() if ( 201 | z != z_ and min_travel_times[(z, z_, at_str)][0] <= time_budget 202 | ) 203 | ) 204 | 205 | if total_attr == 0: 206 | continue 207 | 208 | prod = v.get_production() 209 | 210 | for z_, v_ in zones.items(): 211 | if z_ == z: 212 | continue 213 | 214 | if v_.get_production() == 0: 215 | continue 216 | 217 | if min_travel_times[(z, z_, at_str)][0] > time_budget: 218 | continue 219 | 220 | prod_ = v_.get_production() 221 | portion = prod_ / total_attr 222 | 223 | # no need to check if (at_id, dp_id, z, z_) exits as the current 224 | # way of iterating zones ensures the uniqueness of (z, z_) 225 | column_pool[(at_id, dp_id, z, z_)] = ColumnVec() 226 | column_pool[(at_id, dp_id, z, z_)].increase_volume(round(prod * portion)) 227 | 228 | if not column_pool: 229 | warnings.warn( 230 | f'ZERO demand is synthesized!! Please check speed and length units' 231 | ' in link.csv, and time_budget!' 232 | ) 233 | 234 | 235 | def network_to_zones(ui, grid_dimension=8, max_bin=5, total_demand=100000, time_budget=120, mode='auto'): 236 | """ synthesize zones and OD demand given a network 237 | 238 | Parameters 239 | ---------- 240 | ui 241 | network object generated by pg.read_network(). 242 | 243 | grid_dimension 244 | positive integer. If its value is d, a total of d * d zones will be synthesized. 245 | 246 | max_bin 247 | positive integer. The maximum number of bin_idex generated for synthesized zones. 248 | 249 | total_demand 250 | The total demand or the total number of trips to be allocated to the OD 251 | demand matrix. it should be a positive integer. 252 | 253 | The allocated demand to each zone is proportional to the number of its 254 | activity nodes. Given an origin zone, its production volume will be proportionally 255 | allocated to each connected destination zone. Gravity Model is NOT in use. 256 | 257 | note that the summation of demand over each OD pair is roughly the same 258 | as total_demand due to rounding errors. 259 | 260 | time_budget 261 | the amount of time to travel in minutes, which is used to cut off the demand 262 | allocation. If the minimum travel time between an OD pair under a specific mode 263 | is greater than time_budget, we consider that the two zones are not connected 264 | and no demand will be allocated between them. 265 | 266 | mode 267 | target mode with its default value as 'auto'. It can be either agent type 268 | or its name. For example, 'w' and 'walk' are equivalent inputs. 269 | 270 | It is used along with time_budget to check if the minimum travel time under 271 | the given mode exceeds the time budget or not. 272 | 273 | Returns 274 | ------- 275 | None 276 | 277 | Note 278 | ---- 279 | The following files will be output. 280 | 281 | zone.csv.csv 282 | synthesized zones including zone id, activity nodes, coordinates of its 283 | centroid, it boundaries (as a grid or rectangle), production volume, and 284 | attraction volume. 285 | 286 | zone_id will be an integer starting from one. 287 | 288 | syn_demand.csv 289 | synthesized demand between each connected OD pair (within a time budget). 290 | """ 291 | if grid_dimension <= 0 or grid_dimension != int(grid_dimension): 292 | raise Exception('Invalid grid_dimension: it must be a Positive Integer!') 293 | 294 | if total_demand <= 0: 295 | raise Exception('Invalid total_demand: it must be a Positive Number') 296 | 297 | if time_budget <= 0: 298 | raise Exception('Invalid time_budget: it must be a Positive Number') 299 | 300 | _synthesize_grid(ui, grid_dimension, max_bin) 301 | _synthesize_demand(ui, total_demand, time_budget, mode) -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import re 2 | from setuptools import setup 3 | 4 | 5 | _package_name = 'path4gmns' 6 | 7 | 8 | def get_long_description(): 9 | with open('README.md', 'r') as fh: 10 | return fh.read() 11 | 12 | 13 | def get_version(): 14 | init_file = 'path4gmns/__init__.py' 15 | with open(init_file, 'r') as fh: 16 | content = fh.read() 17 | 18 | version = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", content, re.M) 19 | if version: 20 | return version.group(1) 21 | 22 | raise RuntimeError('unable to find version info in __init__.py') 23 | 24 | 25 | setup( 26 | name=_package_name, 27 | version=get_version(), 28 | author='Dr. Xuesong Zhou, Dr. Peiheng Li', 29 | author_email='xzhou74@asu.edu, jdlph@hotmail.com', 30 | description='An open-source, cross-platform, lightweight, and fast Python\ 31 | path engine for networks encoded in GMNS', 32 | long_description=get_long_description(), 33 | long_description_content_type='text/markdown', 34 | url='https://github.com/jdlph/PATH4GMNS', 35 | packages=[_package_name], 36 | package_dir={_package_name: _package_name}, 37 | package_data={_package_name: ['bin/*']}, 38 | license='Apache License 2.0', 39 | classifiers=[ 40 | 'Programming Language :: Python :: 3', 41 | 'License :: OSI Approved :: Apache Software License', 42 | 'Operating System :: OS Independent', 43 | ], 44 | ) -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdlph/Path4GMNS/e9ed456cbaa72da41b5849e7aacce553d10fc3e1/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from os import getcwd 4 | from os.path import join 5 | from shutil import copytree 6 | 7 | 8 | ORIG_CWD = getcwd() 9 | SRC_DATA_DIR = join(ORIG_CWD, 'data/Chicago_Sketch') 10 | 11 | 12 | @pytest.fixture(scope="session") 13 | def sample_data_dir(tmp_path_factory): 14 | """ create a temporary directory with sample data set 15 | 16 | do not explicitly remove temporary folder and files but leave the removal 17 | to Pytest (i.e., entries older than 3 temporary directories will be removed). 18 | 19 | output files from DTALite would not be released and removing them will trigger 20 | PermissionError. A possible remedy is to use multiprocessing to handle test 21 | case of DTALite. 22 | """ 23 | tmp_dir = tmp_path_factory.mktemp('data') 24 | copytree(SRC_DATA_DIR, tmp_dir, dirs_exist_ok=True) 25 | yield str(tmp_dir) 26 | 27 | 28 | @pytest.fixture(scope="session") 29 | def tmp_output_dir(tmp_path_factory): 30 | tmp_dir = tmp_path_factory.mktemp("output") 31 | yield str(tmp_dir) -------------------------------------------------------------------------------- /tests/test_a11y.py: -------------------------------------------------------------------------------- 1 | from path4gmns.accessibility import evaluate_accessibility, evaluate_equity 2 | from path4gmns.io import read_network 3 | 4 | 5 | def test_multimodal_accessibility(sample_data_dir, tmp_output_dir): 6 | network = read_network(input_dir=sample_data_dir) 7 | # multimodal accessibility evaluation 8 | evaluate_accessibility(network, output_dir=tmp_output_dir) 9 | 10 | # get accessible nodes and links starting from node 1 with a 5-minute 11 | # time window for the default mode auto (i.e., 'auto') 12 | network.get_accessible_nodes(1, 5) 13 | network.get_accessible_links(1, 5) 14 | 15 | # get accessible nodes and links starting from node 1 with a 15-minute 16 | # time window for mode walk (i.e., 'w') 17 | try: 18 | network.get_accessible_nodes(1, 15, 'w') 19 | network.get_accessible_links(1, 15, 'w') 20 | except Exception: 21 | pass 22 | 23 | 24 | def test_unimodal_accessibility(sample_data_dir, tmp_output_dir): 25 | network = read_network(input_dir=sample_data_dir) 26 | # accessibility evaluation for a target mode only 27 | evaluate_accessibility(network, 28 | single_mode=True, 29 | mode='auto', 30 | output_dir=tmp_output_dir) 31 | 32 | 33 | def test_time_dependent_accessibility(sample_data_dir, tmp_output_dir): 34 | network = read_network(input_dir=sample_data_dir) 35 | # time-dependent accessibility under the default mode auto 36 | # for demand period 0 (i.e., VDF_fftt1 in link.csv will be used in the 37 | # evaluation) 38 | evaluate_accessibility(network, 39 | single_mode=True, 40 | time_dependent=True, 41 | output_dir=tmp_output_dir) 42 | 43 | # get accessible nodes and links starting from node 1 with a 5-minute 44 | # time window for the default mode auto for demand period 0 45 | network.get_accessible_nodes(1, 5, time_dependent=True) 46 | network.get_accessible_links(1, 5, time_dependent=True) 47 | 48 | # get accessible nodes and links starting from node 1 with a 15-minute 49 | # time window for mode walk (i.e., 'w') for demand period 0 50 | try: 51 | network.get_accessible_nodes(1, 15, 'w', time_dependent=True) 52 | network.get_accessible_links(1, 15, 'w', time_dependent=True) 53 | except Exception: 54 | pass 55 | 56 | 57 | def test_multimodal_equity(sample_data_dir, tmp_output_dir): 58 | network = read_network(input_dir=sample_data_dir) 59 | # multimodal equity evaluation under default time budget (60 min) 60 | evaluate_equity(network, output_dir=tmp_output_dir) 61 | 62 | 63 | def test_unimodal_equity(sample_data_dir, tmp_output_dir): 64 | network = read_network(input_dir=sample_data_dir) 65 | # equity evaluation for a target mode with time budget as 30 min 66 | evaluate_equity(network, 67 | single_mode=True, 68 | mode='auto', 69 | time_budget=30, 70 | output_dir=tmp_output_dir) -------------------------------------------------------------------------------- /tests/test_dtalite.py: -------------------------------------------------------------------------------- 1 | from os import chdir, getcwd 2 | 3 | from path4gmns.dtaapi import perform_network_assignment_DTALite 4 | 5 | 6 | ORIG_DIR = getcwd() 7 | 8 | 9 | def test_classic_dtalite(sample_data_dir, mode = 1): 10 | chdir(sample_data_dir) 11 | 12 | column_gen_num = 20 13 | column_upd_num = 20 14 | perform_network_assignment_DTALite(mode, column_gen_num, column_upd_num) 15 | 16 | chdir(ORIG_DIR) 17 | 18 | 19 | # disable it from now on (10/16/24) as run_DTALite() requires a different 20 | # settings.yml 21 | # def test_multimodal_dtalite(sample_data_dir): 22 | # chdir(sample_data_dir) 23 | 24 | # run_DTALite() 25 | 26 | # chdir(ORIG_DIR) -------------------------------------------------------------------------------- /tests/test_odme.py: -------------------------------------------------------------------------------- 1 | from os.path import isfile 2 | 3 | from path4gmns.colgen import find_ue 4 | from path4gmns.odme import conduct_odme 5 | from path4gmns.io import load_columns, read_demand, read_network, \ 6 | read_measurements, output_link_performance 7 | 8 | 9 | def test_odme(sample_data_dir, tmp_output_dir): 10 | network = read_network(input_dir=sample_data_dir) 11 | 12 | if isfile(tmp_output_dir + '/route_assignment.csv'): 13 | # bypass perform_column_generation() and call load_columns(network) 14 | # when there is route_assignment.csv 15 | load_columns(network, input_dir=tmp_output_dir) 16 | else: 17 | read_demand(network, input_dir=sample_data_dir) 18 | column_gen_num = 20 19 | column_upd_num = 20 20 | find_ue(network, column_gen_num, column_upd_num) 21 | 22 | read_measurements(network, input_dir=sample_data_dir) 23 | # ODME 24 | odme_upd_num = 20 25 | conduct_odme(network, odme_upd_num) 26 | # ODME output 27 | output_link_performance(network, mode='odme', output_dir=tmp_output_dir) -------------------------------------------------------------------------------- /tests/test_sim.py: -------------------------------------------------------------------------------- 1 | from os.path import isfile 2 | 3 | from path4gmns.colgen import find_ue 4 | from path4gmns.simulation import perform_simple_simulation 5 | from path4gmns.io import load_columns, output_agent_trajectory, \ 6 | read_demand, read_network 7 | 8 | 9 | def test_simulation(sample_data_dir, tmp_output_dir): 10 | network = read_network(input_dir=sample_data_dir) 11 | 12 | if isfile(tmp_output_dir + '/route_assignment.csv'): 13 | # bypass perform_column_generation() and call load_columns(network) 14 | # when there is route_assignment.csv 15 | load_columns(network, input_dir=tmp_output_dir) 16 | else: 17 | read_demand(network, input_dir=sample_data_dir) 18 | column_gen_num = 20 19 | column_upd_num = 20 20 | find_ue(network, column_gen_num, column_upd_num) 21 | 22 | # simulation 23 | perform_simple_simulation(network, 'uniform') 24 | # simulation output 25 | output_agent_trajectory(network, output_dir=tmp_output_dir) -------------------------------------------------------------------------------- /tests/test_sp.py: -------------------------------------------------------------------------------- 1 | from os.path import isfile 2 | from random import randint 3 | 4 | from path4gmns.io import output_agent_paths, read_demand, read_network 5 | 6 | 7 | def test_routing_engine(sample_data_dir): 8 | network = read_network(input_dir=sample_data_dir) 9 | network.benchmark_apsp() 10 | 11 | 12 | def test_find_shortest_path(sample_data_dir): 13 | network = read_network(input_dir=sample_data_dir) 14 | 15 | # shortest path (node id) from node 1 to node 2 measured by time 16 | network.find_shortest_path(1, 2) 17 | # shortest path (link id) from node 1 to node 2 measured by time 18 | network.find_shortest_path(1, 2, seq_type='link') 19 | 20 | # shortest path (node id) from node 1 to node 2 measured by distance 21 | network.find_shortest_path(1, 2, cost_type='distance') 22 | # shortest path (link id) from node 1 to node 2 measured by distance 23 | network.find_shortest_path(1, 2, seq_type='link', cost_type='distance') 24 | 25 | # retrieve the shortest path under a specific mode (which must be defined 26 | # in settings.yaml) 27 | if isfile(sample_data_dir + '/settings.yml'): 28 | # shortest path (node id) from node 1 to node 2 measured by time 29 | network.find_shortest_path(1, 2, mode='a') 30 | # shortest path (link id) from node 1 to node 2 measured by time 31 | network.find_shortest_path(1, 2, mode='a', seq_type='link') 32 | # shortest path (node id) from node 1 to node 2 measured by distance 33 | network.find_shortest_path(1, 2, mode='a', cost_type='distance') 34 | # shortest path (link id) from node 1 to node 2 measured by distance 35 | network.find_shortest_path(1, 2, mode='a', seq_type='link', cost_type='distance') 36 | 37 | 38 | def test_find_shortest_path_for_agents(sample_data_dir, tmp_output_dir): 39 | """ find_path_for_agents has been DEPRECATED """ 40 | network = read_network(input_dir=sample_data_dir) 41 | read_demand(network, input_dir=sample_data_dir) 42 | 43 | # find agent paths under a specific mode defined in settings.yaml, 44 | # say, a (i.e., auto) 45 | # network.find_path_for_agents('a') or network.find_path_for_agents('auto') 46 | # cost is measured in time 47 | network.find_path_for_agents() 48 | 49 | if network.get_agent_num() == 0: 50 | return 51 | 52 | agent_id = randint(0, network.get_agent_num() - 1) 53 | # origin node id of agent 54 | network.get_agent_orig_node_id(agent_id) 55 | # destination node id of agent 56 | network.get_agent_dest_node_id(agent_id) 57 | # shortest path (node id) of agent 58 | network.get_agent_node_path(agent_id) 59 | # shortest path (link id) of agent 60 | network.get_agent_link_path(agent_id) 61 | 62 | # output unique agent paths to a csv file 63 | output_agent_paths(network, output_dir=tmp_output_dir) 64 | # exclude geometry info from the output file 65 | output_agent_paths(network, False, output_dir=tmp_output_dir) 66 | 67 | # test find_path_for_agents() using distance 68 | network.find_path_for_agents(cost_type='distance') 69 | 70 | agent_id = randint(0, network.get_agent_num() - 1) 71 | # origin node id of agent 72 | network.get_agent_orig_node_id(agent_id) 73 | # destination node id of agent 74 | network.get_agent_dest_node_id(agent_id) 75 | # shortest path (node id) of agent 76 | network.get_agent_node_path(agent_id) 77 | # shortest path (link id) of agent 78 | network.get_agent_link_path(agent_id) 79 | 80 | 81 | def test_get_shortest_path_tree(sample_data_dir): 82 | network = read_network(input_dir=sample_data_dir) 83 | # shortest path tree from node 1 (cost is measured by time) 84 | sp_tree_time = network.get_shortest_path_tree(1) 85 | 86 | sp_tree_time[2] 87 | sp_tree_time[3] 88 | sp_tree_time[4] 89 | sp_tree_time[5] 90 | 91 | # shortest path tree from node 1 (cost is measured by distance) 92 | sp_tree_dist = network.get_shortest_path_tree(1, cost_type='distance') 93 | 94 | sp_tree_dist[2] 95 | sp_tree_dist[3] 96 | sp_tree_dist[4] 97 | sp_tree_dist[5] 98 | 99 | sp_tree_time.clear() 100 | sp_tree_dist.clear() 101 | 102 | # use string node id as the key and check the consistency 103 | sp_tree_time = network.get_shortest_path_tree('1') 104 | 105 | sp_tree_time['2'] 106 | sp_tree_time['3'] 107 | sp_tree_time['4'] 108 | sp_tree_time['5'] 109 | 110 | sp_tree_dist = network.get_shortest_path_tree('1', cost_type='distance') 111 | 112 | sp_tree_dist['2'] 113 | sp_tree_dist['3'] 114 | sp_tree_dist['4'] 115 | sp_tree_dist['5'] 116 | -------------------------------------------------------------------------------- /tests/test_ue.py: -------------------------------------------------------------------------------- 1 | from path4gmns.colgen import find_ue 2 | from path4gmns.io import load_columns, output_columns,\ 3 | output_link_performance, read_demand, read_network 4 | 5 | 6 | def test_finding_ue(sample_data_dir, tmp_output_dir): 7 | network = read_network(input_dir=sample_data_dir) 8 | read_demand(network, input_dir=sample_data_dir) 9 | 10 | column_gen_num = 10 11 | column_upd_num = 10 12 | find_ue(network, column_gen_num, column_upd_num) 13 | 14 | # use output_columns(network, False) to exclude geometry info in the output file 15 | output_columns(network, output_dir=tmp_output_dir) 16 | output_link_performance(network, output_dir=tmp_output_dir) 17 | 18 | 19 | def test_finding_ue_with_rel_gap_tolerance(sample_data_dir): 20 | network = read_network(input_dir=sample_data_dir) 21 | read_demand(network, input_dir=sample_data_dir) 22 | 23 | column_gen_num = 20 24 | column_upd_num = 20 25 | 26 | rel_gap = find_ue(network, column_gen_num, column_upd_num) 27 | rel_gap_tolerance = 0.0003 28 | 29 | column_upd_num = 20 30 | while rel_gap > rel_gap_tolerance: 31 | rel_gap = find_ue(network, 0, column_upd_num) 32 | 33 | assert(rel_gap <= rel_gap_tolerance) 34 | 35 | 36 | def test_loading_columns(sample_data_dir, tmp_output_dir): 37 | network = read_network(input_dir=sample_data_dir) 38 | load_columns(network, input_dir=tmp_output_dir) 39 | 40 | column_gen_num = 0 41 | column_upd_num = 10 42 | find_ue(network, column_gen_num, column_upd_num) 43 | 44 | output_columns(network, output_dir=tmp_output_dir) 45 | output_link_performance(network, output_dir=tmp_output_dir) 46 | 47 | 48 | def test_mixed_invoking1(sample_data_dir, tmp_output_dir): 49 | """ test resolution on issue #51 (https://github.com/jdlph/Path4GMNS/issues/51) 50 | """ 51 | network = read_network(input_dir=sample_data_dir) 52 | read_demand(network, input_dir=sample_data_dir) 53 | 54 | # invoke find_shortest_path() before find_ue() 55 | network.find_shortest_path(1, 2) 56 | 57 | column_gen_num = 5 58 | column_upd_num = 5 59 | find_ue(network, column_gen_num, column_upd_num) 60 | 61 | 62 | def test_mixed_invoking2(sample_data_dir, tmp_output_dir): 63 | """ test resolution on issue #51 (https://github.com/jdlph/Path4GMNS/issues/51) 64 | """ 65 | network = read_network(input_dir=sample_data_dir) 66 | read_demand(network, input_dir=sample_data_dir) 67 | 68 | column_gen_num = 5 69 | column_upd_num = 5 70 | find_ue(network, column_gen_num, column_upd_num) 71 | 72 | # invoke find_shortest_path() after find_ue() 73 | network.find_shortest_path(1, 2) 74 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from os import chdir, getcwd 2 | 3 | from path4gmns.utils import download_sample_data_sets 4 | from path4gmns.io import read_demand, read_network 5 | 6 | 7 | def test_download_sample_data_sets(tmp_output_dir): 8 | orig_dir = getcwd() 9 | 10 | chdir(tmp_output_dir) 11 | download_sample_data_sets() 12 | 13 | chdir(orig_dir) 14 | 15 | 16 | def test_data_synthesis(sample_data_dir, tmp_output_dir): 17 | network = read_network(input_dir=sample_data_dir) 18 | 19 | # try to load synthetic data if there is any. otherwise, synthesize demand 20 | # and zones, and output them. 21 | read_demand(network, use_synthetic_data = True, input_dir=tmp_output_dir) --------------------------------------------------------------------------------