Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
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
1 change: 1 addition & 0 deletions activitysim/abm/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,5 @@
vehicle_allocation,
vehicle_type_choice,
work_from_home,
telecommute_status,
)
125 changes: 125 additions & 0 deletions activitysim/abm/models/telecommute_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# ActivitySim
# See full license in LICENSE.txt.
from __future__ import annotations

import logging

import numpy as np
import pandas as pd

from activitysim.core import (
config,
estimation,
expressions,
simulate,
tracing,
workflow,
)
from activitysim.core.configuration.base import PreprocessorSettings, PydanticReadable
from activitysim.core.configuration.logit import LogitComponentSettings

logger = logging.getLogger("activitysim")


class TelecommuteStatusSettings(LogitComponentSettings, extra="forbid"):
"""
Settings for the `telecommute_status` component.
"""

preprocessor: PreprocessorSettings | None = None
"""Setting for the preprocessor."""

TELECOMMUTE_ALT: int
"""Value that specifies if the worker is telecommuting on the simulation day."""

CHOOSER_FILTER_COLUMN_NAME: str = "is_worker"
"""Column name in the dataframe to represent worker."""


@workflow.step
def telecommute_status(
state: workflow.State,
persons_merged: pd.DataFrame,
persons: pd.DataFrame,
model_settings: TelecommuteStatusSettings | None = None,
model_settings_file_name: str = "telecommute_status.yaml",
trace_label: str = "telecommute_status",
) -> None:
"""
This model predicts whether a person (worker) telecommutes on the simulation day.
The output from this model is TRUE (if telecommutes) or FALSE (if does not telecommute).
"""
if model_settings is None:
model_settings = TelecommuteStatusSettings.read_settings_file(
state.filesystem,
model_settings_file_name,
)

choosers = persons_merged
chooser_filter_column_name = model_settings.CHOOSER_FILTER_COLUMN_NAME
choosers = choosers[(choosers[chooser_filter_column_name])]
logger.info("Running %s with %d persons", trace_label, len(choosers))

estimator = estimation.manager.begin_estimation(state, "telecommute_status")

constants = config.get_model_constants(model_settings)

# - preprocessor
preprocessor_settings = model_settings.preprocessor
if preprocessor_settings:
locals_d = {}
if constants is not None:
locals_d.update(constants)

expressions.assign_columns(
state,
df=choosers,
model_settings=preprocessor_settings,
locals_dict=locals_d,
trace_label=trace_label,
)

model_spec = state.filesystem.read_model_spec(file_name=model_settings.SPEC)
coefficients_df = state.filesystem.read_model_coefficients(model_settings)
model_spec = simulate.eval_coefficients(
state, model_spec, coefficients_df, estimator
)
nest_spec = config.get_logit_model_settings(model_settings)

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)

choices = simulate.simple_simulate(
state,
choosers=choosers,
spec=model_spec,
nest_spec=nest_spec,
locals_d=constants,
trace_label=trace_label,
trace_choice_name="is_telecommuting",
estimator=estimator,
compute_settings=model_settings.compute_settings,
)

telecommute_alt = model_settings.TELECOMMUTE_ALT
choices = choices == telecommute_alt

if estimator:
estimator.write_choices(choices)
choices = estimator.get_survey_values(choices, "persons", "is_telecommuting")
estimator.write_override_choices(choices)
estimator.end_estimation()

persons["is_telecommuting"] = choices.reindex(persons.index).fillna(0).astype(bool)

state.add_table("persons", persons)

tracing.print_summary(
"telecommute_status", persons.is_telecommuting, value_counts=True
)

if state.settings.trace_hh_id:
state.tracing.trace_df(persons, label=trace_label, warn_if_empty=True)
16 changes: 16 additions & 0 deletions activitysim/estimation/larch/simple_simulate.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,22 @@ def work_from_home_model(
)


def telecommute_status_model(
name="telecommute_status",
edb_directory="output/estimation_data_bundle/{name}/",
return_data=False,
):
return simple_simulate_model(
name=name,
edb_directory=edb_directory,
return_data=return_data,
choices={
True: 1,
False: 2,
}, # True is telecommute, false is does not telecommute, names match spec positions
)


def mandatory_tour_frequency_model(
name="mandatory_tour_frequency",
edb_directory="output/estimation_data_bundle/{name}/",
Expand Down
58 changes: 58 additions & 0 deletions docs/dev-guide/components/telecommute_status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
(component-telecommute_status)=
# Telecommute Status

```{eval-rst}
.. currentmodule:: activitysim.abm.models.telecommute_status
```

ActivitySim telecommute representation consists of two long term submodels -
a person [work_from_home](work_from_home) model and
a person [telecommute_frequency](telecommute_frequency) model.
The work from home model predicts if a worker works exclusively from home,
whereas the telecommute frequency model predicts number of days in a week a worker telecommutes,
if they do not exclusively work from home.
However, neither of them predicts whether a worker telecommutes or not on the simulation day.
This telecommute status model extends the previous two models to predict for all workers whether
they telecommute on the simulation day.

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.

There have been discussions on exactly where the telecommute status model should be added
in the model sequence. Some suggest it should be applied to all workers before the CDAP model;
some suggest it should be applied after the CDAP model only to workers who have work activities
during the day regardless of in-home or out-of-home (which requires change in CDAP definition).
The Consortium is currently engaged in an explicit telecommute design task as part of Phase 9B,
out of which more guidance on the model sequence will be established.

The main interface to the telecommute status model is the
[telecommute_status](activitysim.abm.models.telecommute_status) function. This
function is registered as an Inject step in the example Pipeline.

## Structure

- *Configuration File*: `telecommute_status.yaml`
- *Core Table*: `persons`
- *Result Table*: `is_telecommuting`


## Configuration

```{eval-rst}
.. autopydantic_model:: TelecommuteStatusSettings
:inherited-members: BaseModel, PydanticReadable
:show-inheritance:
```

### Examples

- [Example SANDAG ABM3](https://github.com/ActivitySim/sandag-abm3-example/tree/main/configs/resident/telecommute_status.yaml)


## Implementation

```{eval-rst}
.. autofunction:: telecommute_status
```
Loading