diff --git a/activitysim/abm/models/trip_destination.py b/activitysim/abm/models/trip_destination.py index 853cfc35e..407d50844 100644 --- a/activitysim/abm/models/trip_destination.py +++ b/activitysim/abm/models/trip_destination.py @@ -30,12 +30,12 @@ ) from activitysim.core.configuration.base import PreprocessorSettings from activitysim.core.configuration.logit import LocationComponentSettings +from activitysim.core.exceptions import DuplicateWorkflowTableError, InvalidTravelError from activitysim.core.interaction_sample import interaction_sample from activitysim.core.interaction_sample_simulate import interaction_sample_simulate from activitysim.core.skim_dictionary import DataFrameMatrix from activitysim.core.tracing import print_elapsed_time from activitysim.core.util import assign_in_place, reindex -from activitysim.core.exceptions import InvalidTravelError, DuplicateWorkflowTableError logger = logging.getLogger(__name__) @@ -273,12 +273,16 @@ def destination_sample( return choices -def aggregate_size_term_matrix(maz_size_term_matrix, network_los): +def aggregate_size_term_matrix(maz_size_term_matrix, network_los, all_tazs=None): df = maz_size_term_matrix.df assert ALT_DEST_TAZ not in df dest_taz = network_los.map_maz_to_taz(df.index) taz_size_term_matrix = df.groupby(dest_taz).sum() + if all_tazs is not None: + taz_size_term_matrix = taz_size_term_matrix.reindex( + all_tazs, fill_value=0 + ).rename_axis(taz_size_term_matrix.index.name, axis=0) taz_size_term_matrix = DataFrameMatrix(taz_size_term_matrix) @@ -612,7 +616,16 @@ def destination_presample( alt_dest_col_name = model_settings.ALT_DEST_COL_NAME - TAZ_size_term_matrix = aggregate_size_term_matrix(size_term_matrix, network_los) + if state.settings.sharrow: + # when using sharrow, we use the skim_dataset structure, and need to ensure + # that all TAZs are represented in the size_term_matrix, even those with no MAZs + all_tazs = state.get_dataframe("land_use_taz").index + else: + all_tazs = None + + TAZ_size_term_matrix = aggregate_size_term_matrix( + size_term_matrix, network_los, all_tazs + ) TRIP_ORIGIN = model_settings.TRIP_ORIGIN PRIMARY_DEST = model_settings.PRIMARY_DEST @@ -627,6 +640,17 @@ def destination_presample( network_los.map_maz_to_taz(alternatives.index) ).sum() + # We now have aggregated alternatives indexed by TAZ instead of MAZ. + # For sharrow, we need the TAZ indexing to be "complete", i.e. include all TAZ ids, + # even those that had no MAZs (and so were missing from the aggregation result). + # this is needed because we are going to taking the entire set of TAZ alternatives + # as a vector which will need to align with the TAZ skims. + if state.settings.sharrow: + all_tazs = state.get_dataframe("land_use_taz").index + alternatives = alternatives.reindex(all_tazs, fill_value=0).rename_axis( + alternatives.index.name, axis=0 + ) + # # i did this but after changing alt_dest_col_name to 'trip_dest' it # # shouldn't be needed anymore # alternatives.index.name = ALT_DEST_TAZ @@ -1520,13 +1544,13 @@ def run_trip_destination( """ When using the trip destination model with sharrow, it is necessary - to set a value for `purpose_index_num` in the trip destination - annotate trips preprocessor. This allows for an optimized compiled + to set a value for `purpose_index_num` in the trip destination + annotate trips preprocessor. This allows for an optimized compiled lookup of the size term from the array of size terms. The value of - `purpose_index_num` should be the integer column position in the size - matrix, with usual zero-based numpy indexing semantics (i.e. the first + `purpose_index_num` should be the integer column position in the size + matrix, with usual zero-based numpy indexing semantics (i.e. the first column is zero). The preprocessor expression most likely needs to be - "size_terms.get_cols(df.purpose)" unless some unusual transform of + "size_terms.get_cols(df.purpose)" unless some unusual transform of size terms has been employed. """ diff --git a/activitysim/abm/test/trip_dest/configs/_dummy_coefficients.csv b/activitysim/abm/test/trip_dest/configs/_dummy_coefficients.csv new file mode 100644 index 000000000..9d471534a --- /dev/null +++ b/activitysim/abm/test/trip_dest/configs/_dummy_coefficients.csv @@ -0,0 +1,2 @@ +coefficient_name,value,constrain +coef_one,1,T diff --git a/activitysim/abm/test/trip_dest/configs/constants.yaml b/activitysim/abm/test/trip_dest/configs/constants.yaml new file mode 100644 index 000000000..b093206ab --- /dev/null +++ b/activitysim/abm/test/trip_dest/configs/constants.yaml @@ -0,0 +1,3 @@ +max_walk_distance: 3 +walkSpeed: 3.00 +walkThresh: 1.50 diff --git a/activitysim/abm/test/trip_dest/configs/destination_choice_size_terms.csv b/activitysim/abm/test/trip_dest/configs/destination_choice_size_terms.csv new file mode 100644 index 000000000..5d0b192b8 --- /dev/null +++ b/activitysim/abm/test/trip_dest/configs/destination_choice_size_terms.csv @@ -0,0 +1,24 @@ +segment,model_selector,Acres,Population +work_low,workplace,0,0.602938343 +work_med,workplace,0,0.487310988 +work_high,workplace,0,0.359519129 +work_veryhigh,workplace,0,0.169128839 +university,school,1,0 +preschool,school,0.07552,1 +gradeschool,school,1,0 +highschool,school,1,0 +escort,non_mandatory,0.465,0.144 +shopping,non_mandatory,1,0 +eatout,non_mandatory,0.4,0.6 +othmaint,non_mandatory,0,0.518 +social,non_mandatory,0.2,0.478 +othdiscr,non_mandatory,0.252252252,0.272272272 +atwork,atwork,0,0.258 +work,trip,0.2,0.166666667 +escort,trip,0.655,0.144 +shopping,trip,0.86,0 +eatout,trip,0.2,1 +othmaint,trip,0.001,0.518 +social,trip,0.3,0.478 +othdiscr,trip,0.252252252,0.272272272 +univ,trip,0.999001,0 \ No newline at end of file diff --git a/activitysim/abm/test/trip_dest/configs/network_los.yaml b/activitysim/abm/test/trip_dest/configs/network_los.yaml new file mode 100644 index 000000000..fdc77dc2f --- /dev/null +++ b/activitysim/abm/test/trip_dest/configs/network_los.yaml @@ -0,0 +1,14 @@ +read_skim_cache: False +write_skim_cache: False +zone_system: 2 +taz_skims: + omx: min*.omx +maz: maz.csv +maz_to_maz: + tables: + - maz_to_maz_walk.csv +skim_time_periods: + time_window: 1440 + period_minutes: 60 + periods: [0, 5, 9, 15, 18, 20, 24] + labels: ['EA', 'AM', 'MD', 'PM', 'EV', 'EA'] diff --git a/activitysim/abm/test/trip_dest/configs/settings.yaml b/activitysim/abm/test/trip_dest/configs/settings.yaml new file mode 100644 index 000000000..6cd0132bd --- /dev/null +++ b/activitysim/abm/test/trip_dest/configs/settings.yaml @@ -0,0 +1,56 @@ +sharrow: test +recode_pipeline_columns: true + +expression_profile: False +households_sample_size: 0 +sharrow_cache_dir: sharrow_cache +chunk_training_mode: explicit +check_for_variability: False +use_shadow_pricing: False +want_dest_choice_sample_tables: False +trace_hh_id: +cleanup_trace_files_on_resume: True + +input_table_list: + - tablename: households + filename: households.csv + index_col: household_id + rename_columns: + MAZ: home_zone_id + hhno: household_id + recode_columns: + home_zone_id: land_use.zone_id + keep_columns: + - home_zone_id + - income + - tablename: persons + filename: persons.csv + index_col: person_id + rename_columns: + PERID: person_id + keep_columns: + - household_id + - age + - PNUM + - tablename: land_use # MAZ,TAZ,Acres,Population + filename: land_use.csv + index_col: zone_id + rename_columns: + MAZ: zone_id + recode_columns: + zone_id: zero-based + TAZ: land_use_taz.TAZ + keep_columns: + - TAZ + - Acres + - Population + - tablename: land_use_taz + filename: taz.csv + index_col: TAZ + recode_columns: + TAZ: zero-based + +resume_after: + +models: + - trip_destination diff --git a/activitysim/abm/test/trip_dest/configs/trip_destination.csv b/activitysim/abm/test/trip_dest/configs/trip_destination.csv new file mode 100644 index 000000000..fcd8b48df --- /dev/null +++ b/activitysim/abm/test/trip_dest/configs/trip_destination.csv @@ -0,0 +1,10 @@ +Description,Expression,work,univ,school,escort,shopping,eatout,othmaint,social,othdiscr,atwork +size term,"@np.log1p(size_terms.get(df.alt_dest, df.purpose)) # sharrow: np.log1p(size_terms['sizearray'])",1,1,1,1,1,1,1,1,1,1 +no attractions,"@size_terms.get(df.alt_dest, df.purpose) == 0 # sharrow: size_terms['sizearray'] == 0",-999,-999,-999,-999,-999,-999,-999,-999,-999,-999 +stop proximity to home (outbound),@df.outbound * od_skims['distwalk'],-0.3800,0,0,0,0,0,0,0,0,0 +stop proximity to home (inbound),@~df.outbound * dp_skims['distwalk'],-0.1500,0,0,0,0,0,0,0,0,0 +stop proximity to main destination (outbound),@df.outbound * dp_skims['distwalk'],-0.26,,,,,,,,, +"Sample of alternatives correction factor","@np.minimum(np.log(df.pick_count/df.prob), 60)",1,1,1,1,1,1,1,1,1,1 +Mode choice logsum from origin to stop,od_logsum,1.821,1.821,1.821,1.821,1.821,1.821,1.821,1.821,1.821,1.821 +Mode choice logsum from stop to destination,dp_logsum,1.821,1.821,1.821,1.821,1.821,1.821,1.821,1.821,1.821,1.821 +meaty,@odt_skims['meat'],-0.01,-0.01,-0.01,-0.01,-0.01,-0.01,-0.01,-0.01,-0.01,-0.01 diff --git a/activitysim/abm/test/trip_dest/configs/trip_destination.yaml b/activitysim/abm/test/trip_dest/configs/trip_destination.yaml new file mode 100644 index 000000000..a4a2c418e --- /dev/null +++ b/activitysim/abm/test/trip_dest/configs/trip_destination.yaml @@ -0,0 +1,35 @@ +SAMPLE_SIZE: 30 + +DESTINATION_SAMPLE_SPEC: trip_destination_sample.csv +DESTINATION_SPEC: trip_destination.csv +COEFFICIENTS: _dummy_coefficients.csv + +LOGSUM_SETTINGS: trip_mode_choice.yaml + +# optional (comment out if not desired) +DEST_CHOICE_LOGSUM_COLUMN_NAME: destination_logsum + +# comment out DEST_CHOICE_LOGSUM_COLUMN_NAME if saved alt logsum table +DEST_CHOICE_SAMPLE_TABLE_NAME: trip_destination_sample + +# model-specific logsum-related settings +TRIP_ORIGIN: origin +ALT_DEST_COL_NAME: alt_dest +PRIMARY_DEST: destination + +REDUNDANT_TOURS_MERGED_CHOOSER_COLUMNS: + - tour_mode + +preprocessor: + SPEC: trip_destination_annotate_trips_preprocessor + DF: trips + TABLES: + - tours + - persons + +# drop failed trips and cleanup failed trip leg_mates for consistency +# (i.e. adjust trip_count, trip_num, first for missing failed trips) +CLEANUP: False + +# this setting is used by testing code to force failed trip_destination +# fail_some_trips_for_testing: False diff --git a/activitysim/abm/test/trip_dest/configs/trip_destination_annotate_trips_preprocessor.csv b/activitysim/abm/test/trip_dest/configs/trip_destination_annotate_trips_preprocessor.csv new file mode 100644 index 000000000..157ae07b4 --- /dev/null +++ b/activitysim/abm/test/trip_dest/configs/trip_destination_annotate_trips_preprocessor.csv @@ -0,0 +1,9 @@ +Description,Target,Expression +,tour_mode,"reindex(tours.tour_mode, df.tour_id)" +,_tod,"np.where(df.outbound,reindex_i(tours.start, df.tour_id),reindex_i(tours.end, df.tour_id))" +,trip_period,network_los.skim_time_period_label(_tod) +,is_joint,"reindex(tours.tour_category, df.tour_id)=='joint'" +,purpose_index_num,"size_terms.get_cols(df.purpose)" +,tour_mode_is_walk,"reindex(tours.tour_mode, df.tour_id)=='WALK'" +,tour_mode_is_bike,"reindex(tours.tour_mode, df.tour_id)=='BIKE'" +,tour_leg_dest,"np.where(df.outbound,reindex(tours.destination, df.tour_id), reindex(tours.origin, df.tour_id)).astype(int)" diff --git a/activitysim/abm/test/trip_dest/configs/trip_destination_sample.csv b/activitysim/abm/test/trip_dest/configs/trip_destination_sample.csv new file mode 100644 index 000000000..549e5e2da --- /dev/null +++ b/activitysim/abm/test/trip_dest/configs/trip_destination_sample.csv @@ -0,0 +1,8 @@ +Description,Expression,work,univ,school,escort,shopping,eatout,othmaint,social,othdiscr,atwork +,_od_DIST@od_skims['distwalk'],1,1,1,1,1,1,1,1,1,1 +,_dp_DIST@dp_skims['distwalk'],1,1,1,1,1,1,1,1,1,1 +size term,"@np.log1p(size_terms.get(df.alt_dest, df.purpose)) # sharrow: np.log1p(size_terms['sizearray'])",1,1,1,1,1,1,1,1,1,1 +no attractions,"@size_terms.get(df.alt_dest, df.purpose) == 0 # sharrow: size_terms['sizearray'] == 0",-999,-999,-999,-999,-999,-999,-999,-999,-999,-999 +stop proximity to home (outbound),@df.outbound * _od_DIST,-0.3800,0,0,0,0,0,0,0,0,0 +stop proximity to home (inbound),@~df.outbound * _od_DIST,-0.1500,0,0,0,0,0,0,0,0,0 +stop proximity to main destination (outbound),@df.outbound * _dp_DIST,-0.26,,,,,,,,, diff --git a/activitysim/abm/test/trip_dest/configs/trip_mode_choice.csv b/activitysim/abm/test/trip_dest/configs/trip_mode_choice.csv new file mode 100644 index 000000000..947247bba --- /dev/null +++ b/activitysim/abm/test/trip_dest/configs/trip_mode_choice.csv @@ -0,0 +1,6 @@ +Label,Description,Expression,DRIVEALONEFREE,SHAREDRIDE,WALK +#,Drive alone ,,,, +util_DRIVEALONEFREE_Unavailable_for_persons_less_than_16,DRIVEALONEFREE - Unavailable for persons less than 16,age < 16,-999,, +util_DRIVEALONEFREE_Person_is_between_16_and_19_years_old,DRIVEALONEFREE - Person is between 16 and 19 years old,@(df.age >= 16) & (df.age <= 19),coef_age1619_da,, +util_SHARED2FREE_In_vehicle_time,SHARED2FREE - In-vehicle time,@odt_skims['meat'],,coef_ivt, +util_WALK_Time_short,WALK - Time up to 1.5 miles,@od_skims['distwalk'],,,coef_walktime_short diff --git a/activitysim/abm/test/trip_dest/configs/trip_mode_choice.yaml b/activitysim/abm/test/trip_dest/configs/trip_mode_choice.yaml new file mode 100644 index 000000000..557e0dd82 --- /dev/null +++ b/activitysim/abm/test/trip_dest/configs/trip_mode_choice.yaml @@ -0,0 +1,52 @@ +# trip_mode_choice.yaml + +compute_settings: + fastmath: false # use of isnan in utility functions requires fastmath=False + +SPEC: trip_mode_choice.csv +COEFFICIENTS: trip_mode_choice_coefficients.csv +COEFFICIENT_TEMPLATE: trip_mode_choice_coefficients_template.csv + +LOGIT_TYPE: NL + +NESTS: + name: root + coefficient: coef_nest_root + alternatives: + - name: AUTO + coefficient: coef_nest_AUTO + alternatives: + - DRIVEALONEFREE + - SHAREDRIDE + - name: NONMOTORIZED + coefficient: coef_nest_NONMOTORIZED + alternatives: + - WALK + +CONSTANTS: + orig_col_name: origin + dest_col_name: destination + I_MODE_MAP: + DRIVEALONEFREE: 1 + SHAREDRIDE: 2 + WALK: 3 + I_SOV_MODES: 1 + I_AUTO_MODES: [1,2] + I_WALK_MODE: 3 + +# so far, we can use the same spec as for non-joint tours +preprocessor: + SPEC: trip_mode_choice_annotate_trips_preprocessor + DF: df + TABLES: + - land_use + - tours + +MODE_CHOICE_LOGSUM_COLUMN_NAME: mode_choice_logsum + +TOURS_MERGED_CHOOSER_COLUMNS: + - tour_category + - tour_mode + - start + - end + - age diff --git a/activitysim/abm/test/trip_dest/configs/trip_mode_choice_annotate_trips_preprocessor.csv b/activitysim/abm/test/trip_dest/configs/trip_mode_choice_annotate_trips_preprocessor.csv new file mode 100644 index 000000000..e0d2844e0 --- /dev/null +++ b/activitysim/abm/test/trip_dest/configs/trip_mode_choice_annotate_trips_preprocessor.csv @@ -0,0 +1,8 @@ +Description,Target,Expression +,i_tour_mode,df.tour_mode.map(I_MODE_MAP) +,tour_mode_is_SOV,i_tour_mode == I_SOV_MODES +,tour_mode_is_auto,i_tour_mode.isin(I_AUTO_MODES) +,tour_mode_is_walk,i_tour_mode == I_WALK_MODE +,inbound,~df.outbound +,first_trip,df.trip_num == 1 +,last_trip,df.trip_num == df.trip_count \ No newline at end of file diff --git a/activitysim/abm/test/trip_dest/configs/trip_mode_choice_coefficients.csv b/activitysim/abm/test/trip_dest/configs/trip_mode_choice_coefficients.csv new file mode 100644 index 000000000..4618bf244 --- /dev/null +++ b/activitysim/abm/test/trip_dest/configs/trip_mode_choice_coefficients.csv @@ -0,0 +1,32 @@ +coefficient_name,value,constrain +coef_one,1,T +coef_zero,0,T +coef_nest_root,1,T +coef_nest_AUTO,0.72,T +coef_nest_AUTO_DRIVEALONE,0.35,T +coef_nest_AUTO_SHAREDRIDE2,0.35,T +coef_nest_AUTO_SHAREDRIDE3,0.35,T +coef_nest_NONMOTORIZED,0.72,T +coef_nest_TRANSIT,0.72,T +coef_nest_TRANSIT_WALKACCESS,0.5,T +coef_nest_TRANSIT_DRIVEACCESS,0.5,T +coef_nest_RIDEHAIL,0.36,T +coef_nest_SCHOOL_BUS,0.5,T +coef_ivt_disc,-0.03,F +coef_ivt_maint,-0.034,F +coef_ivt_school,-0.02,F +coef_ivt_univ,-0.032,F +coef_ivt_work,-0.01,F +coef_ivt_atwork,-0.064,F +coef_walktime_short_disc,-0.0558,F +coef_walktime_short_maint,-0.0558,F +coef_walktime_short_school,-0.0542,F +coef_walktime_short_univ,-0.0542,F +coef_walktime_short_work,-0.1,F +coef_walktime_short_atwork,-0.0558,F +coef_age1619_da_disc,0,F +coef_age1619_da_maint,0,F +coef_age1619_da_school,0,F +coef_age1619_da_univ,0,F +coef_age1619_da_work,0,F +coef_age1619_da_atwork,0,F diff --git a/activitysim/abm/test/trip_dest/configs/trip_mode_choice_coefficients_template.csv b/activitysim/abm/test/trip_dest/configs/trip_mode_choice_coefficients_template.csv new file mode 100644 index 000000000..8e3f77d91 --- /dev/null +++ b/activitysim/abm/test/trip_dest/configs/trip_mode_choice_coefficients_template.csv @@ -0,0 +1,17 @@ +coefficient_name,eatout,escort,othdiscr,othmaint,school,shopping,social,univ,work,atwork +coef_one,,,,,,,,,, +coef_nest_root,,,,,,,,,, +coef_nest_AUTO,,,,,,,,,, +coef_nest_AUTO_DRIVEALONE,,,,,,,,,, +coef_nest_AUTO_SHAREDRIDE2,,,,,,,,,, +coef_nest_AUTO_SHAREDRIDE3,,,,,,,,,, +coef_nest_NONMOTORIZED,,,,,,,,,, +coef_nest_TRANSIT,,,,,,,,,, +coef_nest_TRANSIT_WALKACCESS,,,,,,,,,, +coef_nest_TRANSIT_DRIVEACCESS,,,,,,,,,, +coef_nest_RIDEHAIL,,,,,,,,,, +coef_nest_SCHOOL_BUS,,,,,,,,,, +#,,,,,,,,,, +coef_ivt,coef_ivt_disc,coef_ivt_maint,coef_ivt_disc,coef_ivt_maint,coef_ivt_school,coef_ivt_maint,coef_ivt_disc,coef_ivt_univ,coef_ivt_work,coef_ivt_atwork +coef_walktime_short,coef_walktime_short_disc,coef_walktime_short_maint,coef_walktime_short_disc,coef_walktime_short_maint,coef_walktime_short_school,coef_walktime_short_maint,coef_walktime_short_disc,coef_walktime_short_univ,coef_walktime_short_work,coef_walktime_short_atwork +coef_age1619_da,coef_age1619_da_disc,coef_age1619_da_maint,coef_age1619_da_disc,coef_age1619_da_maint,coef_age1619_da_school,coef_age1619_da_maint,coef_age1619_da_disc,coef_age1619_da_univ,coef_age1619_da_work,coef_age1619_da_atwork diff --git a/activitysim/abm/test/trip_dest/data/households.csv b/activitysim/abm/test/trip_dest/data/households.csv new file mode 100644 index 000000000..068c894d5 --- /dev/null +++ b/activitysim/abm/test/trip_dest/data/households.csv @@ -0,0 +1,11 @@ +income,hhno,MAZ +83800,1,101 +83800,2,102 +54650,3,103 +54650,4,104 +54650,5,108 +80000,6,109 +40000,7,111 +65000,8,121 +45800,9,122 +9600,10,101 diff --git a/activitysim/abm/test/trip_dest/data/land_use.csv b/activitysim/abm/test/trip_dest/data/land_use.csv new file mode 100644 index 000000000..329b64fb2 --- /dev/null +++ b/activitysim/abm/test/trip_dest/data/land_use.csv @@ -0,0 +1,10 @@ +MAZ,TAZ,Acres,Population +101,2,3.2,553 +102,2,2.6,9089 +103,3,0.4,14 +104,5,3.7,376 +108,11,6.6,884 +109,11,9.1,234 +111,11,1.1,67 +121,2,8.2,1 +122,3,45.6,0 diff --git a/activitysim/abm/test/trip_dest/data/maz.csv b/activitysim/abm/test/trip_dest/data/maz.csv new file mode 100644 index 000000000..f24add676 --- /dev/null +++ b/activitysim/abm/test/trip_dest/data/maz.csv @@ -0,0 +1,10 @@ +MAZ,TAZ +101,2 +102,2 +103,3 +104,5 +108,11 +109,11 +111,11 +121,2 +122,3 diff --git a/activitysim/abm/test/trip_dest/data/maz_to_maz_walk.csv b/activitysim/abm/test/trip_dest/data/maz_to_maz_walk.csv new file mode 100644 index 000000000..5bf0804b3 --- /dev/null +++ b/activitysim/abm/test/trip_dest/data/maz_to_maz_walk.csv @@ -0,0 +1,15 @@ +OMAZ,DMAZ,distwalk +101,101,0.078 +101,102,0.178 +101,103,0.098 +101,104,0.118 +101,108,0.348 +101,109,0.118 +101,111,0.503 +101,121,0.578 +103,103,0.098 +103,104,0.118 +103,108,0.348 +103,109,0.118 +103,111,0.503 +103,121,0.578 diff --git a/activitysim/abm/test/trip_dest/data/mini.omx b/activitysim/abm/test/trip_dest/data/mini.omx new file mode 100644 index 000000000..122693fb7 Binary files /dev/null and b/activitysim/abm/test/trip_dest/data/mini.omx differ diff --git a/activitysim/abm/test/trip_dest/data/out_trips.csv b/activitysim/abm/test/trip_dest/data/out_trips.csv new file mode 100644 index 000000000..d4456a9cd --- /dev/null +++ b/activitysim/abm/test/trip_dest/data/out_trips.csv @@ -0,0 +1,7 @@ +trip_id,tour_id,outbound,person_id,trip_num,trip_count,purpose,primary_purpose,origin,destination,failed,destination_logsum +10001,500,True,1,1,1,work,work,0,3,False, +10002,500,False,1,1,2,social,work,3,1,False,-5.062940251818796 +10003,500,False,1,2,2,home,work,1,0,False, +20003,501,True,4,1,1,social,social,2,8,False, +20004,501,False,4,1,2,eatout,social,8,1,False,13.322331251251137 +20005,501,False,4,2,2,home,social,1,2,False, diff --git a/activitysim/abm/test/trip_dest/data/persons.csv b/activitysim/abm/test/trip_dest/data/persons.csv new file mode 100644 index 000000000..11047e122 --- /dev/null +++ b/activitysim/abm/test/trip_dest/data/persons.csv @@ -0,0 +1,16 @@ +PERID,PNUM,age,household_id +0,1,69,1 +1,2,69,1 +2,1,61,2 +3,2,61,2 +4,1,61,3 +5,2,51,3 +6,1,52,4 +7,2,52,4 +8,1,73,5 +9,1,73,6 +10,1,73,7 +11,1,73,8 +12,1,65,9 +13,1,73,10 +14,2,65,10 diff --git a/activitysim/abm/test/trip_dest/data/taz.csv b/activitysim/abm/test/trip_dest/data/taz.csv new file mode 100644 index 000000000..890e8cb1c --- /dev/null +++ b/activitysim/abm/test/trip_dest/data/taz.csv @@ -0,0 +1,6 @@ +TAZ +2 +3 +5 +7 +11 diff --git a/activitysim/abm/test/trip_dest/data/tours.csv b/activitysim/abm/test/trip_dest/data/tours.csv new file mode 100644 index 000000000..d413dc9f5 --- /dev/null +++ b/activitysim/abm/test/trip_dest/data/tours.csv @@ -0,0 +1,3 @@ +tour_id,person_id,tour_mode,start,end,origin,destination,tour_category,purpose +500,1,DA,10,20,0,3,mandatory,work +501,4,WALK,15,22,2,8,non_mandatory,social \ No newline at end of file diff --git a/activitysim/abm/test/trip_dest/data/trips.csv b/activitysim/abm/test/trip_dest/data/trips.csv new file mode 100644 index 000000000..48570c0b8 --- /dev/null +++ b/activitysim/abm/test/trip_dest/data/trips.csv @@ -0,0 +1,7 @@ +trip_id,tour_id,outbound,person_id,trip_num,trip_count,purpose,primary_purpose,origin,destination,failed +10001,500,TRUE,1,1,1,work,work,0,3,FALSE +10002,500,FALSE,1,1,2,social,work,3,0,FALSE +10003,500,FALSE,1,2,2,home,work,3,0,FALSE +20003,501,TRUE,4,1,1,social,social,2,8,FALSE +20004,501,FALSE,4,1,2,eatout,social,8,2,FALSE +20005,501,FALSE,4,2,2,home,social,8,2,FALSE \ No newline at end of file diff --git a/activitysim/abm/test/trip_dest/test_trip_destination.py b/activitysim/abm/test/trip_dest/test_trip_destination.py new file mode 100644 index 000000000..fdfd9b6de --- /dev/null +++ b/activitysim/abm/test/trip_dest/test_trip_destination.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +import shutil +from pathlib import Path + +import pandas as pd + +from activitysim import abm # noqa: F401 +from activitysim.core import workflow as wf + + +def test_trip_destination(tmp_path: Path): + shutil.copytree( + Path(__file__).parent.joinpath("configs"), tmp_path.joinpath("configs") + ) + shutil.copytree(Path(__file__).parent.joinpath("data"), tmp_path.joinpath("data")) + + state = wf.State.make_default(working_dir=tmp_path) + + # init tours + tours = pd.read_csv( + tmp_path / state.filesystem.data_dir[0] / "tours.csv" + ).set_index("tour_id") + state.add_table("tours", tours) + state.tracing.register_traceable_table("tours", tours) + state.get_rn_generator().add_channel("tours", tours) + + # init trips + trips = pd.read_csv( + tmp_path / state.filesystem.data_dir[0] / "trips.csv" + ).set_index("trip_id") + state.add_table("trips", trips) + state.tracing.register_traceable_table("trips", trips) + state.get_rn_generator().add_channel("trips", trips) + + state.run.all() + + out_trips = state.get_dataframe("trips") + + # logsums are generated for intermediate trips only + assert out_trips["destination_logsum"].isna().tolist() == [ + True, + False, + True, + True, + False, + True, + ] diff --git a/activitysim/core/configuration/base.py b/activitysim/core/configuration/base.py index 6936e6d1a..2a18adec2 100644 --- a/activitysim/core/configuration/base.py +++ b/activitysim/core/configuration/base.py @@ -161,7 +161,7 @@ class ComputeSettings(PydanticBase): """ - fastmath: bool = True + fastmath: bool = False """Use fastmath when evaluating this component with sharrow. The fastmath option can be used to speed up the evaluation of expressions in @@ -170,6 +170,14 @@ class ComputeSettings(PydanticBase): computations are NaN or Inf. This can lead to errors when the assumptions are violated. If running in sharrow test mode generates errors, try turning this setting off. + + .. versionchanged:: 1.6 + + In ActivitySim versions 1.5 and earlier, the default value for this + setting was `True`, meant to favor superior runtime performance when + using sharrow. However, due to the difficulty of diagnosing and fixing + bugs that arise from the use of `fastmath`, the default setting has been + changed to `False`. """ use_bottleneck: bool | None = None diff --git a/activitysim/core/los.py b/activitysim/core/los.py index 5ac90f930..7f6f5b4c8 100644 --- a/activitysim/core/los.py +++ b/activitysim/core/los.py @@ -497,6 +497,9 @@ def load_data(self): else: # SkimDataset assert len(skims.dataset.indexes["otaz"]) == len( self.state.get_dataframe("land_use_taz") + ), ( + f"land_use_taz table length {len(self.state.get_dataframe('land_use_taz'))} does not match " + f"taz skim length {len(skims.dataset.indexes['otaz'])}" ) def create_skim_dict(self, skim_tag, _override_offset_int=None): diff --git a/activitysim/core/skim_dataset.py b/activitysim/core/skim_dataset.py index 14c7ed862..2ee7aff00 100644 --- a/activitysim/core/skim_dataset.py +++ b/activitysim/core/skim_dataset.py @@ -594,6 +594,7 @@ def load_sparse_maz_skims( maz_to_maz_tables=(), max_blend_distance=None, data_file_resolver=None, + max_float_precision: int = 32, ): """ Load sparse MAZ data on top of TAZ skim data. @@ -677,11 +678,19 @@ def load_sparse_maz_skims( max_blend_distance_i = max_blend_distance.get( colname, max_blend_distance_i ) + current_data = df[colname] + if max_float_precision: + # if current data is a float dtype of higher precision than `max_float_precision`, downcast it. + if np.issubdtype(current_data.dtype, np.floating): + if current_data.dtype.itemsize > (max_float_precision // 8): + current_data = current_data.astype( + f"float{max_float_precision}" + ) dataset.redirection.sparse_blender( colname, df.OMAZ, df.DMAZ, - df[colname], + current_data, max_blend_distance=max_blend_distance_i, index=land_use_index, ) diff --git a/docs/dev-guide/changes.md b/docs/dev-guide/changes.md index 2cd584b3c..98879d4a5 100644 --- a/docs/dev-guide/changes.md +++ b/docs/dev-guide/changes.md @@ -8,39 +8,49 @@ configurations or code to fail to run correctly. ## Upcoming Changes -This section describes changes that are implemented in current development -branch (i.e., the main branch on GitHub), but not yet released in a stable version -of ActivitySim. See below under the various version headings for changes in +This section describes changes that are implemented in current development +branch (i.e., the main branch on GitHub), but not yet released in a stable version +of ActivitySim. See below under the various version headings for changes in released versions. +### Changed Default for Sharrow "Fastmath" Optimization + +The default setting for the "fastmath" optimization in sharrow has been changed +from `True` to `False`. This optimization can improve performance in some cases, +but can also cause subtle and hard to diagnose bugs, particularly when the +data being processed contains `NaN` or `Inf` values. By defaulting to +`False`, we aim to improve platform stability for most users. Users who wish +to enable the "fastmath" optimization can do so by setting the `fastmath` option +to `True` in the `compute_settings` for each model component where it is desired. + ## v1.5.1 -This release includes a handful of minor updates and fixes, as well as enhancements -to the ActivtySim documentation. Users should generally not expect any breaking -changes relative to v1.5.0, except that when running a simulation there will be a +This release includes a handful of minor updates and fixes, as well as enhancements +to the ActivtySim documentation. Users should generally not expect any breaking +changes relative to v1.5.0, except that when running a simulation there will be a significant reduction in logging messages displayed on screen and written to the run log. ## v1.5 -This release includes most of the new features and enhancements developed as part +This release includes most of the new features and enhancements developed as part of the Phase 10 work. ### Preprocessing & Annotation -We have expanded preprocessing & annotation functionality, which is now standardized -in formatting and available on most model components. Existing model implementations -may need to make minor upgrades to model configuration files to conform with the new -standardized formatting. +We have expanded preprocessing & annotation functionality, which is now standardized +in formatting and available on most model components. Existing model implementations +may need to make minor upgrades to model configuration files to conform with the new +standardized formatting. ### Estimation Mode -Estimation mode has been updated to work with Larch v6. This new version of Larch -is modernized and more stable across platforms, and is more consistent with ActivitySim -spec files (as both are now built on Sharrow). The overall workflow for re-estimating -model parameters is very similar to before, but users will need to use Larch v6 instead -of Larch v5. In addition, some new capabilities have been added for modifying model +Estimation mode has been updated to work with Larch v6. This new version of Larch +is modernized and more stable across platforms, and is more consistent with ActivitySim +spec files (as both are now built on Sharrow). The overall workflow for re-estimating +model parameters is very similar to before, but users will need to use Larch v6 instead +of Larch v5. In addition, some new capabilities have been added for modifying model specifications in Larch (instead of re-running ActivitySim). ### Using UV for Dependency Management @@ -52,21 +62,21 @@ for details on how to install ActivitySim using UV. ### Skim Naming Conflict Resolution -The SkimDataset structure (required when using sharrow, optional in legacy mode) -requires every skim variable to have a unique name. It also merges OMX variables -based on time period, so that e.g. `BIKETIME__AM` and `BIKETIME__PM`, which would -be 2-d arrays in the OMX file, become just two different parts of a 3-d array -called `BIKETIME` in the SkimDataset. This is problematic when the skims also -contain a 2-d array called `BIKETIME`, as that has no temporal dimension, and it -gets loaded into a 2-d array in the SkimDataset, with the same name as the 3-d array, +The SkimDataset structure (required when using sharrow, optional in legacy mode) +requires every skim variable to have a unique name. It also merges OMX variables +based on time period, so that e.g. `BIKETIME__AM` and `BIKETIME__PM`, which would +be 2-d arrays in the OMX file, become just two different parts of a 3-d array +called `BIKETIME` in the SkimDataset. This is problematic when the skims also +contain a 2-d array called `BIKETIME`, as that has no temporal dimension, and it +gets loaded into a 2-d array in the SkimDataset, with the same name as the 3-d array, and thus one is overwritten and lost. -ActivitySim now includes a skims input check to identify this overwriting condition, -and raise an error if it is happening, so that the user can correct the condition -via (1) the `omx_ignore_patterns` setting, (2) revising the skim generation process -to not create the overlapping named skims in the file in the first place, -or (3) renaming one or both skims if the users actually wants both skims variables -in the model. The error message generated includes a link to instructions and +ActivitySim now includes a skims input check to identify this overwriting condition, +and raise an error if it is happening, so that the user can correct the condition +via (1) the `omx_ignore_patterns` setting, (2) revising the skim generation process +to not create the overlapping named skims in the file in the first place, +or (3) renaming one or both skims if the users actually wants both skims variables +in the model. The error message generated includes a link to instructions and discussion of these alternatives. ### Settings Checker @@ -93,12 +103,12 @@ See [Expression Profiling](Expression-Profiling) for details. A new telecommute status model component has been added to ActivitySim. This component models the telecommute status of workers, which can be used to determine -whether a worker telecommutes full-time, part-time, or not at all. A simple -implementation of the telecommute status model can be based on the worker's telecommute -frequency. For example, if a worker telecommutes 4 days a week, then there is -a 80% probability for them to telecommute on the simulation day. The telecommute -status model software can accommodate more complex model forms if needed. An example -telecommute status model specification can be found in +whether a worker telecommutes full-time, part-time, or not at all. A simple +implementation of the telecommute status model can be based on the worker's telecommute +frequency. For example, if a worker telecommutes 4 days a week, then there is +a 80% probability for them to telecommute on the simulation day. The telecommute +status model software can accommodate more complex model forms if needed. An example +telecommute status model specification can be found in [ActivitySim/sandag-abm3-example#30](https://github.com/ActivitySim/sandag-abm3-example/pull/30).