Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚀 [feat] vehicle type model #486

Merged
merged 44 commits into from
Jun 6, 2022
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
cb5169a
vehicle type model, first commit
mxndrwgrdnr Sep 21, 2021
2fbd161
vehicle type model, first commit
mxndrwgrdnr Sep 21, 2021
82e7e4c
option 2 spec changes
mxndrwgrdnr Nov 7, 2021
d0572a3
got vehicle choice model running w probabilistic dimension
mxndrwgrdnr Nov 8, 2021
4260fb9
update example mtc configs to run vehicle choice
mxndrwgrdnr Nov 8, 2021
72404f0
got vehicle choice model running w probabilistic dimension
mxndrwgrdnr Nov 8, 2021
cec3e4e
pycodestyle fixes and mi to meters conversion
mxndrwgrdnr Nov 9, 2021
6d6f9e7
add veh choice to estimation example
mxndrwgrdnr Nov 12, 2021
0ed8c5d
implemented option 2 and 4 as MNL
mxndrwgrdnr Dec 1, 2021
5c5540d
simply veh type choice alts/utilities creation
mxndrwgrdnr Dec 5, 2021
d629401
fix alt file storage bug
mxndrwgrdnr Dec 5, 2021
5f626d7
pycodestyle fixes
mxndrwgrdnr Dec 5, 2021
349c904
pycodestyle fixes
mxndrwgrdnr Dec 5, 2021
5ee4d17
pycodestyle fixes
mxndrwgrdnr Dec 5, 2021
ac1c934
debug estimation example
mxndrwgrdnr Dec 6, 2021
8118466
estimation mode debug
mxndrwgrdnr Dec 6, 2021
f29f690
docstrings, etc
mxndrwgrdnr Dec 11, 2021
3c4bc5a
typo
mxndrwgrdnr Dec 11, 2021
665c4a2
veh typ choice yaml updates
mxndrwgrdnr Dec 11, 2021
3c874bb
pycodestyle fixes
mxndrwgrdnr Dec 15, 2021
832fc30
added vehicle type data, implemented option 4 minus owned veh interac…
dhensle Feb 26, 2022
1b1dd54
implemented option4
dhensle Mar 3, 2022
6660ab4
implemented vehicle type option 2 and vehicle allocation models
dhensle Mar 11, 2022
d1d3284
tied vehicle ids to household ids, added fleet_year option
Mar 24, 2022
a9da22f
added options to include vehicle data in vehicle table, documentation
Mar 31, 2022
3130d68
added annotate tours functionality
Apr 13, 2022
235c4fa
cleanup & documentation
Apr 13, 2022
498c09f
restore example_mtc
Apr 14, 2022
3a9542b
created example_mtc_extended
Apr 14, 2022
4be57ee
vehicle type model documentation
Apr 19, 2022
9583f38
documentation
dhensle Apr 26, 2022
e8b0d23
adding missed vehicle allocation spec
dhensle Apr 26, 2022
b078369
updating regress tables with new auto ownership costs
dhensle Apr 26, 2022
990876d
fixed bug where chargers were applied to all alts and not just evs
dhensle Apr 29, 2022
f55c2f0
fixing incorrect coefficients
dhensle Apr 29, 2022
75d4e7d
responses to pull request comments
dhensle May 24, 2022
7d0a467
small variable name change
dhensle May 24, 2022
81010ee
log_alt_losers setting
dhensle May 24, 2022
e497f65
Merge branch 'develop' into vehicle
dhensle May 24, 2022
e7b7c0e
pycodestyle
dhensle May 25, 2022
691205d
no vehicle type estimation
dhensle May 30, 2022
9c5753f
adding MTC Extended Example to TravisCI
dhensle May 30, 2022
fe4f763
removing unused expressions
dhensle May 30, 2022
d556239
replaced table with correct column order
dhensle May 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions activitysim/abm/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@
from . import trip_scheduling_choice
from . import trip_matrices
from . import summarize
from . import vehicle_allocation
from . import vehicle_type_choice
e-lo marked this conversation as resolved.
Show resolved Hide resolved
10 changes: 10 additions & 0 deletions activitysim/abm/models/atwork_subtour_mode_choice.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from activitysim.core import inject
from activitysim.core import pipeline
from activitysim.core import simulate
from activitysim.core import expressions

