From ab7b0c7afd5edcca5a36b1e50198ab4e6f8a1687 Mon Sep 17 00:00:00 2001 From: David Hensle <51132108+dhensle@users.noreply.github.com> Date: Fri, 16 Jan 2026 11:28:48 -0800 Subject: [PATCH 01/13] adding estimation configs --- .vscode/launch.json | 14 ++ resident/configs_estimation/estimation.yaml | 109 ++++++++++++++ resident/configs_estimation/logging.yaml | 59 ++++++++ resident/configs_estimation/settings.yaml | 148 ++++++++++++++++++++ 4 files changed, 330 insertions(+) create mode 100644 resident/configs_estimation/estimation.yaml create mode 100644 resident/configs_estimation/logging.yaml create mode 100644 resident/configs_estimation/settings.yaml diff --git a/.vscode/launch.json b/.vscode/launch.json index daa9893..cefdd83 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,6 +17,20 @@ "-o", "outputs/full", ] }, + { + "name": "Run Estimation Mode", + "type": "debugpy", + "request": "launch", + "cwd": "${workspaceFolder}/resident", + "program": "simulation.py", + "console": "integratedTerminal", + "args": [ + "-c", "configs_estimation", // check the settings.yaml file for run settings! + "-c", "configs", + "-d", "model_data/metro/data_full", + "-o", "outputs/estimation", + ] + }, { "name": "Run Cropped Example", "type": "debugpy", diff --git a/resident/configs_estimation/estimation.yaml b/resident/configs_estimation/estimation.yaml new file mode 100644 index 0000000..621e231 --- /dev/null +++ b/resident/configs_estimation/estimation.yaml @@ -0,0 +1,109 @@ +EDB_FILETYPE: parquet # options: csv, pkl, parquet +DELETE_MP_SUBDIRS: True + +enable: True + +bundles: + - license_holding_status + - bike_comfort + - auto_ownership + - work_from_home + - external_worker_identification + - external_workplace_location + - school_location + - workplace_location + - vehicle_type_choice + - transit_pass_subsidy + - transit_pass_ownership + - free_parking + - telecommute_frequency + - cdap + - mandatory_tour_frequency + - mandatory_tour_scheduling_work + - mandatory_tour_scheduling_school + - mandatory_tour_scheduling_univ + - school_escorting + - joint_tour_frequency_composition + - external_joint_tour_identification + - joint_tour_participation + - joint_tour_destination + - joint_tour_scheduling + - non_mandatory_tour_frequency + - external_non_mandatory_identification + - external_non_mandatory_destination + - non_mandatory_tour_destination + - non_mandatory_tour_scheduling + - vehicle_allocation + # - park_and_ride_lot_choice + - tour_mode_choice + - atwork_subtour_frequency + - atwork_subtour_destination + - atwork_subtour_scheduling + - atwork_subtour_mode_choice + - stop_frequency + - trip_purpose + - trip_destination + - trip_scheduling + - trip_mode_choice + +# - atwork_subtour_mode_choice subtours.tour_mode + +survey_tables: + households: + file_name: override_households.csv + index_col: household_id + persons: + file_name: override_persons.csv + index_col: person_id + tours: + file_name: override_tours.csv + index_col: tour_id + joint_tour_participants: + file_name: override_joint_tour_participants.csv + index_col: participant_id + trips: + file_name: override_trips.csv + index_col: trip_id + +estimation_table_types: + license_holding_status: simple_simulate + bike_comfort: simple_simulate + work_from_home: simple_simulate + external_worker_identification: simple_simulate + external_workplace_location: interaction_sample_simulate + school_location: interaction_sample_simulate + workplace_location: interaction_sample_simulate + transit_pass_subsidy: simple_simulate + transit_pass_ownership: simple_simulate + auto_ownership: simple_simulate + vehicle_type_choice: interaction_simulate + transponder_ownership: simple_simulate + free_parking: simple_simulate + telecommute_frequency: simple_simulate + cdap: cdap_simulate + mandatory_tour_frequency: simple_simulate + mandatory_tour_scheduling_work: interaction_sample_simulate + mandatory_tour_scheduling_school: interaction_sample_simulate + mandatory_tour_scheduling_univ: interaction_sample_simulate + school_escorting: interaction_simulate + joint_tour_frequency_composition: interaction_simulate + external_joint_tour_identification: simple_simulate + joint_tour_participation: simple_simulate + joint_tour_destination: interaction_sample_simulate + joint_tour_scheduling: interaction_sample_simulate + non_mandatory_tour_frequency: interaction_simulate + external_non_mandatory_identification: simple_simulate + non_mandatory_tour_destination: interaction_sample_simulate + external_non_mandatory_destination: interaction_sample_simulate + non_mandatory_tour_scheduling: interaction_sample_simulate + vehicle_allocation: simple_simulate + tour_mode_choice: simple_simulate + atwork_subtour_frequency: simple_simulate + atwork_subtour_destination: interaction_sample_simulate + atwork_subtour_scheduling: interaction_sample_simulate + atwork_subtour_mode_choice: simple_simulate + stop_frequency: simple_simulate + trip_purpose: simple_probabilistic + trip_destination: interaction_sample_simulate + trip_scheduling: simple_probabilistic + trip_mode_choice: simple_simulate diff --git a/resident/configs_estimation/logging.yaml b/resident/configs_estimation/logging.yaml new file mode 100644 index 0000000..5f1f018 --- /dev/null +++ b/resident/configs_estimation/logging.yaml @@ -0,0 +1,59 @@ +# Config for logging +# ------------------ +# See http://docs.python.org/2.7/library/logging.config.html#configuration-dictionary-schema + +logging: + version: 1 + disable_existing_loggers: true + + + # Configuring the default (root) logger is highly recommended + root: + level: NOTSET + handlers: [console] + + loggers: + + activitysim: + level: DEBUG + handlers: [console, logfile] + propagate: false + + sharrow: + level: INFO + handlers: [console, logfile] + propagate: false + + handlers: + + logfile: + class: logging.FileHandler + filename: + get_log_file_path: 'activitysim.log' + mode: w + formatter: fileFormatter + level: NOTSET + + console: + class: logging.StreamHandler + stream: ext://sys.stdout + formatter: elapsedFormatter + level: INFO + + formatters: + + simpleFormatter: + class: logging.Formatter + # format: '%(levelname)s - %(name)s - %(message)s' + format: '%(levelname)s - %(message)s' + datefmt: '%d/%m/%Y %H:%M:%S' + + fileFormatter: + class: logging.Formatter + format: '%(asctime)s - %(levelname)s - %(name)s - %(message)s' + datefmt: '%d/%m/%Y %H:%M:%S' + + elapsedFormatter: + (): activitysim.core.tracing.ElapsedTimeFormatter + format: '[{elapsedTime}] {levelname:s}: {message:s}' + style: '{' diff --git a/resident/configs_estimation/settings.yaml b/resident/configs_estimation/settings.yaml new file mode 100644 index 0000000..6d8984b --- /dev/null +++ b/resident/configs_estimation/settings.yaml @@ -0,0 +1,148 @@ + +inherit_settings: True + +# assume enough RAM to not chunk +chunk_training_mode: disabled + +# input tables +# input tables +input_table_list: + - tablename: households + filename: override_households.csv + index_col: household_id + - tablename: persons + filename: override_persons.csv + index_col: person_id + - tablename: land_use + filename: land_use.csv + index_col: zone_id + rename_columns: + MAZ: zone_id + drop_columns: + - i1 + - i2 + - i3 + - i4 + - i5 + - i6 + - i7 + - i8 + - i9 + - hs + - hs_sf + - hs_mf + - hs_mh + - hh_sf + - hh_mf + - hh_mh + - zip09 + - luz_id + +write_raw_tables: False + +fail_fast: True + +use_shadow_pricing: False + +multiprocess: False +num_processes: 10 + +# turn writing of sample_tables on and off for all models +# (if True, tables will be written if DEST_CHOICE_SAMPLE_TABLE_NAME is specified in individual model settings) +want_dest_choice_sample_tables: False + +# number of households to simulate +households_sample_size: 0 + +resume_after: + +models: + ### mp_init_proto_pop (single process) + - transit_lot_connectivity + - initialize_proto_population # Separate step so proto tables can be split for multiprocess. + ### mp_disaggregate_accessibility + - compute_disaggregate_accessibility + ### mp_initialize_hhs (single process) + - initialize_landuse + - initialize_households + ### mp_accessibility + - compute_accessibility + ### mp_households + - license_holding_status + - bike_comfort + - av_ownership + - auto_ownership_simulate + - work_from_home + - external_worker_identification + - external_workplace_location + - school_location + - workplace_location + - vehicle_type_choice + - adjust_auto_operating_cost + - transit_pass_subsidy + - transit_pass_ownership + - free_parking + - telecommute_frequency + - cdap_simulate + - mandatory_tour_frequency + - mandatory_tour_scheduling + - school_escorting + - joint_tour_frequency_composition + - external_joint_tour_identification + - joint_tour_participation + - joint_tour_destination + - external_joint_tour_destination + - joint_tour_scheduling + - non_mandatory_tour_frequency + - external_non_mandatory_identification + - non_mandatory_tour_destination + - external_non_mandatory_destination + - non_mandatory_tour_scheduling + - vehicle_allocation + - park_and_ride_lot_choice + - tour_mode_choice_simulate + - atwork_subtour_frequency + - atwork_subtour_destination + - atwork_subtour_scheduling + - atwork_subtour_mode_choice + - stop_frequency + - trip_purpose + - trip_destination + - trip_purpose_and_destination + - trip_scheduling + - trip_mode_choice + - parking_location + ### mp_summarize (single process) + - write_data_dictionary + - track_skim_usage + - write_trip_matrices + - write_tables + +multiprocess_steps: + - name: mp_init_proto_pop + begin: transit_lot_connectivity + - name: mp_disaggregate_accessibility + num_processes: 10 + begin: compute_disaggregate_accessibility + slice: + tables: + - proto_households + - proto_persons + - proto_tours + - name: mp_initialize_hhs + begin: initialize_landuse + - name: mp_accessibility + begin: compute_accessibility + num_processes: 10 + slice: + tables: + - accessibility + exclude: True # this is needed so landuse (i.e. destinations) doesn't get split + - name: mp_households + begin: license_holding_status + slice: + tables: + - households + - persons + - name: mp_summarize + begin: write_data_dictionary \ No newline at end of file From 75202acdcf8dd81c347cc782adb7244cf2557eb1 Mon Sep 17 00:00:00 2001 From: Will Alexander Date: Mon, 26 Jan 2026 11:44:47 -0500 Subject: [PATCH 02/13] Fix school location model spec --- resident/configs/accessibility.yaml | 1 + resident/configs/non_mandatory_tour_destination.yaml | 1 + resident/configs/school_location.csv | 8 ++++---- resident/configs/school_location.yaml | 2 +- .../tour_mode_choice_annotate_choosers_preprocessor.csv | 2 +- .../trip_mode_choice_annotate_trips_preprocessor.csv | 2 +- resident/configs/workplace_location.yaml | 2 +- resident/extensions/external_location_choice.py | 4 ++-- 8 files changed, 12 insertions(+), 10 deletions(-) diff --git a/resident/configs/accessibility.yaml b/resident/configs/accessibility.yaml index 13b96c2..34ba094 100644 --- a/resident/configs/accessibility.yaml +++ b/resident/configs/accessibility.yaml @@ -1,2 +1,3 @@ # columns from land_use table to add to df land_use_columns: ['EMP_RET', 'EMP_TOTAL', 'TOTHHS', 'walk_dist_local_bus'] +explicit_chunk: 0.2 \ No newline at end of file diff --git a/resident/configs/non_mandatory_tour_destination.yaml b/resident/configs/non_mandatory_tour_destination.yaml index f8daa2c..6813065 100644 --- a/resident/configs/non_mandatory_tour_destination.yaml +++ b/resident/configs/non_mandatory_tour_destination.yaml @@ -3,6 +3,7 @@ SPEC: non_mandatory_tour_destination.csv COEFFICIENTS: non_mandatory_tour_destination_coefficients.csv SAMPLE_SIZE: 30 +ESTIMATION_SAMPLE_SIZE: 30 SIZE_TERM_SELECTOR: non_mandatory diff --git a/resident/configs/school_location.csv b/resident/configs/school_location.csv index 3b07c3d..db0940b 100644 --- a/resident/configs/school_location.csv +++ b/resident/configs/school_location.csv @@ -1,7 +1,7 @@ Label,Description,Expression,university,highschool,gradeschool,preschool -local_dist,,"_DIST@skims[('SOV_M_DIST', 'MD')]",1,1,1,1 +local_dist,,"_DIST@skims[('SOV_M_DIST', 'MD')].fillna(0)",1,1,1,1 util_mode_choice_logsum,Mode choice logsum,mode_choice_logsum,coef_mclogsum_univ,coef_mclogsum_hsch,coef_mclogsum_gs,coef_mclogsum_ps -util_Distance,Distance,@_DIST.clip(upper=50),coef_dist_univ,coef_dist_hsch,coef_dist_gs,coef_dist_ps +util_Distance,Distance,@_DIST.clip(upper=50).fillna(0),coef_dist_univ,coef_dist_hsch,coef_dist_gs,coef_dist_ps util_log_Distance,log_Distance,@np.log(_DIST.clip(upper=50)+1),coef_lndist_univ,coef_zero,coef_zero,coef_lndist_ps util_Distance_squareroot,Squareroot of distance,@_DIST.clip(upper=50)**0.5,coef_sqrtdist_univ,coef_sqrtdist_hsch,coef_sqrtdist_gs,coef_sqrtdist_ps util_Distance_squared,Distance_squared,@_DIST.clip(upper=50)**2,coef_sqrddist_univ,coef_sqrddist_hsch,coef_sqrddist_gs,coef_sqrddist_ps @@ -12,10 +12,10 @@ util_Distance_worker_univ,Distance for a worker_university specific,"@np.where(d util_Distance_largeuniversity_univ,Distance for large university enrollment,"@np.where(df.COLLEGEENROLL>5000, _DIST.clip(upper=50), 0)",coef_univenrol_dist_univ,coef_zero,coef_zero,coef_zero util_Distance _lowincome_prek,Distance - low income,"@np.where(df.income<60000, _DIST.clip(upper=50), 0)",coef_zero,coef_zero,coef_zero,coef_lowincdist_ps util_Distance - age03_prek,Distance - age 0 to 3,"@np.where(df.age<3,_DIST.clip(upper=50),0)",coef_zero,coef_zero,coef_zero,coef_age03dist_ps -util_LoggedSize,Logged Size variable - University specific,@df['size_term'].apply(np.log1p),coef_lnSize,coef_lnSize,coef_lnSize,coef_lnSize +util_LoggedSize,Logged Size variable - University specific,@df['size_term'].fillna(0).apply(np.log1p),coef_lnSize,coef_lnSize,coef_lnSize,coef_lnSize util_no_attractions,no attractions if logged university size is zero,@df['size_term']==0,-999,-999,-999,-999 util_sample_of_corrections_factor,Sample of alternatives correction factor,"@np.minimum(np.log(df.pick_count/df.prob), 60)",1,1,1,1 -util_sp_utility_adjustment,shadow price utility adjustment,@df['shadow_price_utility_adjustment'],1,1,1 +util_sp_utility_adjustment,shadow price utility adjustment,@df['shadow_price_utility_adjustment'].fillna(0),1,1,1 #,,,,,, util_ABM2calibration_0-1miles,ABM2 calibration_0-1miles,@(_DIST<1),coef_zero,coef_abmcalib_01miles,coef_abmcalib_01miles,coef_abmcalib_01miles util_ABM2calibration_1-2miles,ABM2 calibration_1-2miles,@(_DIST<2) * (_DIST>=1),coef_zero,coef_abmcalib_12miles,coef_abmcalib_12miles,coef_abmcalib_12miles diff --git a/resident/configs/school_location.yaml b/resident/configs/school_location.yaml index 7c3c2b1..6ff92cd 100644 --- a/resident/configs/school_location.yaml +++ b/resident/configs/school_location.yaml @@ -1,5 +1,5 @@ SAMPLE_SIZE: 30 -ESTIMATION_SAMPLE_SIZE: 10 +ESTIMATION_SAMPLE_SIZE: 30 SIMULATE_CHOOSER_COLUMNS: - home_zone_id diff --git a/resident/configs/tour_mode_choice_annotate_choosers_preprocessor.csv b/resident/configs/tour_mode_choice_annotate_choosers_preprocessor.csv index eaef019..b6a8802 100644 --- a/resident/configs/tour_mode_choice_annotate_choosers_preprocessor.csv +++ b/resident/configs/tour_mode_choice_annotate_choosers_preprocessor.csv @@ -46,7 +46,7 @@ treat tours as work if tour_type not yet decided,tour_type,"df.get('tour_type', population_density calculated in annotate landuse in acres,_origin_density_measure,"reindex(land_use.population_density, df[orig_col_name]) / 640", population_density calculated in annotate landuse in acres,_dest_density_measure,"reindex(land_use.population_density, df[dest_col_name]) / 640", ,origin_density,"pd.cut(_origin_density_measure, bins=[-np.inf, 500, 2000, 5000, 15000, np.inf], labels=[5, 4, 3, 2, 1]).astype(int)", -,dest_density,"pd.cut(_dest_density_measure, bins=[-np.inf, 500, 2000, 5000, 15000, np.inf], labels=[5, 4, 3, 2, 1]).astype(int)", +,dest_density,"pd.cut(_dest_density_measure.fillna(0), bins=[-np.inf, 500, 2000, 5000, 15000, np.inf], labels=[5, 4, 3, 2, 1]).astype(int)", ,origin_zone_taxi_wait_time_mean,"origin_density.map({k: v for k, v in Taxi_waitTime_mean.items()})", ,origin_zone_taxi_wait_time_sd,"origin_density.map({k: v for k, v in Taxi_waitTime_sd.items()})", ,dest_zone_taxi_wait_time_mean,"dest_density.map({k: v for k, v in Taxi_waitTime_mean.items()})", diff --git a/resident/configs/trip_mode_choice_annotate_trips_preprocessor.csv b/resident/configs/trip_mode_choice_annotate_trips_preprocessor.csv index 5cd5420..698078a 100644 --- a/resident/configs/trip_mode_choice_annotate_trips_preprocessor.csv +++ b/resident/configs/trip_mode_choice_annotate_trips_preprocessor.csv @@ -47,7 +47,7 @@ Description,Target,Expression household_density calculated in annotate_landuse in acres and is converted to sq miles here,_origin_density_measure,"reindex(land_use.population_density, df[orig_col_name])" employment_density calculated in annotate_landuse in acres and is converted to sq miles here,_dest_density_measure,"reindex(land_use.population_density, df[dest_col_name])" ,origin_density,"pd.cut(_origin_density_measure, bins=[-np.inf, 500, 2000, 5000, 15000, np.inf], labels=[5, 4, 3, 2, 1]).astype(int)" -,dest_density,"pd.cut(_dest_density_measure, bins=[-np.inf, 500, 2000, 5000, 15000, np.inf], labels=[5, 4, 3, 2, 1]).astype(int)" +,dest_density,"pd.cut(_dest_density_measure.fillna(0), bins=[-np.inf, 500, 2000, 5000, 15000, np.inf], labels=[5, 4, 3, 2, 1]).astype(int)" Origin MGRA Dwelling Unit Density,oMGRADUDen,"reindex(land_use.duden,df.origin)" Origin MGRA Employment Density,oMGRAEmpDen,"reindex(land_use.empden,df.origin)" Origin MGRA Total Intersections,oMGRATotInt,"reindex(land_use.totint,df.origin)" diff --git a/resident/configs/workplace_location.yaml b/resident/configs/workplace_location.yaml index 59dcc0b..03a0a94 100644 --- a/resident/configs/workplace_location.yaml +++ b/resident/configs/workplace_location.yaml @@ -1,5 +1,5 @@ SAMPLE_SIZE: 30 -ESTIMATION_SAMPLE_SIZE: 10 +ESTIMATION_SAMPLE_SIZE: 30 SIMULATE_CHOOSER_COLUMNS: - income_segment diff --git a/resident/extensions/external_location_choice.py b/resident/extensions/external_location_choice.py index 0b3ee8e..c5c087b 100644 --- a/resident/extensions/external_location_choice.py +++ b/resident/extensions/external_location_choice.py @@ -57,7 +57,7 @@ def external_school_location( estimator = estimation.manager.begin_estimation(state, "external_school_location") if estimator: - write_estimation_specs(estimator, model_settings, model_settings_file_name) + write_estimation_specs(state, estimator, model_settings, model_settings_file_name) persons_df = iterate_location_choice( state=state, @@ -106,7 +106,7 @@ def external_workplace_location( state, "external_workplace_location" ) if estimator: - write_estimation_specs(estimator, model_settings, model_settings_file_name) + write_estimation_specs(state, estimator, model_settings, model_settings_file_name) persons_df = iterate_location_choice( state=state, From 732eb9682d98c6aadeea235f945a2f5c6ff19aee Mon Sep 17 00:00:00 2001 From: David Hensle Date: Mon, 26 Jan 2026 12:05:36 -0800 Subject: [PATCH 03/13] adding missing occupation segment in wlc --- resident/configs/destination_choice_size_terms.csv | 1 + resident/configs/workplace_location.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/resident/configs/destination_choice_size_terms.csv b/resident/configs/destination_choice_size_terms.csv index cdfacae..d0f8540 100644 --- a/resident/configs/destination_choice_size_terms.csv +++ b/resident/configs/destination_choice_size_terms.csv @@ -6,6 +6,7 @@ workplace,mngt_busi_scic_arts,0,0,0.023150473,0.105478261,0.033921273,0.52058869 workplace,prod_trans_move,0,0,0.044941354,0.059838173,0.160289782,0.055761149,0.692090573,0.577996613,0.289719626,0.051119614,0.300100358,0.169102427,0.010851157,0.045537226,0.039515775,0,0,0,0,0,0 workplace,sales_office,0,0,0.011768937,0.051276887,0.174045033,1,0.206618229,0.914135674,0.494698223,0.170534494,0.142953744,0.337702324,0.048183584,0.052183296,0.031177613,0,0,0,0,0,0 workplace,services,0,0,0.03214445,0.013782902,0.002031041,0.064277161,0.007689779,0.04669046,0.477412008,0.145096152,0.403566649,0.565075489,0.264904083,0.154746531,0.079950222,0,0,0,0,0,0 +workplace,missing,0,0,0.03214445,0.013782902,0.002031041,0.064277161,0.007689779,0.04669046,0.477412008,0.145096152,0.403566649,0.565075489,0.264904083,0.154746531,0.079950222,0,0,0,0,0,0 school,preschool,0,0.1888,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0 school,gradeschool,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0 school,highschool,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0 diff --git a/resident/configs/workplace_location.yaml b/resident/configs/workplace_location.yaml index 03a0a94..9a16bcb 100644 --- a/resident/configs/workplace_location.yaml +++ b/resident/configs/workplace_location.yaml @@ -54,7 +54,6 @@ CHOOSER_SEGMENT_COLUMN_NAME: occupation #income_segment CHOOSER_FILTER_COLUMN_NAME: is_internal_worker # FIXME - these are assigned to persons in annotate_persons. we need a better way to manage this -# FIXME - these are not needed for this model and should be re/factored out SEGMENT_IDS: mngt_busi_scic_arts: mngt_busi_scic_arts services: services @@ -63,6 +62,7 @@ SEGMENT_IDS: prod_trans_move: prod_trans_move constr_maint: constr_maint military: military + missing: missing # used for estimation mode only # work_low: 1 # INCOME_SEGMENT_LOW # work_med: 2 # INCOME_SEGMENT_MED # work_high: 3 # INCOME_SEGMENT_HIGH From 89f1223e750df6e3c7f5d57a0916c198067a6728 Mon Sep 17 00:00:00 2001 From: David Hensle Date: Mon, 26 Jan 2026 12:29:57 -0800 Subject: [PATCH 04/13] adding missing occup to shadow pricing --- resident/configs/shadow_pricing.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/resident/configs/shadow_pricing.yaml b/resident/configs/shadow_pricing.yaml index 6321c95..39acbf5 100644 --- a/resident/configs/shadow_pricing.yaml +++ b/resident/configs/shadow_pricing.yaml @@ -47,4 +47,5 @@ workplace_segmentation_targets: prod_trans_move: EMP_TOTAL sales_office: EMP_TOTAL services: EMP_TOTAL + missing: EMP_TOTAL From becafb234343b97eaf23b61408447dca680fa683 Mon Sep 17 00:00:00 2001 From: Will Alexander Date: Wed, 4 Feb 2026 16:53:20 -0500 Subject: [PATCH 05/13] Remove hard-coded alt # from ext worker ident model --- resident/configs/constants.yaml | 1 - .../external_worker_identification.csv | 30 +++++++++---------- .../external_worker_identification.yaml | 2 ++ .../extensions/external_identification.py | 4 ++- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/resident/configs/constants.yaml b/resident/configs/constants.yaml index 8714b0b..50f40ec 100644 --- a/resident/configs/constants.yaml +++ b/resident/configs/constants.yaml @@ -99,7 +99,6 @@ occupation_xwalk: 5: nat_res_cnstr_maint 6: prod_trans_move 999: missing # no occupation - -1: missing # no occupation # RIDEHAIL Settings Taxi_baseFare: 3.00 diff --git a/resident/configs/external_worker_identification.csv b/resident/configs/external_worker_identification.csv index d70b683..3b31005 100644 --- a/resident/configs/external_worker_identification.csv +++ b/resident/configs/external_worker_identification.csv @@ -1,16 +1,16 @@ -Label,Description,Expression,work_external,work_internal -util_dist_to_nearest_ext_station,Distance to nearest external station,dist_to_external_zone,coef_dist_to_nearest_ext_station, +Label,Description,Expression,work_internal,work_external +util_dist_to_nearest_ext_station,Distance to nearest external station,dist_to_external_zone,,coef_dist_to_nearest_ext_station # FIXME just have a single external indicator not counts at the station here and in destination choice size terms,,,, -#util_size_of_nearest_ext_station,Size of nearest external station,"@np.log1p(reindex(land_use.external_work,df.closest_external_zone))",coef_size_of_nearest_ext_station, -util_size_of_nearest_ext_station,Size of nearest external station,"@np.log1p(reindex(land_use.EXTERNAL,df.closest_external_zone))",coef_size_of_nearest_ext_station, -util_dist_lt_2p5,Distance less than 2.5 miles,"@np.where(df.dist_to_external_zone<2.5,1,0)",coef_dist_lt_2p5, -util_part_time,Part time worker,"@np.where(df.pemploy == 2,1,0)",coef_part_time, -util_inc_lt15,Household Income less than $15k,@(df.income<15000),coef_inc_lt15, -util_inc_15_25,Household income $15k-$25k,@(df.income>=15000) & (df.income<25000) ,coef_inc_15_25, -util_inc_25_50,Household Income $25k-$50k,@(df.income>=25000) & (df.income<50000) ,coef_inc_25_50, -util_inc_150_250,Household Income $150k-$50k,@(df.income>=150000) * (df.income<250000) ,coef_inc_150_250, -util_inc_250plus,Household Income $250k+,@(df.income>=250000),coef_inc_250plus, -util_asc,Alternative-specific constant for external worker,1,asc_external_worker, -util_global_switch,Global switch to have everything internal,@NO_EXTERNAL,-999, -util_2016,Constant for pre-COVID conditions,@PRE_COVID,asc_external_2016, -util_calib,Constant for calibration,1,0.35, +#util_size_of_nearest_ext_station,Size of nearest external station,"@np.log1p(reindex(land_use.external_work,df.closest_external_zone))",,coef_size_of_nearest_ext_station +util_size_of_nearest_ext_station,Size of nearest external station,"@np.log1p(reindex(land_use.EXTERNAL,df.closest_external_zone))",,coef_size_of_nearest_ext_station +util_dist_lt_2p5,Distance less than 2.5 miles,"@np.where(df.dist_to_external_zone<2.5,1,0)",,coef_dist_lt_2p5 +util_part_time,Part time worker,"@np.where(df.pemploy == 2,1,0)",,coef_part_time +util_inc_lt15,Household Income less than $15k,@(df.income<15000),,coef_inc_lt15 +util_inc_15_25,Household income $15k-$25k,@(df.income>=15000) & (df.income<25000) ,,coef_inc_15_25 +util_inc_25_50,Household Income $25k-$50k,@(df.income>=25000) & (df.income<50000) ,,coef_inc_25_50 +util_inc_150_250,Household Income $150k-$50k,@(df.income>=150000) * (df.income<250000) ,,coef_inc_150_250 +util_inc_250plus,Household Income $250k+,@(df.income>=250000),,coef_inc_250plus +util_asc,Alternative-specific constant for external worker,1,,asc_external_worker +util_global_switch,Global switch to have everything internal,@NO_EXTERNAL,,-999 +util_2016,Constant for pre-COVID conditions,@PRE_COVID,,asc_external_2016 +util_calib,Constant for calibration,1,,0.35 diff --git a/resident/configs/external_worker_identification.yaml b/resident/configs/external_worker_identification.yaml index e9660b6..fc81619 100644 --- a/resident/configs/external_worker_identification.yaml +++ b/resident/configs/external_worker_identification.yaml @@ -4,6 +4,8 @@ COEFFICIENTS: external_worker_identification_coeffs.csv LOGIT_TYPE: MNL +EXTERNAL_WORKER_ALT: 1 + # boolean column to filter choosers (True means keep) # will only expose these people to the model CHOOSER_FILTER_COLUMN_NAME: is_out_of_home_worker diff --git a/resident/extensions/external_identification.py b/resident/extensions/external_identification.py index 3800a04..2892ab4 100644 --- a/resident/extensions/external_identification.py +++ b/resident/extensions/external_identification.py @@ -38,6 +38,8 @@ class ExternalIdentificationSettings(LogitComponentSettings, extra="forbid"): preprocessor: PreprocessorSettings | None = None + EXTERNAL_WORKER_ALT: int | None = 0 + def determine_closest_external_station( state, choosers, skim_dict, origin_col="home_zone_id" @@ -183,7 +185,7 @@ def external_worker_identification( if external_col_name is not None: persons[external_col_name] = ( - (choices == 0).reindex(persons.index).fillna(False).astype(bool) + (choices == model_settings.EXTERNAL_WORKER_ALT).reindex(persons.index).fillna(False).astype(bool) ) if internal_col_name is not None: persons[internal_col_name] = persons[filter_col] & ~persons[external_col_name] From 22291590a4f6014a04ccc5dd04c19544b0627911 Mon Sep 17 00:00:00 2001 From: Will Alexander Date: Tue, 10 Feb 2026 12:13:48 -0500 Subject: [PATCH 06/13] Fix jtf composition spec --- resident/configs/annotate_persons.csv | 2 +- resident/configs/disaggregate_accessibility.yaml | 4 ++-- resident/configs/joint_tour_frequency_composition.csv | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resident/configs/annotate_persons.csv b/resident/configs/annotate_persons.csv index 0bc8398..cf6c983 100644 --- a/resident/configs/annotate_persons.csv +++ b/resident/configs/annotate_persons.csv @@ -65,4 +65,4 @@ travel time sensitivity factor for non-work travel,time_factor_nonwork,"np.minim ,naics_code,_naics_code.astype('int') if all(_naics_code!=0) else 0 #,, # FIXME does filling with -1 make sense here? depends on what the missing values mean,, -occupation categories mapped to workplace segments,occupation,persons.OCCP.fillna(-1).map(occupation_xwalk) +occupation categories mapped to workplace segments,occupation,persons.OCCP.fillna(999).map(occupation_xwalk) diff --git a/resident/configs/disaggregate_accessibility.yaml b/resident/configs/disaggregate_accessibility.yaml index e87dbcd..49358e2 100644 --- a/resident/configs/disaggregate_accessibility.yaml +++ b/resident/configs/disaggregate_accessibility.yaml @@ -92,9 +92,9 @@ CREATE_TABLES: 1: "M" 2: "N" # FIXME this should be updated when occupation_xwalk is updated - OCCCAT: # occupation codes + OCCP: # occupation codes 1: 1 # Management Occupations - 2: -1 # Non-worker + 2: 999 # Non-worker PROTO_TOURS: index_col: proto_tour_id diff --git a/resident/configs/joint_tour_frequency_composition.csv b/resident/configs/joint_tour_frequency_composition.csv index 8a1bb7b..be23d9d 100644 --- a/resident/configs/joint_tour_frequency_composition.csv +++ b/resident/configs/joint_tour_frequency_composition.csv @@ -21,7 +21,7 @@ util_constant_for_2_visiting_tour,Constant for 2 visiting tour,@df.social==2,coe util_constant_for_1_visiting_tour,Constant for 1 visiting tour,@df.social==2,coef_constant_for_1_visiting_tour util_1_visiting_and_1_discretionary_tour,1 Visiting and 1 Discretionary Tour,@((df.social==1) & (df.othdiscr==1)),coef_1_visiting_and_1_discretionary_tour util_constant_for_2_discretionary_tour,Constant for 2 discretionary tour,othdiscr==2,coef_constant_for_2_discretionary_tour -util_constant_for_2_discretionary_tour,Constant for 2 discretionary tour,othdiscr==1,coef_constant_for_1_discretionary_tour +util_constant_for_1_discretionary_tour,Constant for 1 discretionary tour,othdiscr==1,coef_constant_for_1_discretionary_tour util_number_of_active_full_time_workers_shopping,Number of Active Full time workers /Shopping,num_travel_active_full_time_workers * shopping,coef_number_of_active_full_time_workers_shopping util_number_of_active_nonworkers_shopping,Number of Active Non-workers /Shopping,num_travel_active_non_workers * shopping,coef_number_of_active_nonworkers_shopping util_number_of_active_pre_driving_age_school_children_shopping,Number of Active Pre- Driving Age School Children /Shopping,num_travel_active_pre_driving_age_school_kids * shopping,coef_number_of_active_pre_driving_age_school_children_shopping From b62c9bec9021ee127c87ccc3b78813f3bb8d5eef Mon Sep 17 00:00:00 2001 From: Will Alexander Date: Mon, 16 Feb 2026 11:43:46 -0500 Subject: [PATCH 07/13] Fix dupl. label in nm tour destination spec --- resident/configs/non_mandatory_tour_destination.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resident/configs/non_mandatory_tour_destination.csv b/resident/configs/non_mandatory_tour_destination.csv index 116360a..754a4db 100644 --- a/resident/configs/non_mandatory_tour_destination.csv +++ b/resident/configs/non_mandatory_tour_destination.csv @@ -71,4 +71,4 @@ util_joint_0_2_ASC,joint tours in 0_2bin calibration constant,"@(_DIST.between(0 util_joint_2_5_ASC,joint tours in 2_5bin calibration constant,"@(_DIST.between(2,5)) * (df.get('tour_category', default=False) == 'joint')",,coef_abm3_dist_2_5jointmaint_asc,coef_abm3_dist_2_5jointdisc_asc,coef_abm3_dist_2_5jointmaint_asc,coef_abm3_dist_2_5jointdisc_asc,coef_abm3_dist_2_5jointdisc_asc util_joint_5_10_ASC,joint tours in 5_10bin calibration constant,"@(_DIST.between(5,10)) * (df.get('tour_category', default=False) == 'joint')",,coef_abm3_dist_5_10jointmaint_asc,coef_abm3_dist_5_10jointdisc_asc,coef_abm3_dist_5_10jointmaint_asc,coef_abm3_dist_5_10jointdisc_asc,coef_abm3_dist_5_10jointdisc_asc util_joint_10_30_ASC,joint tours in 10_30bin calibration constant,"@(_DIST.between(10,30)) * (df.get('tour_category', default=False) == 'joint')",,coef_abm3_dist_10_30jointmaint_asc,coef_abm3_dist_10_30jointdisc_asc,coef_abm3_dist_10_30jointmaint_asc,coef_abm3_dist_10_30jointdisc_asc,coef_abm3_dist_10_30jointdisc_asc -util_indiv_dist_ASC,indiv tours in distance calibration constant,"@(_DIST) * (df.get('tour_category', default=False) == 'joint')",,0.10,,0.10,, \ No newline at end of file +util_joint_dist_ASC,indiv tours in distance calibration constant,"@(_DIST) * (df.get('tour_category', default=False) == 'joint')",,0.10,,0.10,, \ No newline at end of file From 74a57dd08298986f4cf378906cfd10a8e5f75026 Mon Sep 17 00:00:00 2001 From: Will Alexander Date: Mon, 16 Feb 2026 17:52:58 -0500 Subject: [PATCH 08/13] updated nm size terms --- resident/configs/destination_choice_size_terms.csv | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resident/configs/destination_choice_size_terms.csv b/resident/configs/destination_choice_size_terms.csv index 17c1fa1..f26f0e5 100644 --- a/resident/configs/destination_choice_size_terms.csv +++ b/resident/configs/destination_choice_size_terms.csv @@ -11,10 +11,10 @@ school,gradeschool,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0 school,highschool,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0 school,university,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0 non_mandatory,escort,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0 -non_mandatory,shopping,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0 +non_mandatory,shopping,0.01,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0 non_mandatory,othmaint,0,0,0,0,0,0,0,1.60296,0.42255,0.42255,0,0.24001,0,0,0,0,0,0,0,0,0 -non_mandatory,eatout,0.5512,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -non_mandatory,social,0.3006,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +non_mandatory,eatout,0.5512,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0 +non_mandatory,social,0.3006,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0 non_mandatory,othdiscr,0.04333,0,0,0,0,0.004465,0,0.042025,0.004465,0,1,0.005953,0,0.20337,0.03453,0,0.03167,0.05136,0.02258,3.71685,0 atwork,atwork,0,0,0,0,0,0,0,0.104,0.0145,0,0,0,0,0,0,0,0,0,0,0,0 trip,work,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0 From 75492fde1292b5505450d7c1e9804ce3f0603fbc Mon Sep 17 00:00:00 2001 From: Will Alexander Date: Tue, 17 Feb 2026 17:56:34 -0500 Subject: [PATCH 09/13] ISO timestamp format --- resident/configs/logging.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resident/configs/logging.yaml b/resident/configs/logging.yaml index 71162f1..0843782 100644 --- a/resident/configs/logging.yaml +++ b/resident/configs/logging.yaml @@ -46,12 +46,12 @@ logging: class: logging.Formatter # format: '%(levelname)s - %(name)s - %(message)s' format: '%(levelname)s - %(message)s' - datefmt: '%d/%m/%Y %H:%M:%S' + datefmt: '%Y-%m-%dT%H:%M:%S%:z' fileFormatter: class: logging.Formatter format: '%(asctime)s - %(levelname)s - %(name)s - %(message)s' - datefmt: '%d/%m/%Y %H:%M:%S' + datefmt: '%Y-%m-%dT%H:%M:%S%:z' elapsedFormatter: (): activitysim.core.tracing.ElapsedTimeFormatter From d23e48311f9abd9d22ead260cf34e2d7cf58b6e5 Mon Sep 17 00:00:00 2001 From: Will Alexander Date: Thu, 19 Feb 2026 14:32:04 -0500 Subject: [PATCH 10/13] more dest size terms updates --- resident/configs/destination_choice_size_terms.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resident/configs/destination_choice_size_terms.csv b/resident/configs/destination_choice_size_terms.csv index f26f0e5..9dcb720 100644 --- a/resident/configs/destination_choice_size_terms.csv +++ b/resident/configs/destination_choice_size_terms.csv @@ -12,9 +12,9 @@ school,highschool,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0 school,university,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0 non_mandatory,escort,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0 non_mandatory,shopping,0.01,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0 -non_mandatory,othmaint,0,0,0,0,0,0,0,1.60296,0.42255,0.42255,0,0.24001,0,0,0,0,0,0,0,0,0 +non_mandatory,othmaint,0,0,0,0.1,0,0,0.1,1.60296,0.42255,0.42255,0.1,0.24001,0,0,0,0,0,0,0,0,0 non_mandatory,eatout,0.5512,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0 -non_mandatory,social,0.3006,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0 +non_mandatory,social,0.3006,0,0,0,0,0,0,0.1,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0 non_mandatory,othdiscr,0.04333,0,0,0,0,0.004465,0,0.042025,0.004465,0,1,0.005953,0,0.20337,0.03453,0,0.03167,0.05136,0.02258,3.71685,0 atwork,atwork,0,0,0,0,0,0,0,0.104,0.0145,0,0,0,0,0,0,0,0,0,0,0,0 trip,work,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0 From 5f1e2a578c5bc4c7669b815eae996d39de2dc2f4 Mon Sep 17 00:00:00 2001 From: Will Alexander Date: Thu, 19 Feb 2026 14:51:25 -0500 Subject: [PATCH 11/13] update trace label for Ext NMT Ident --- resident/extensions/external_identification.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resident/extensions/external_identification.py b/resident/extensions/external_identification.py index 2892ab4..2a99e2b 100644 --- a/resident/extensions/external_identification.py +++ b/resident/extensions/external_identification.py @@ -306,7 +306,7 @@ def external_non_mandatory_identification( network_los: los.Network_LOS, model_settings: ExternalIdentificationSettings | None = None, model_settings_file_name: str = "external_non_mandatory_identification.yaml", - trace_label: str = "external_non_mandatory_identification", + trace_label: str = "external_non_mandatory_tour_identification", trace_hh_id: bool = False, ) -> None: """ From 2b2e4ce4cbf6826d6c3ad1a00895ffdca9ffb998 Mon Sep 17 00:00:00 2001 From: Will Alexander Date: Thu, 19 Feb 2026 17:29:55 -0500 Subject: [PATCH 12/13] Destination choice size terms made nonzero --- resident/configs/destination_choice_size_terms.csv | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/resident/configs/destination_choice_size_terms.csv b/resident/configs/destination_choice_size_terms.csv index 9dcb720..1de8e16 100644 --- a/resident/configs/destination_choice_size_terms.csv +++ b/resident/configs/destination_choice_size_terms.csv @@ -11,12 +11,12 @@ school,gradeschool,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0 school,highschool,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0 school,university,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0 non_mandatory,escort,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0 -non_mandatory,shopping,0.01,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0 -non_mandatory,othmaint,0,0,0,0.1,0,0,0.1,1.60296,0.42255,0.42255,0.1,0.24001,0,0,0,0,0,0,0,0,0 -non_mandatory,eatout,0.5512,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0 -non_mandatory,social,0.3006,0,0,0,0,0,0,0.1,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0 +non_mandatory,shopping,0.01,0,0,0,0,0,0,1,0.1,0,0,0,0,0,0,0,0,0,0,0,0 +non_mandatory,othmaint,0,0,0.1,0.1,0.1,0.1,0.1,1.60296,0.42255,0.42255,0.1,0.24001,0.1,0.1,0.1,0,0,0,0,0,0 +non_mandatory,eatout,0.5512,0,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,1,0,0,0,0,0,0 +non_mandatory,social,0.3006,0,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0,0,0,0,0,0 non_mandatory,othdiscr,0.04333,0,0,0,0,0.004465,0,0.042025,0.004465,0,1,0.005953,0,0.20337,0.03453,0,0.03167,0.05136,0.02258,3.71685,0 -atwork,atwork,0,0,0,0,0,0,0,0.104,0.0145,0,0,0,0,0,0,0,0,0,0,0,0 +atwork,atwork,0,0,0.1,0.1,0.1,0.1,0.1,0.104,0.0145,0.1,0.1,0.1,0.1,0.1,0.1,0,0,0,0,0,0 trip,work,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0 trip,escort,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0 trip,shopping,0.000001,0,0,0,0,0,0,0.375,0,0,0,0,0,0,0,0.0001,0,0,0,0,0 From 867f5baf6db7075a953567d3f43807fdf48cd7be Mon Sep 17 00:00:00 2001 From: David Hensle <51132108+dhensle@users.noreply.github.com> Date: Thu, 19 Feb 2026 23:10:31 -0800 Subject: [PATCH 13/13] preprocessor adds TAZ column to landuse --- resident/preprocessor.py | 68 +++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/resident/preprocessor.py b/resident/preprocessor.py index aea9f1b..12a4699 100644 --- a/resident/preprocessor.py +++ b/resident/preprocessor.py @@ -67,47 +67,65 @@ def load_data(settings: PreprocessorSettings) -> tuple[pd.DataFrame, pd.DataFram def set_land_use_maz_index(land_use: pd.DataFrame) -> pd.DataFrame: - """Infer the MAZ column, rename it to 'MAZ', and set it as the index. + """Infer the MAZ and TAZ columns, rename them, and set MAZ as the index. Searches for common MAZ column name variations (MAZ, MAZ_ID, MAZ_NO) - and standardizes to 'MAZ' as the DataFrame index. + and TAZ column name variations (TAZ, TAZ_ID, TAZ_NO), standardizes + their names to 'MAZ' and 'TAZ', and sets 'MAZ' as the DataFrame index. Parameters ---------- land_use : pd.DataFrame - Land use table with an MAZ-like column + Land use table with MAZ-like and optionally TAZ-like columns Returns ------- pd.DataFrame - Land use table with 'MAZ' as the index + Land use table with 'MAZ' as the index and 'TAZ' column renamed """ + # --- MAZ --- # If MAZ is already the index, just ensure the name - if land_use.index.name and land_use.index.name.upper() in ['MAZ', 'MAZ_ID', 'MAZ_NO']: + if land_use.index.name and land_use.index.name == 'MAZ': land_use.index.name = 'MAZ' print("land_use index already set to MAZ") - return land_use - - # Search for a MAZ-like column - maz_col = None - for col in land_use.columns: - if col.upper() in ['MAZ', 'MAZ_ID', 'MAZ_NO']: - maz_col = col - break - - if maz_col is None: - raise RuntimeError( - f"Could not identify MAZ column in land_use. " - f"Available columns: {list(land_use.columns)}" - ) + else: + # Search for a MAZ-like column + maz_col = None + for col in land_use.columns: + if col.upper() in ['MAZ', 'MAZ_ID', 'MAZ_NO']: + maz_col = col + break + + if maz_col is None: + raise RuntimeError( + f"Could not identify MAZ column in land_use. " + f"Available columns: {list(land_use.columns)}" + ) + + # Rename to 'MAZ' if needed, then set as index + if maz_col != 'MAZ': + land_use = land_use.rename(columns={maz_col: 'MAZ'}) + print(f"Renamed land_use column '{maz_col}' to 'MAZ'") + + land_use = land_use.set_index('MAZ') + print(f"Set land_use index to 'MAZ' ({len(land_use)} zones)") - # Rename to 'MAZ' if needed, then set as index - if maz_col != 'MAZ': - land_use = land_use.rename(columns={maz_col: 'MAZ'}) - print(f"Renamed land_use column '{maz_col}' to 'MAZ'") + # --- TAZ --- + if 'TAZ' in land_use.columns: + print("TAZ column already exists in land_use") + else: + taz_col = None + for col in land_use.columns: + if col.upper() in ['TAZ', 'TAZ_ID', 'TAZ_NO']: + taz_col = col + break + + if taz_col is None: + print("Warning: Could not identify TAZ column in land_use, skipping TAZ rename") + else: + land_use = land_use.rename(columns={taz_col: 'TAZ'}) + print(f"Renamed land_use column '{taz_col}' to 'TAZ'") - land_use = land_use.set_index('MAZ') - print(f"Set land_use index to 'MAZ' ({len(land_use)} zones)") return land_use