Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/asim-cvm/configs/settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,37 @@ models:
- write_cvm_trip_matrices
- write_tables

multiprocess: True
fail_fast: True
num_processes: 12

chunk_size: 0
chunk_method: hybrid_uss
chunk_training_mode: disabled

multiprocess_steps:
- name: mp_initialize
begin: household_attractor
- name: mp_accessibility
begin: cvm_accessibility
slice:
tables:
- commercial_accessibility
exclude: True
- name: mp_route_generation
begin: route_generation
slice:
tables:
- establishments
# route_generation_tnc single-process only
# land_use aggregation and duplicate establishment_id under slicing
- name: mp_route_generation_tnc
begin: route_generation_tnc
- name: mp_route
begin: route_purpose_and_vehicle
slice:
tables:
- establishments
- routes
- name: mp_finalize
begin: write_cvm_trip_matrices
2 changes: 2 additions & 0 deletions src/asim-cvm/configs/shadow_pricing.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# placeholder required by multiprocessing
shadow_pricing_models:
4 changes: 4 additions & 0 deletions src/asim-cvm/extensions/cvm_establishment_attractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,7 @@ def establishment_attractor(

# write land use table back to state
state.add_table("land_use", land_use)

# instantiate commercial_accessibility (must be checkpointed to be used
# to slice it)
state.get_dataframe("commercial_accessibility")
2 changes: 1 addition & 1 deletion src/asim-cvm/extensions/cvm_route_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@

_business_type_offset = int(10 ** np.ceil(np.log10(max(BusinessTypes))))


@workflow.step
def route_generation(
state: State,
Expand Down Expand Up @@ -165,6 +164,7 @@ def route_generation(

# sum the two logsums to get the accessibility
accessibility_df["accessibility"] = accessibility_df['estab_acc_logsum'] + accessibility_df['household_acc_logsum']
state.add_table("commercial_accessibility", accessibility_df)

# get the accessibility column for the industry
join_df = establishments_df.merge(
Expand Down
11 changes: 6 additions & 5 deletions src/asim-cvm/extensions/cvm_route_stops.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,12 +549,14 @@ def _dwell_time(
"max_duration"
]

random_dwell_times = scipy.stats.beta.rvs(
random_order = state.get_rn_generator().random_for_df(df)

random_dwell_times = scipy.stats.beta.ppf(
random_order[:,0],

@JiaXu1024 JiaXu1024 Aug 21, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yueshuaing @i-am-sijia Just wanna confirm if this section of code change fixes the issue of non-reproducible random number generation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Susan, yes, we removed the previous global random draw np.random.seed(seed=42), as in multiprocessing this code runs in each of the process, seed to the same draw. And the previous scipy.stats.beta.rvs() function draws a batch of len(df) random numbers, assigns them to the row based on its position, so reproducibility now depends on df's row order being identical every time which is not guaranteed in multiprocessing. So we switched to the ActivitySim's reproducible method state.get_rn_generator().random_for_df(df) from SimpleChannel

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @yueshuaing , thanks for the clarification. So the previous global random‑draw settings in the single‑process setup didn’t have any issues, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the code wasn't broken in the single-process cause the single-process processes the same data in the same order every time. The issue only appears when introducing multiprocessing, which became not reproducible between single vs multi process.

a=alpha,
b=beta,
loc=model_settings.min_dwell_time,
scale=max_duration,
size=len(df),
)

result_list.append(
Expand Down Expand Up @@ -622,7 +624,8 @@ def route_stops(
np.exp(accessibility_df['estab_acc_hh_food']) +
np.exp(accessibility_df['estab_acc_hh_package'])
)

state.add_table("commercial_accessibility", accessibility_df)

route_trip_num = 1

cv_trips = []
Expand Down Expand Up @@ -749,8 +752,6 @@ def route_stops(

nonterminated_routes[model_settings.location_settings_estb.RESULT_COL_NAME] = next_stop_location

np.random.seed(seed=42)

# Choose dwell time
nonterminated_routes = _dwell_time(
state,
Expand Down
22 changes: 22 additions & 0 deletions src/asim-cvm/extensions/cvm_state.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from activitysim.core.workflow import State as workflow_State
from activitysim.core.workflow.state import StateAttr
from activitysim.abm.models.util import canonical_ids as cid

from .cvm_settings import CVMSettings

Expand All @@ -10,3 +11,24 @@ class State(workflow_State):
"""

settings: CVMSettings = StateAttr(CVMSettings)

# cvm tables that need RNG channel / traceable-table / canonical
# index registration on every process start (single or multiprocess),
# since checkpoint reloads only tables listed here.
_CVM_TABLES = [
("establishments", "establishment_id"),
("establishments_all", "establishment_id_all"),
("routes", "route_id"),
]

@classmethod
def _register_cvm_tables(cls):
for table_name, index_name in cls._CVM_TABLES:
if table_name not in cid.RANDOM_CHANNELS:
cid.RANDOM_CHANNELS.append(table_name)
if table_name not in cid.TRACEABLE_TABLES:
cid.TRACEABLE_TABLES.append(table_name)
cid.CANONICAL_TABLE_INDEX_NAMES.setdefault(table_name, index_name)


State._register_cvm_tables()
45 changes: 31 additions & 14 deletions src/asim-cvm/run_cvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,40 @@
from pathlib import Path
import pytest
import activitysim.abm # register components # noqa: F401
from activitysim.core import mp_tasks
from activitysim.cli.run import INJECTABLES

# TODO: should this just be its own installable package?
ext_dir = str(Path(__file__).parents)
sys.path.insert(0, ext_dir)
extensions = importlib.import_module("extensions")
sys.path = sys.path[1:]
def main():
# TODO: should this just be its own installable package?
ext_dir = str(Path(__file__).parents)
sys.path.insert(0, ext_dir)
extensions = importlib.import_module("extensions")
sys.path = sys.path[1:]

arguments = sys.argv
arguments = sys.argv

data_dir = tuple(arguments[1].split(","))
configs_dir = tuple(arguments[2].split(","))
output_dir = arguments[3]
data_dir = tuple(arguments[1].split(","))
configs_dir = tuple(arguments[2].split(","))
output_dir = arguments[3]

state = extensions.cvm_state.State.make_default(
__file__, configs_dir=configs_dir, data_dir=data_dir, output_dir=output_dir
)
state = extensions.cvm_state.State.make_default(
__file__, configs_dir=configs_dir, data_dir=data_dir, output_dir=output_dir
)

state.logging.config_logger()
state.import_extensions("extensions")
state.logging.config_logger()

state.run(models=state.settings.models)
if state.settings.multiprocess:
injectables = {}
for k in INJECTABLES:
try:
injectables[k] = state.get_injectable(k)
except KeyError:
pass
injectables["settings"] = state.settings
mp_tasks.run_multiprocess(state, injectables)
else:
state.run(models=state.settings.models)

if __name__ == "__main__":
main()