from activitysim.core import los
from activitysim.core.pathbuilder import TransitVirtualPathBuilder
Expand Down Expand Up @@ -166,6 +167,15 @@ def atwork_subtour_mode_choice(
assign_in_place(tours, choices_df)
pipeline.replace_table("tours", tours)

# - annotate tours table
if model_settings.get('annotate_tours'):
tours = inject.get_table('tours').to_frame()
expressions.assign_columns(
df=tours,
model_settings=model_settings.get('annotate_tours'),
trace_label=tracing.extend_trace_label(trace_label, 'annotate_tours'))
pipeline.replace_table("tours", tours)

if trace_hh_id:
tracing.trace_df(tours[tours.tour_category == 'atwork'],
label=tracing.extend_trace_label(trace_label, mode_column_name),
Expand Down
10 changes: 10 additions & 0 deletions activitysim/abm/models/tour_mode_choice.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from activitysim.core import config
from activitysim.core import inject
from activitysim.core import pipeline
from activitysim.core import expressions
from activitysim.core import simulate
from activitysim.core import logit
from activitysim.core.util import assign_in_place, reindex
Expand Down Expand Up @@ -353,6 +354,15 @@ def tour_mode_choice_simulate(tours, persons_merged,

pipeline.replace_table("tours", all_tours)

# - annotate tours table
if model_settings.get('annotate_tours'):
tours = inject.get_table('tours').to_frame()
expressions.assign_columns(
df=tours,
model_settings=model_settings.get('annotate_tours'),
trace_label=tracing.extend_trace_label(trace_label, 'annotate_tours'))
pipeline.replace_table("tours", tours)

if trace_hh_id:
tracing.trace_df(primary_tours,
label=tracing.extend_trace_label(trace_label, mode_column_name),
Expand Down
7 changes: 4 additions & 3 deletions activitysim/abm/models/util/canonical_ids.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@
logger = logging.getLogger(__name__)


RANDOM_CHANNELS = ['households', 'persons', 'tours', 'joint_tour_participants', 'trips']
TRACEABLE_TABLES = ['households', 'persons', 'tours', 'joint_tour_participants', 'trips']
RANDOM_CHANNELS = ['households', 'persons', 'tours', 'joint_tour_participants', 'trips', 'vehicles']
TRACEABLE_TABLES = ['households', 'persons', 'tours', 'joint_tour_participants', 'trips', 'vehicles']

CANONICAL_TABLE_INDEX_NAMES = {
'households': 'household_id',
'persons': 'person_id',
'tours': 'tour_id',
'joint_tour_participants': 'participant_id',
'trips': 'trip_id',
'land_use': 'zone_id'
'land_use': 'zone_id',
'vehicles': 'vehicle_id'
}

# unfortunately the two places this is needed (joint_tour_participation and estimation.infer
Expand Down
236 changes: 236 additions & 0 deletions activitysim/abm/models/vehicle_allocation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
# ActivitySim
# See full license in LICENSE.txt.

import logging

import pandas as pd
import numpy as np
import itertools
import os

from activitysim.core.interaction_simulate import interaction_simulate
from activitysim.core import simulate
from activitysim.core import tracing
from activitysim.core import config
from activitysim.core import inject
from activitysim.core import pipeline
from activitysim.core import expressions
from activitysim.core import logit
from activitysim.core import assign
from activitysim.core import los

from activitysim.core.util import assign_in_place

from .util.mode import mode_choice_simulate
from .util import estimation

logger = logging.getLogger(__name__)


def annotate_vehicle_allocation(model_settings, trace_label):
"""
Add columns to the tours table in the pipeline according to spec.

Parameters
----------
model_settings : dict
trace_label : str
"""
tours = inject.get_table('tours').to_frame()
expressions.assign_columns(
df=tours,
model_settings=model_settings.get('annotate_tours'),
trace_label=tracing.extend_trace_label(trace_label, 'annotate_tours'))
pipeline.replace_table("tours", tours)


def get_skim_dict(network_los, choosers):
"""
Returns a dictionary of skim wrappers to use in expression writing.

Skims have origin as home_zone_id and destination as the tour destination.

Parameters
----------
network_los : activitysim.core.los.Network_LOS object
choosers : pd.DataFrame

Returns
-------
skims : dict
index is skim wrapper name, value is the skim wrapper
"""
skim_dict = network_los.get_default_skim_dict()
orig_col_name = 'home_zone_id'
dest_col_name = 'destination'

out_time_col_name = 'start'
in_time_col_name = 'end'
odt_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=orig_col_name, dest_key=dest_col_name,
dim3_key='out_period')
dot_skim_stack_wrapper = skim_dict.wrap_3d(orig_key=dest_col_name, dest_key=orig_col_name,
dim3_key='in_period')

choosers['in_period'] = network_los.skim_time_period_label(choosers[in_time_col_name])
choosers['out_period'] = network_los.skim_time_period_label(choosers[out_time_col_name])

skims = {
"odt_skims": odt_skim_stack_wrapper.set_df(choosers),
"dot_skims": dot_skim_stack_wrapper.set_df(choosers),
}
return skims


@inject.step()
def vehicle_allocation(
persons,
households,
vehicles,
tours,
tours_merged,
network_los,
chunk_size,
trace_hh_id):
"""Selects a vehicle for each occupancy level for each tour.

Alternatives consist of the up to the number of household vehicles plus one
option for non-household vehicles.

The model will be run once for each tour occupancy defined in the model yaml.
Output tour table will columns added for each occupancy level.

The user may also augment the `tours` tables with new vehicle
type-based fields specified via the annotate_tours option.

Parameters
----------
persons : orca.DataFrameWrapper
households : orca.DataFrameWrapper
vehicles : orca.DataFrameWrapper
vehicles_merged : orca.DataFrameWrapper
tours : orca.DataFrameWrapper
tours_merged : orca.DataFrameWrapper
chunk_size : orca.injectable
trace_hh_id : orca.injectable
"""
trace_label = 'vehicle_allocation'
model_settings_file_name = 'vehicle_allocation.yaml'
model_settings = config.read_model_settings(model_settings_file_name)

logsum_column_name = model_settings.get('MODE_CHOICE_LOGSUM_COLUMN_NAME')

estimator = estimation.manager.begin_estimation('vehicle_allocation')

model_spec_raw = simulate.read_model_spec(file_name=model_settings['SPEC'])
coefficients_df = simulate.read_model_coefficients(model_settings)
model_spec = simulate.eval_coefficients(model_spec_raw, coefficients_df, estimator)

nest_spec = config.get_logit_model_settings(model_settings)
constants = config.get_model_constants(model_settings)

locals_dict = {}
locals_dict.update(constants)
locals_dict.update(coefficients_df)

# ------ constructing alternatives from model spec and joining to choosers
vehicles_wide = vehicles.to_frame().pivot_table(
index='household_id', columns='vehicle_num',
values='vehicle_type', aggfunc=lambda x: ''.join(x))

alts_from_spec = model_spec.columns
# renaming vehicle numbers to alternative names in spec
vehicle_alt_columns_dict = {}
for veh_num in range(1, len(alts_from_spec)):
vehicle_alt_columns_dict[veh_num] = alts_from_spec[veh_num-1]
vehicles_wide.rename(columns=vehicle_alt_columns_dict, inplace=True)

# if the number of vehicles is less than the alternatives, fill with NA
# e.g. all households only have 1 or 2 vehicles because of small sample size,
# still need columns for alternatives 3 and 4
for veh_num, col_name in vehicle_alt_columns_dict.items():
if col_name not in vehicles_wide.columns:
vehicles_wide[col_name] = ''

# last entry in spec is the non-hh-veh option
assert alts_from_spec[-1] == 'non_hh_veh', "Last option in spec needs to be non_hh_veh"
vehicles_wide[alts_from_spec[-1]] = ''

# merging vehicle alternatives to choosers
choosers = tours_merged.to_frame().reset_index()
choosers = pd.merge(choosers, vehicles_wide, how='left', on='household_id')
choosers.set_index('tour_id', inplace=True)

# ----- setup skim keys
skims = get_skim_dict(network_los, choosers)
locals_dict.update(skims)

# ------ preprocessor
preprocessor_settings = model_settings.get('preprocessor', None)
if preprocessor_settings:
expressions.assign_columns(
df=choosers,
model_settings=preprocessor_settings,
locals_dict=locals_dict,
trace_label=trace_label)

logger.info("Running %s with %d tours", trace_label, len(choosers))

if estimator:
estimator.write_model_settings(model_settings, model_settings_file_name)
estimator.write_spec(model_settings)
estimator.write_coefficients(coefficients_df, model_settings)
estimator.write_choosers(choosers)

tours = tours.to_frame()

# ------ running for each occupancy level selected
tours_veh_occup_cols = []
for occup in model_settings.get('OCCUPANCY_LEVELS', [1]):
logger.info("Running for occupancy = %d", occup)
# setting occup for access in spec expressions
locals_dict.update({'occup': occup})

choices = simulate.simple_simulate(
choosers=choosers,
spec=model_spec,
nest_spec=nest_spec,
skims=skims,
locals_d=locals_dict,
chunk_size=chunk_size,
trace_label=trace_label,
trace_choice_name='vehicle_allocation',
estimator=estimator)

# matching alt names to choices
choices = choices.map(dict(enumerate(alts_from_spec))).to_frame()
choices.columns = ['alt_choice']

# last alternative is the non-household vehicle option
for alt in alts_from_spec[:-1]:
choices.loc[choices['alt_choice'] == alt, 'choice'] = \
choosers.loc[choices['alt_choice'] == alt, alt]
choices.loc[choices['alt_choice'] == alts_from_spec[-1], 'choice'] = alts_from_spec[-1]

# creating a column for choice of each occupancy level
tours_veh_occup_col = f'vehicle_occup_{occup}'
tours[tours_veh_occup_col] = choices['choice']
tours_veh_occup_cols.append(tours_veh_occup_col)

if estimator:
estimator.write_choices(choices)
choices = estimator.get_survey_values(choices, 'households', 'vehicle_allocation')
estimator.write_override_choices(choices)
estimator.end_estimation()

pipeline.replace_table("tours", tours)

tracing.print_summary('vehicle_allocation', tours[tours_veh_occup_cols], value_counts=True)

annotate_settings = model_settings.get('annotate_tours', None)
if annotate_settings:
annotate_vehicle_allocation(model_settings, trace_label)

if trace_hh_id:
tracing.trace_df(tours,
label='vehicle_allocation',
warn_if_empty=True)
Loading