From 86c67cb600d9ef7f9769f7152b8ca76bec21b446 Mon Sep 17 00:00:00 2001 From: Matt Richards Date: Tue, 10 Mar 2026 20:52:55 +1100 Subject: [PATCH 1/3] ENH: add trip scheduling choice explicit chunking # Conflicts: # activitysim/abm/test/test_misc/test_trip_scheduling_choice.py --- .../abm/models/trip_scheduling_choice.py | 13 ++++++++++-- .../test_misc/test_trip_scheduling_choice.py | 21 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/activitysim/abm/models/trip_scheduling_choice.py b/activitysim/abm/models/trip_scheduling_choice.py index 63f19f5a1..07b4eb447 100644 --- a/activitysim/abm/models/trip_scheduling_choice.py +++ b/activitysim/abm/models/trip_scheduling_choice.py @@ -276,7 +276,12 @@ def run_trip_scheduling_choice( choosers, chunk_trace_label, chunk_sizer, - ) in chunk.adaptive_chunked_choosers(state, indirect_tours, trace_label): + ) in chunk.adaptive_chunked_choosers( + state, + indirect_tours, + trace_label, + explicit_chunk_size=model_settings.explicit_chunk, + ): # Sort the choosers and get the schedule alternatives choosers = choosers.sort_index() # FIXME-EET: under use_explicit_error_terms, error terms here are aligned positionally, not keyed @@ -373,7 +378,11 @@ class TripSchedulingChoiceSettings(LogitComponentSettings, extra="forbid"): Settings for the `trip_scheduling_choice` component. """ - pass + explicit_chunk: float = 0 + """ + If > 0, use this chunk size instead of adaptive chunking. + If less than 1, use this fraction of the total number of rows. + """ @workflow.step diff --git a/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py b/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py index 8401c785c..f519afd13 100644 --- a/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py +++ b/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py @@ -6,6 +6,7 @@ import numpy as np import pandas as pd import pytest +from pandas.testing import assert_frame_equal from activitysim.abm.models import trip_scheduling_choice as tsc from activitysim.abm.tables.skims import skim_dict @@ -287,3 +288,23 @@ def test_run_trip_scheduling_choice(model_spec, tours, skims, locals_dict): # check that tours with no inbound stops have zero inbound duration assert out_tours[tsc.IB_DURATION].mask(in_tours[tsc.HAS_IB_STOPS], 0).sum() == 0 + + # confirm explicit chunking is supported and doesn't affect results + model_settings_explicit_chunk = tsc.TripSchedulingChoiceSettings( + **{ + "SPEC": "placeholder.csv", + "compute_settings": { + "protect_columns": ["origin", "destination", "schedule_id"] + }, + } + ) + out_tours_chunked = tsc.run_trip_scheduling_choice( + state, + model_spec, + tours, + skims, + locals_dict, + trace_label="PyTest Trip Scheduling", + model_settings=model_settings_explicit_chunk, + ) + assert_frame_equal(out_tours, out_tours_chunked) From 627f0ea52dc7d9793bbc87847db4d0d57a906319 Mon Sep 17 00:00:00 2001 From: Jeffrey Newman Date: Thu, 16 Jul 2026 09:04:27 -0400 Subject: [PATCH 2/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../abm/test/test_misc/test_trip_scheduling_choice.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py b/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py index f519afd13..f86f278bb 100644 --- a/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py +++ b/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py @@ -290,9 +290,12 @@ def test_run_trip_scheduling_choice(model_spec, tours, skims, locals_dict): assert out_tours[tsc.IB_DURATION].mask(in_tours[tsc.HAS_IB_STOPS], 0).sum() == 0 # confirm explicit chunking is supported and doesn't affect results + state.settings.chunk_training_mode = "explicit" + model_settings_explicit_chunk = tsc.TripSchedulingChoiceSettings( **{ "SPEC": "placeholder.csv", + "explicit_chunk": 2, "compute_settings": { "protect_columns": ["origin", "destination", "schedule_id"] }, @@ -301,7 +304,7 @@ def test_run_trip_scheduling_choice(model_spec, tours, skims, locals_dict): out_tours_chunked = tsc.run_trip_scheduling_choice( state, model_spec, - tours, + in_tours.copy(deep=True), skims, locals_dict, trace_label="PyTest Trip Scheduling", From ca09062217af3d69a703bf43fde47a5b83a2b0d3 Mon Sep 17 00:00:00 2001 From: Jeff Newman Date: Fri, 7 Aug 2026 11:34:25 -0500 Subject: [PATCH 3/3] TST: strengthen trip scheduling explicit chunking coverage --- .../test_misc/test_trip_scheduling_choice.py | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py b/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py index f86f278bb..6282dad63 100644 --- a/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py +++ b/activitysim/abm/test/test_misc/test_trip_scheduling_choice.py @@ -6,7 +6,6 @@ import numpy as np import pandas as pd import pytest -from pandas.testing import assert_frame_equal from activitysim.abm.models import trip_scheduling_choice as tsc from activitysim.abm.tables.skims import skim_dict @@ -229,9 +228,15 @@ def test_two_way_stop_patterns(tours): assert set(output_columns).issubset(windows.columns) -def test_run_trip_scheduling_choice(model_spec, tours, skims, locals_dict): - # create a temporary workflow state with no content +def test_run_trip_scheduling_choice(model_spec, tours, skims, locals_dict, monkeypatch): + # Keep the module-scoped fixture pristine so both runs start from identical inputs. + in_tours = tours.copy(deep=True) + + # Register the tours with the random-number manager so draws remain tied to tour IDs + # when the explicit run splits the choosers into multiple chunks. state = workflow.State.make_temp() + state.rng().add_channel("tours", in_tours) + state.rng().begin_step("test_trip_scheduling_choice") # Define model settings for this test. # The settings for this model requires a filename for the spec, but in this test we @@ -247,15 +252,11 @@ def test_run_trip_scheduling_choice(model_spec, tours, skims, locals_dict): } ) - # As is common in ActivitySim the component will modify the input dataframe in-place. - # For testing we make a copy of the input tours to compare against after running the model. - in_tours = tours.copy(deep=True) - # run the trip scheduling choice model out_tours = tsc.run_trip_scheduling_choice( state, model_spec, - tours, + in_tours.copy(deep=True), skims, locals_dict, trace_label="PyTest Trip Scheduling", @@ -290,7 +291,10 @@ def test_run_trip_scheduling_choice(model_spec, tours, skims, locals_dict): assert out_tours[tsc.IB_DURATION].mask(in_tours[tsc.HAS_IB_STOPS], 0).sum() == 0 # confirm explicit chunking is supported and doesn't affect results - state.settings.chunk_training_mode = "explicit" + chunked_state = workflow.State.make_temp() + chunked_state.settings.chunk_training_mode = "explicit" + chunked_state.rng().add_channel("tours", in_tours) + chunked_state.rng().begin_step("test_trip_scheduling_choice") model_settings_explicit_chunk = tsc.TripSchedulingChoiceSettings( **{ @@ -301,8 +305,19 @@ def test_run_trip_scheduling_choice(model_spec, tours, skims, locals_dict): }, } ) + + # Record the real chunker's output sizes to verify the model forwards its setting. + explicit_chunk_lengths = [] + adaptive_chunked_choosers = tsc.chunk.adaptive_chunked_choosers + + def record_explicit_chunks(*args, **kwargs): + for chunk_details in adaptive_chunked_choosers(*args, **kwargs): + explicit_chunk_lengths.append(len(chunk_details[1])) + yield chunk_details + + monkeypatch.setattr(tsc.chunk, "adaptive_chunked_choosers", record_explicit_chunks) out_tours_chunked = tsc.run_trip_scheduling_choice( - state, + chunked_state, model_spec, in_tours.copy(deep=True), skims, @@ -310,4 +325,5 @@ def test_run_trip_scheduling_choice(model_spec, tours, skims, locals_dict): trace_label="PyTest Trip Scheduling", model_settings=model_settings_explicit_chunk, ) - assert_frame_equal(out_tours, out_tours_chunked) + assert explicit_chunk_lengths == [2, 2, 1] + pd.testing.assert_frame_equal(out_tours, out_tours_chunked)