|
| 1 | +# ActivitySim |
| 2 | +# See full license in LICENSE.txt. |
| 3 | + |
| 4 | +import logging |
| 5 | + |
| 6 | +import openmatrix as omx |
| 7 | +import pandas as pd |
| 8 | +import numpy as np |
| 9 | + |
| 10 | +from activitysim.core import config |
| 11 | +from activitysim.core import inject |
| 12 | +from activitysim.core import pipeline |
| 13 | + |
| 14 | +from .util import expressions |
| 15 | +from .util.expressions import skim_time_period_label |
| 16 | + |
| 17 | +logger = logging.getLogger(__name__) |
| 18 | + |
| 19 | + |
| 20 | +@inject.step() |
| 21 | +def write_trip_matrices(trips, skim_dict, skim_stack): |
| 22 | + """ |
| 23 | + Write trip matrices step. |
| 24 | +
|
| 25 | + Adds boolean columns to local trips table via annotation expressions, |
| 26 | + then aggregates trip counts and writes OD matrices to OMX. Save annotated |
| 27 | + trips table to pipeline if desired. |
| 28 | + """ |
| 29 | + |
| 30 | + model_settings = config.read_model_settings('write_trip_matrices.yaml') |
| 31 | + trips_df = annotate_trips(trips, skim_dict, skim_stack, model_settings) |
| 32 | + |
| 33 | + if bool(model_settings.get('SAVE_TRIPS_TABLE')): |
| 34 | + pipeline.replace_table('trips', trips_df) |
| 35 | + |
| 36 | + logger.info('Aggregating trips...') |
| 37 | + aggregate_trips = trips_df.groupby(['origin', 'destination'], sort=False).sum() |
| 38 | + logger.info('Finished.') |
| 39 | + |
| 40 | + orig_vals = aggregate_trips.index.get_level_values('origin') |
| 41 | + dest_vals = aggregate_trips.index.get_level_values('destination') |
| 42 | + |
| 43 | + zone_index = pipeline.get_table('land_use').index |
| 44 | + assert all(zone in zone_index for zone in orig_vals) |
| 45 | + assert all(zone in zone_index for zone in dest_vals) |
| 46 | + |
| 47 | + _, orig_index = zone_index.reindex(orig_vals) |
| 48 | + _, dest_index = zone_index.reindex(dest_vals) |
| 49 | + |
| 50 | + write_matrices(aggregate_trips, zone_index, orig_index, dest_index, model_settings) |
| 51 | + |
| 52 | + |
| 53 | +def annotate_trips(trips, skim_dict, skim_stack, model_settings): |
| 54 | + """ |
| 55 | + Add columns to local trips table. The annotator has |
| 56 | + access to the origin/destination skims and everything |
| 57 | + defined in the model settings CONSTANTS. |
| 58 | +
|
| 59 | + Pipeline tables can also be accessed by listing them under |
| 60 | + TABLES in the preprocessor settings. |
| 61 | + """ |
| 62 | + |
| 63 | + trips_df = trips.to_frame() |
| 64 | + |
| 65 | + trace_label = 'trip_matrices' |
| 66 | + |
| 67 | + # setup skim keys |
| 68 | + assert ('trip_period' not in trips_df) |
| 69 | + trips_df['trip_period'] = skim_time_period_label(trips_df.depart) |
| 70 | + od_skim_wrapper = skim_dict.wrap('origin', 'destination') |
| 71 | + odt_skim_stack_wrapper = skim_stack.wrap(left_key='origin', right_key='destination', |
| 72 | + skim_key='trip_period') |
| 73 | + skims = { |
| 74 | + 'od_skims': od_skim_wrapper, |
| 75 | + "odt_skims": odt_skim_stack_wrapper |
| 76 | + } |
| 77 | + |
| 78 | + locals_dict = {} |
| 79 | + constants = config.get_model_constants(model_settings) |
| 80 | + if constants is not None: |
| 81 | + locals_dict.update(constants) |
| 82 | + |
| 83 | + expressions.annotate_preprocessors( |
| 84 | + trips_df, locals_dict, skims, |
| 85 | + model_settings, trace_label) |
| 86 | + |
| 87 | + # Data will be expanded by an expansion weight column from |
| 88 | + # the households pipeline table, if specified in the model settings. |
| 89 | + hh_weight_col = model_settings.get('HH_EXPANSION_WEIGHT_COL') |
| 90 | + |
| 91 | + if hh_weight_col and hh_weight_col not in trips_df: |
| 92 | + logger.info("adding '%s' from households to trips table" % hh_weight_col) |
| 93 | + household_weights = pipeline.get_table('households')[hh_weight_col] |
| 94 | + trips_df[hh_weight_col] = trips_df.household_id.map(household_weights) |
| 95 | + |
| 96 | + return trips_df |
| 97 | + |
| 98 | + |
| 99 | +def write_matrices(aggregate_trips, zone_index, orig_index, dest_index, model_settings): |
| 100 | + """ |
| 101 | + Write aggregated trips to OMX format. |
| 102 | +
|
| 103 | + The MATRICES setting lists the new OMX files to write. |
| 104 | + Each file can contain any number of 'tables', each specified by a |
| 105 | + table key ('name') and a trips table column ('data_field') to use |
| 106 | + for aggregated counts. |
| 107 | +
|
| 108 | + Any data type may be used for columns added in the annotation phase, |
| 109 | + but the table 'data_field's must be summable types: ints, floats, bools. |
| 110 | + """ |
| 111 | + |
| 112 | + matrix_settings = model_settings.get('MATRICES') |
| 113 | + |
| 114 | + if not matrix_settings: |
| 115 | + logger.error('Missing MATRICES setting in write_trip_matrices.yaml') |
| 116 | + |
| 117 | + for matrix in matrix_settings: |
| 118 | + filename = matrix.get('file_name') |
| 119 | + filepath = config.output_file_path(filename) |
| 120 | + logger.info('opening %s' % filepath) |
| 121 | + file = omx.open_file(filepath, 'w') # possibly overwrite existing file |
| 122 | + table_settings = matrix.get('tables') |
| 123 | + |
| 124 | + for table in table_settings: |
| 125 | + table_name = table.get('name') |
| 126 | + col = table.get('data_field') |
| 127 | + |
| 128 | + if col not in aggregate_trips: |
| 129 | + logger.error('missing %s column in %s DataFrame' % (col, aggregate_trips.name)) |
| 130 | + return |
| 131 | + |
| 132 | + hh_weight_col = model_settings.get('HH_EXPANSION_WEIGHT_COL') |
| 133 | + if hh_weight_col: |
| 134 | + aggregate_trips[col] = aggregate_trips[col] / aggregate_trips[hh_weight_col] |
| 135 | + |
| 136 | + data = np.zeros((len(zone_index), len(zone_index))) |
| 137 | + data[orig_index, dest_index] = aggregate_trips[col] |
| 138 | + logger.info('writing %s' % table_name) |
| 139 | + file[table_name] = data # write to file |
| 140 | + |
| 141 | + # include the index-to-zone map in the file |
| 142 | + logger.info('adding %s mapping for %s zones to %s' % |
| 143 | + (zone_index.name, zone_index.size, filename)) |
| 144 | + file.create_mapping(zone_index.name, zone_index.to_numpy()) |
| 145 | + |
| 146 | + logger.info('closing %s' % filepath) |
| 147 | + file.close() |
0 commit comments