From 1a7e512b1cd049cefd317aed37a0172af7d4a660 Mon Sep 17 00:00:00 2001 From: asiripanich <17020181+asiripanich@users.noreply.github.com> Date: Tue, 18 Mar 2025 16:12:01 +1100 Subject: [PATCH 1/4] feat: link vitm configs as a git submodule. --- .gitmodules | 3 +++ vitm | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 vitm diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..7b2f2cf2e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vitm"] + path = vitm + url = https://github.com/VictoriaTransport/activitysim-vitm.git diff --git a/vitm b/vitm new file mode 160000 index 000000000..5e80bfc64 --- /dev/null +++ b/vitm @@ -0,0 +1 @@ +Subproject commit 5e80bfc648c47d7553596196ccb183d4bfc0971c From 1781014ff500ffb834103a3ae2751bfbbbb81c9c Mon Sep 17 00:00:00 2001 From: asiripanich <17020181+asiripanich@users.noreply.github.com> Date: Tue, 18 Mar 2025 20:03:44 +1100 Subject: [PATCH 2/4] feat: modify abm.models.util to allow business tours --- activitysim/abm/models/util/canonical_ids.py | 5 ++++- activitysim/abm/models/util/tour_frequency.py | 17 ++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/activitysim/abm/models/util/canonical_ids.py b/activitysim/abm/models/util/canonical_ids.py index ab2623916..f9765f546 100644 --- a/activitysim/abm/models/util/canonical_ids.py +++ b/activitysim/abm/models/util/canonical_ids.py @@ -104,10 +104,13 @@ def determine_mandatory_tour_flavors(mtf_settings, model_spec, default_flavors): # hard code work and school tours "work": parse_tour_flavor_from_columns(model_spec.columns, "work"), "school": parse_tour_flavor_from_columns(model_spec.columns, "school"), + "business": parse_tour_flavor_from_columns(model_spec.columns, "business"), } valid_flavors = (mandatory_tour_flavors["work"] >= 1) & ( mandatory_tour_flavors["school"] >= 1 + ) & ( + mandatory_tour_flavors["business"] >= 1 ) if provided_flavors is not None: @@ -264,7 +267,7 @@ def canonical_tours(state: workflow.State): ) mtf_spec = mtf_model_settings.get("SPEC", "mandatory_tour_frequency.csv") mtf_model_spec = read_alts_file(state, file_name=mtf_spec) - default_mandatory_tour_flavors = {"work": 2, "school": 2} + default_mandatory_tour_flavors = {"work": 2, "school": 2, "business": 2} mandatory_tour_flavors = determine_mandatory_tour_flavors( mtf_model_settings, mtf_model_spec, default_mandatory_tour_flavors diff --git a/activitysim/abm/models/util/tour_frequency.py b/activitysim/abm/models/util/tour_frequency.py index 93c624b13..0109958a3 100644 --- a/activitysim/abm/models/util/tour_frequency.py +++ b/activitysim/abm/models/util/tour_frequency.py @@ -222,6 +222,7 @@ def process_mandatory_tours( "workplace_zone_id", "home_zone_id", "household_id", + "business_zone_id", ] assert not persons.mandatory_tour_frequency.isnull().any() @@ -249,10 +250,20 @@ def process_mandatory_tours( ) # work tours destination is workplace_zone_id, school tours destination is school_zone_id - tours["destination"] = tours_merged.workplace_zone_id.where( - (tours_merged.tour_type == "work"), tours_merged.school_zone_id - ) + conditions = [ + tours_merged.tour_type == "work", + tours_merged.tour_type == "school", + tours_merged.tour_type == "business" + ] + + choices = [ + tours_merged.workplace_zone_id, + tours_merged.school_zone_id, + tours_merged.business_zone_id + ] + tours["destination"] = np.select(conditions, choices, default=-1) + tours["origin"] = tours_merged.home_zone_id tours["household_id"] = tours_merged.household_id From 40450ff318829d1ec6d76077b8be9e9923b4d7db Mon Sep 17 00:00:00 2001 From: asiripanich <17020181+asiripanich@users.noreply.github.com> Date: Tue, 18 Mar 2025 20:04:24 +1100 Subject: [PATCH 3/4] feat: implement work segment --- .../abm/models/mandatory_scheduling.py | 19 +++++++++++++++++++ activitysim/abm/models/tour_mode_choice.py | 14 ++++++++++++++ activitysim/core/simulate.py | 4 +++- conda-environments/activitysim-dev.yml | 2 +- 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/activitysim/abm/models/mandatory_scheduling.py b/activitysim/abm/models/mandatory_scheduling.py index 64fc26215..02a603759 100644 --- a/activitysim/abm/models/mandatory_scheduling.py +++ b/activitysim/abm/models/mandatory_scheduling.py @@ -4,6 +4,7 @@ import logging +import numpy as np import pandas as pd from activitysim.abm.models.util.tour_scheduling import run_tour_scheduling @@ -53,6 +54,24 @@ def mandatory_tour_scheduling( ~is_university_tour, "univ" ) + # split work purpose into work_white_collar and work_blue_collar which have different coefficients + mandatory_tours[tour_segment_col] = np.where((mandatory_tours.tour_type == 'work') & + (reindex(persons_merged['work_segment'].isin([1,2,3,4]), mandatory_tours.person_id)), + 'work_white_collar', mandatory_tours[tour_segment_col]) + + mandatory_tours[tour_segment_col] = np.where((mandatory_tours.tour_type == 'work') & + (reindex((persons_merged['work_segment'] == 5), mandatory_tours.person_id)), + 'work_blue_collar', mandatory_tours[tour_segment_col]) + + # split school purpose into school_primary and school_secondary which have different coefficients + mandatory_tours[tour_segment_col] = np.where((mandatory_tours.tour_type == 'school') & + (reindex(persons_merged.is_primary_student, mandatory_tours.person_id)), + 'school_primary', mandatory_tours[tour_segment_col]) + + mandatory_tours[tour_segment_col] = np.where((mandatory_tours.tour_type == 'school') & + (reindex(persons_merged.is_secondary_student, mandatory_tours.person_id)), + 'school_secondary', mandatory_tours[tour_segment_col]) + choices = run_tour_scheduling( state, model_name, diff --git a/activitysim/abm/models/tour_mode_choice.py b/activitysim/abm/models/tour_mode_choice.py index c6b8a5e00..74681ba7f 100644 --- a/activitysim/abm/models/tour_mode_choice.py +++ b/activitysim/abm/models/tour_mode_choice.py @@ -325,6 +325,20 @@ def tour_mode_choice_simulate( not_university, "univ" ) + # split work purpose into work_white_collar and work_blue_collar which have different coefficients + primary_tours_merged['tour_purpose'] = np.where((primary_tours_merged['tour_purpose'] == 'work') & + (primary_tours_merged['work_segment'].isin([1,2,3,4])), 'work_white_collar', primary_tours_merged['tour_purpose']) + + primary_tours_merged['tour_purpose'] = np.where((primary_tours_merged['tour_purpose'] == 'work') & + (primary_tours_merged['work_segment'] == 5), 'work_blue_collar', primary_tours_merged['tour_purpose']) + + # split school purpose into school_primary and school_secondary which have different coefficients + primary_tours_merged['tour_purpose'] = np.where((primary_tours_merged['tour_purpose'] == 'school') & + (primary_tours_merged.is_primary_student), 'school_primary', primary_tours_merged['tour_purpose']) + + primary_tours_merged['tour_purpose'] = np.where((primary_tours_merged['tour_purpose'] == 'school') & + (primary_tours_merged.is_secondary_student), 'school_secondary', primary_tours_merged['tour_purpose']) + # if trip logsums are used, run trip mode choice and append the logsums if model_settings.COMPUTE_TRIP_MODE_CHOICE_LOGSUMS: primary_tours_merged = get_trip_mc_logsums_for_all_modes( diff --git a/activitysim/core/simulate.py b/activitysim/core/simulate.py index fcffcf606..30a70d86e 100644 --- a/activitysim/core/simulate.py +++ b/activitysim/core/simulate.py @@ -418,7 +418,7 @@ def get_segment_coefficients( coefficients_df = filesystem.read_model_coefficients(model_settings) template_df = read_model_coefficient_template(filesystem, model_settings) coefficients_col = ( - template_df[segment_name].map(coefficients_df.value).astype(float) + template_df[segment_name].replace(coefficients_df.value).astype(float) ) if coefficients_col.isnull().any(): @@ -430,6 +430,8 @@ def get_segment_coefficients( assert not coefficients_col.isnull().any() coefficients_dict = coefficients_col.to_dict() + + coefficients_dict['SEGMENT_NAME'] = segment_name return coefficients_dict diff --git a/conda-environments/activitysim-dev.yml b/conda-environments/activitysim-dev.yml index b69a69f23..6533c10ca 100644 --- a/conda-environments/activitysim-dev.yml +++ b/conda-environments/activitysim-dev.yml @@ -27,7 +27,7 @@ dependencies: - ipykernel # so this env will appear in jupyter as a selection - isort - jupyterlab -- larch = 5.7.* +# - larch = 5.7.* - matplotlib - multimethod <2.0 - myst-parser # allows markdown in sphinx From 4b2de9f0ca7e4e9ad7d65172f648cd3043316889 Mon Sep 17 00:00:00 2001 From: asiripanich <17020181+asiripanich@users.noreply.github.com> Date: Tue, 18 Mar 2025 22:27:13 +1100 Subject: [PATCH 4/4] feat(shadow_pricing): use 'work_segment' for workplace instead of the default 'income_segment' --- activitysim/abm/tables/shadow_pricing.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/activitysim/abm/tables/shadow_pricing.py b/activitysim/abm/tables/shadow_pricing.py index 1b28883df..7c81382be 100644 --- a/activitysim/abm/tables/shadow_pricing.py +++ b/activitysim/abm/tables/shadow_pricing.py @@ -64,13 +64,7 @@ default_segment_to_name_dict = { # model_selector : persons_segment_name "school": "school_segment", - "workplace": "income_segment", -} - -default_segment_to_name_dict = { - # model_selector : persons_segment_name - "school": "school_segment", - "workplace": "income_segment", + "workplace": "work_segment", }