Skip to content

Commit 51fd5e8

Browse files
i-am-sijiayueshuaingCopilot
authored
Add telecommute status model (#935)
* add explicit telecommute model * Update explicit_telecommute.py * Revert "Update explicit_telecommute.py" This reverts commit 54bc4cc. * get work and home zone variable from workplace location model * run explicit telecommute before cdap * set workplace zone to -1 for person who is telecommuting * keep workplace location unchanged * format code with Black * documentation * change the model name to telecommute status * update doc * update doc * Update docs/dev-guide/components/telecommute_status.md Co-authored-by: Copilot <[email protected]> * Update docs/dev-guide/components/telecommute_status.md Co-authored-by: Copilot <[email protected]> * alphabetical order * address review comments --------- Co-authored-by: Yue Shuai <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent ebcaadd commit 51fd5e8

File tree

4 files changed

+208
-0
lines changed

4 files changed

+208
-0
lines changed

activitysim/abm/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
stop_frequency,
3434
summarize,
3535
telecommute_frequency,
36+
telecommute_status,
3637
tour_mode_choice,
3738
tour_od_choice,
3839
tour_scheduling_probabilistic,
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# ActivitySim
2+
# See full license in LICENSE.txt.
3+
from __future__ import annotations
4+
5+
import logging
6+
7+
import numpy as np
8+
import pandas as pd
9+
10+
from activitysim.core import (
11+
config,
12+
estimation,
13+
expressions,
14+
simulate,
15+
tracing,
16+
workflow,
17+
)
18+
from activitysim.core.configuration.base import PreprocessorSettings, PydanticReadable
19+
from activitysim.core.configuration.logit import LogitComponentSettings
20+
21+
logger = logging.getLogger("activitysim")
22+
23+
24+
class TelecommuteStatusSettings(LogitComponentSettings, extra="forbid"):
25+
"""
26+
Settings for the `telecommute_status` component.
27+
"""
28+
29+
TELECOMMUTE_ALT: int
30+
"""Value that specifies if the worker is telecommuting on the simulation day."""
31+
32+
CHOOSER_FILTER_COLUMN_NAME: str = "is_worker"
33+
"""Column name in the dataframe to represent worker."""
34+
35+
36+
@workflow.step
37+
def telecommute_status(
38+
state: workflow.State,
39+
persons_merged: pd.DataFrame,
40+
persons: pd.DataFrame,
41+
model_settings: TelecommuteStatusSettings | None = None,
42+
model_settings_file_name: str = "telecommute_status.yaml",
43+
trace_label: str = "telecommute_status",
44+
) -> None:
45+
"""
46+
This model predicts whether a person (worker) telecommutes on the simulation day.
47+
The output from this model is TRUE (if telecommutes) or FALSE (if does not telecommute).
48+
"""
49+
if model_settings is None:
50+
model_settings = TelecommuteStatusSettings.read_settings_file(
51+
state.filesystem,
52+
model_settings_file_name,
53+
)
54+
55+
choosers = persons_merged
56+
chooser_filter_column_name = model_settings.CHOOSER_FILTER_COLUMN_NAME
57+
choosers = choosers[(choosers[chooser_filter_column_name])]
58+
logger.info("Running %s with %d persons", trace_label, len(choosers))
59+
60+
estimator = estimation.manager.begin_estimation(state, "telecommute_status")
61+
62+
constants = config.get_model_constants(model_settings)
63+
64+
# - preprocessor
65+
expressions.annotate_preprocessors(
66+
state,
67+
df=choosers,
68+
locals_dict=constants,
69+
skims=None,
70+
model_settings=model_settings,
71+
trace_label=trace_label,
72+
)
73+
74+
model_spec = state.filesystem.read_model_spec(file_name=model_settings.SPEC)
75+
coefficients_df = state.filesystem.read_model_coefficients(model_settings)
76+
model_spec = simulate.eval_coefficients(
77+
state, model_spec, coefficients_df, estimator
78+
)
79+
nest_spec = config.get_logit_model_settings(model_settings)
80+
81+
if estimator:
82+
estimator.write_model_settings(model_settings, model_settings_file_name)
83+
estimator.write_spec(model_settings)
84+
estimator.write_coefficients(coefficients_df, model_settings)
85+
estimator.write_choosers(choosers)
86+
87+
choices = simulate.simple_simulate(
88+
state,
89+
choosers=choosers,
90+
spec=model_spec,
91+
nest_spec=nest_spec,
92+
locals_d=constants,
93+
trace_label=trace_label,
94+
trace_choice_name="is_telecommuting",
95+
estimator=estimator,
96+
compute_settings=model_settings.compute_settings,
97+
)
98+
99+
telecommute_alt = model_settings.TELECOMMUTE_ALT
100+
choices = choices == telecommute_alt
101+
102+
if estimator:
103+
estimator.write_choices(choices)
104+
choices = estimator.get_survey_values(choices, "persons", "is_telecommuting")
105+
estimator.write_override_choices(choices)
106+
estimator.end_estimation()
107+
108+
persons["is_telecommuting"] = choices.reindex(persons.index).fillna(0).astype(bool)
109+
110+
state.add_table("persons", persons)
111+
112+
tracing.print_summary(
113+
"telecommute_status", persons.is_telecommuting, value_counts=True
114+
)
115+
116+
if state.settings.trace_hh_id:
117+
state.tracing.trace_df(persons, label=trace_label, warn_if_empty=True)
118+
119+
expressions.annotate_tables(
120+
state,
121+
locals_dict=constants,
122+
skims=None,
123+
model_settings=model_settings,
124+
trace_label=trace_label,
125+
)

activitysim/estimation/larch/simple_simulate.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,22 @@ def work_from_home_model(
257257
)
258258

259259

260+
def telecommute_status_model(
261+
name="telecommute_status",
262+
edb_directory="output/estimation_data_bundle/{name}/",
263+
return_data=False,
264+
):
265+
return simple_simulate_model(
266+
name=name,
267+
edb_directory=edb_directory,
268+
return_data=return_data,
269+
choices={
270+
True: 1,
271+
False: 2,
272+
}, # True is telecommute, false is does not telecommute, names match spec positions
273+
)
274+
275+
260276
def mandatory_tour_frequency_model(
261277
name="mandatory_tour_frequency",
262278
edb_directory="output/estimation_data_bundle/{name}/",
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
(component-telecommute_status)=
2+
# Telecommute Status
3+
4+
```{eval-rst}
5+
.. currentmodule:: activitysim.abm.models.telecommute_status
6+
```
7+
8+
ActivitySim telecommute representation consists of two long term submodels -
9+
a person [work_from_home](work_from_home) model and
10+
a person [telecommute_frequency](telecommute_frequency) model.
11+
The work from home model predicts if a worker works exclusively from home,
12+
whereas the telecommute frequency model predicts number of days in a week a worker telecommutes,
13+
if they do not exclusively work from home.
14+
However, neither of them predicts whether a worker telecommutes or not on the simulation day.
15+
This telecommute status model extends the previous two models to predict for all workers whether
16+
they telecommute on the simulation day.
17+
18+
A simple implementation of the telecommute status model can be based on the worker's telecommute frequency.
19+
For example, if a worker telecommutes 4 days a week, then there is a 80% probability for them
20+
to telecommute on the simulation day.
21+
The telecommute status model software can accommodate more complex model forms if needed.
22+
23+
There have been discussions about where to place the telecommute status model within the model sequence,
24+
particularly regarding its interation with the Coordinated Daily Activity Pattern (CDAP) model.
25+
Some have proposed expanding the CDAP definition of the "Mandatory" day pattern to include commuting, telecommuting and working from home,
26+
and then applying the telecommute status model to workers with a "Mandatory" day pattern.
27+
While this idea had merit, it would require re-defining and re-estimating CDAP for many regions, which presents practical challenges.
28+
29+
During Phase 9B development, the Consortium collaboratively reached a consensus on a preferred design for explicitly modeling telecommuting.
30+
It was decided that the existing CDAP definitions would remain unchanged. The new design introduces the ability
31+
to model hybrid workers—those who work both in-home and out-of-home on the simulation day. To support this,
32+
the Consortium recommended adding two new models: a Telecommute Arrangement model and an In-Home Work Activity Duration model.
33+
34+
As of August 2025, these two models remain at the design stage and have not yet been implemented. Once deployed,
35+
they will supersede the current telecommute status model, which will no longer be needed. In the interim,
36+
the telecommute status model can be used to flag telecommuters in the simulation.
37+
38+
The main interface to the telecommute status model is the
39+
[telecommute_status](activitysim.abm.models.telecommute_status) function. This
40+
function is registered as an Inject step in the example Pipeline.
41+
42+
## Structure
43+
44+
- *Configuration File*: `telecommute_status.yaml`
45+
- *Core Table*: `persons`
46+
- *Result Table*: `is_telecommuting`
47+
48+
49+
## Configuration
50+
51+
```{eval-rst}
52+
.. autopydantic_model:: TelecommuteStatusSettings
53+
:inherited-members: BaseModel, PydanticReadable
54+
:show-inheritance:
55+
```
56+
57+
### Examples
58+
59+
- [Example SANDAG ABM3](https://github.com/ActivitySim/sandag-abm3-example/tree/main/configs/resident/telecommute_status.yaml)
60+
61+
62+
## Implementation
63+
64+
```{eval-rst}
65+
.. autofunction:: telecommute_status
66+
```

0 commit comments

Comments
 (0)