diff --git a/activitysim/abm/models/util/test/test_vehicle_type_alts.py b/activitysim/abm/models/util/test/test_vehicle_type_alts.py new file mode 100644 index 000000000..d9cf1c176 --- /dev/null +++ b/activitysim/abm/models/util/test/test_vehicle_type_alts.py @@ -0,0 +1,59 @@ +# ActivitySim +# See full license in LICENSE.txt. + +import pandas as pd +import pandas.testing as pdt + +from activitysim.abm.models.vehicle_type_choice import ( + get_combinatorial_vehicle_alternatives, + construct_model_alternatives, + VehicleTypeChoiceSettings, +) +from activitysim.core import workflow + + +def test_vehicle_type_alts(): + state = workflow.State.make_default(__file__) + + alts_cats_dict = { + "body_type": ["Car", "SUV"], + "fuel_type": ["Gas", "BEV"], + "age": [1, 2, 3], + } + + alts_wide, alts_long = get_combinatorial_vehicle_alternatives(alts_cats_dict) + + # alts are initially constructed combinatorially + assert len(alts_long) == 12, "alts_long should have 12 rows" + assert len(alts_wide) == 12, "alts_wide should have 12 rows" + + model_settings = VehicleTypeChoiceSettings.model_construct() + model_settings.combinatorial_alts = alts_cats_dict + model_settings.PROBS_SPEC = None + model_settings.WRITE_OUT_ALTS_FILE = False + + # constructing veh type data with missing alts + vehicle_type_data = pd.DataFrame( + data={ + "body_type": ["Car", "Car", "Car", "SUV", "SUV"], + "fuel_type": ["Gas", "Gas", "BEV", "Gas", "BEV"], + "age": ["1", "2", "3", "1", "2"], + "dummy_data": [1, 2, 3, 4, 5], + }, + index=[0, 1, 2, 3, 4], + ) + + alts_wide, alts_long = construct_model_alternatives( + state, model_settings, alts_cats_dict, vehicle_type_data + ) + + # should only have alts left that are in the file + assert len(alts_long) == 5, "alts_long should have 5 rows" + + # indexes need to be the same to choices match alts + pdt.assert_index_equal(alts_long.index, alts_wide.index) + + # columns need to be in correct order for downstream configs + pdt.assert_index_equal( + alts_long.columns, pd.Index(["body_type", "age", "fuel_type"]) + ) diff --git a/activitysim/abm/models/vehicle_type_choice.py b/activitysim/abm/models/vehicle_type_choice.py index a2ea7b0bd..071061cb8 100644 --- a/activitysim/abm/models/vehicle_type_choice.py +++ b/activitysim/abm/models/vehicle_type_choice.py @@ -244,7 +244,19 @@ def construct_model_alternatives( ), f"missing vehicle data for alternatives:\n {missing_alts}" else: # eliminate alternatives if no vehicle type data + num_alts_before_filer = len(alts_wide) alts_wide = alts_wide[alts_wide._merge != "left_only"] + logger.warning( + f"Removed {num_alts_before_filer - len(alts_wide)} alternatives not included in input vehicle type data." + ) + # need to also remove any alts from alts_long + alts_long.set_index(["body_type", "age", "fuel_type"], inplace=True) + alts_long = alts_long[ + alts_long.index.isin( + alts_wide.set_index(["body_type", "age", "fuel_type"]).index + ) + ].reset_index() + alts_long.index = alts_wide.index alts_wide.drop(columns="_merge", inplace=True) # converting age to integer to allow interactions in utilities @@ -455,11 +467,11 @@ def iterate_vehicle_type_choice( alts = ( alts_long[alts_long.columns] .apply(lambda row: "_".join(row.values.astype(str)), axis=1) - .values + .to_dict() ) else: - alts = model_spec.columns - choices["vehicle_type"] = choices["vehicle_type"].map(dict(enumerate(alts))) + alts = enumerate(dict(model_spec.columns)) + choices["vehicle_type"] = choices["vehicle_type"].map(alts) # STEP II: append probabilistic vehicle type attributes if probs_spec_file is not None: