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

Overflow protection for NL #821

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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/atwork_subtour_frequency.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ def atwork_subtour_frequency(
trace_label=trace_label,
trace_choice_name="atwork_subtour_frequency",
estimator=estimator,
overflow_protection=model_settings.overflow_protection,
)

# convert indexes to alternative names
Expand Down
1 change: 1 addition & 0 deletions activitysim/abm/models/auto_ownership.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def auto_ownership_simulate(
trace_choice_name="auto_ownership",
log_alt_losers=log_alt_losers,
estimator=estimator,
overflow_protection=model_settings.overflow_protection,
)

if estimator:
Expand Down
1 change: 1 addition & 0 deletions activitysim/abm/models/free_parking.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ def free_parking(
trace_label=trace_label,
trace_choice_name="free_parking_at_work",
estimator=estimator,
overflow_protection=model_settings.overflow_protection,
)

free_parking_alt = model_settings.FREE_PARKING_ALT
Expand Down
1 change: 1 addition & 0 deletions activitysim/abm/models/joint_tour_composition.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ def joint_tour_composition(
trace_label=trace_label,
trace_choice_name="composition",
estimator=estimator,
overflow_protection=model_settings.overflow_protection,
)

# convert indexes to alternative names
Expand Down
1 change: 1 addition & 0 deletions activitysim/abm/models/joint_tour_frequency.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ def joint_tour_frequency(
trace_label=trace_label,
trace_choice_name="joint_tour_frequency",
estimator=estimator,
overflow_protection=model_settings.overflow_protection,
)

# convert indexes to alternative names
Expand Down
1 change: 1 addition & 0 deletions activitysim/abm/models/joint_tour_participation.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ def joint_tour_participation(
trace_choice_name="participation",
custom_chooser=participants_chooser,
estimator=estimator,
overflow_protection=model_settings.overflow_protection,
)

# choice is boolean (participate or not)
Expand Down
1 change: 1 addition & 0 deletions activitysim/abm/models/mandatory_tour_frequency.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ def mandatory_tour_frequency(
trace_label=trace_label,
trace_choice_name="mandatory_tour_frequency",
estimator=estimator,
overflow_protection=model_settings.overflow_protection,
)

# convert indexes to alternative names
Expand Down
1 change: 1 addition & 0 deletions activitysim/abm/models/stop_frequency.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ def stop_frequency(
trace_label=tracing.extend_trace_label(trace_label, segment_name),
trace_choice_name="stops",
estimator=estimator,
overflow_protection=model_settings.overflow_protection,
)

# convert indexes to alternative names
Expand Down
1 change: 1 addition & 0 deletions activitysim/abm/models/telecommute_frequency.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def telecommute_frequency(
trace_label=trace_label,
trace_choice_name="telecommute_frequency",
estimator=estimator,
overflow_protection=model_settings.overflow_protection,
)

choices = pd.Series(model_spec.columns[choices.values], index=choices.index)
Expand Down
1 change: 1 addition & 0 deletions activitysim/abm/models/transit_pass_ownership.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ def transit_pass_ownership(
trace_label=trace_label,
trace_choice_name="transit_pass_ownership",
estimator=estimator,
overflow_protection=model_settings.overflow_protection,
)

if estimator:
Expand Down
1 change: 1 addition & 0 deletions activitysim/abm/models/transit_pass_subsidy.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ def transit_pass_subsidy(
trace_label=trace_label,
trace_choice_name="transit_pass_subsidy",
estimator=estimator,
overflow_protection=model_settings.overflow_protection,
)

if estimator:
Expand Down
1 change: 1 addition & 0 deletions activitysim/abm/models/trip_mode_choice.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ def trip_mode_choice(
trace_label=segment_trace_label,
trace_choice_name="trip_mode_choice",
estimator=estimator,
overflow_protection=model_settings.overflow_protection,
)

if state.settings.trace_hh_id:
Expand Down
3 changes: 3 additions & 0 deletions activitysim/abm/models/util/mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def mode_choice_simulate(
trace_choice_name,
trace_column_names=None,
estimator: Optional[Estimator] = None,
overflow_protection: bool = False,
):
"""
common method for both tour_mode_choice and trip_mode_choice
Expand Down Expand Up @@ -70,6 +71,7 @@ def mode_choice_simulate(
trace_choice_name=trace_choice_name,
estimator=estimator,
trace_column_names=trace_column_names,
overflow_protection=overflow_protection,
)

# for consistency, always return dataframe, whether or not logsums were requested
Expand Down Expand Up @@ -170,6 +172,7 @@ def run_tour_mode_choice_simulate(
trace_choice_name=trace_choice_name,
trace_column_names=trace_column_names,
estimator=estimator,
overflow_protection=model_settings.overflow_protection,
)

return choices
1 change: 1 addition & 0 deletions activitysim/abm/models/vehicle_allocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ def vehicle_allocation(
trace_label=trace_label,
trace_choice_name="vehicle_allocation",
estimator=estimator,
overflow_protection=model_settings.overflow_protection,
)

# matching alt names to choices
Expand Down
1 change: 1 addition & 0 deletions activitysim/abm/models/vehicle_type_choice.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,7 @@ def iterate_vehicle_type_choice(
trace_label=trace_label,
trace_choice_name="vehicle_type",
estimator=estimator,
overflow_protection=model_settings.overflow_protection,
)
else:
raise NotImplementedError(simulation_type)
Expand Down
1 change: 1 addition & 0 deletions activitysim/abm/models/work_from_home.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ def work_from_home(
trace_label=trace_label,
trace_choice_name="work_from_home",
estimator=estimator,
overflow_protection=model_settings.overflow_protection,
)

if iterations_target_percent is not None:
Expand Down
10 changes: 10 additions & 0 deletions activitysim/core/configuration/logit.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,16 @@ def nests_are_for_nl(cls, nests, values):
raise ValueError("NESTS cannot be provided for a MNL model")
return nests

overflow_protection: bool = False
"""Whether to use overflow protection in the probability calculation.

Overflow protection will shift the utility values to avoid numerical
overflow when exponentiating the utility values to calculate probabilities.
It will also protect against underflow if the utility values are very
negative. Ideally, model specifications will be constructed to avoid
overflow, but this option is provided as a safety net.
"""


class TemplatedLogitComponentSettings(LogitComponentSettings):
"""
Expand Down
15 changes: 14 additions & 1 deletion activitysim/core/interaction_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ def _interaction_sample(
trace_label=None,
zone_layer=None,
chunk_sizer=None,
overflow_protection: bool | None = None,
):
"""
Run a MNL simulation in the situation in which alternatives must
Expand Down Expand Up @@ -178,6 +179,15 @@ def _interaction_sample(
'maz' zone layer in a one-zone model, but you can use the 'taz' layer in
a two- or three-zone model (e.g. for destination pre-sampling).

overflow_protection : bool, optional
If True, use overflow-protected exponentiation and normalization to
compute probabilities, which will help prevent problems if the utility
values are very large in magnitude (positive or negative). If False,
overflow-protected exponentiation and normalization will not be used,
and the model might fail if the utility values are very large in magnitude.
If None (the default), overflow-protection is turned on if
`allow_zero_probs` is False, and left off otherwise.

Returns
-------
choices_df : pandas.DataFrame
Expand Down Expand Up @@ -402,6 +412,9 @@ def _interaction_sample(

state.tracing.dump_df(DUMP, utilities, trace_label, "utilities")

if overflow_protection is None:
overflow_protection = not allow_zero_probs

# convert to probabilities (utilities exponentiated and normalized to probs)
# probs is same shape as utilities, one row per chooser and one column for alternative
probs = logit.utils_to_probs(
Expand All @@ -410,7 +423,7 @@ def _interaction_sample(
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
overflow_protection=overflow_protection,
)
chunk_sizer.log_df(trace_label, "probs", probs)

Expand Down
17 changes: 15 additions & 2 deletions activitysim/core/interaction_sample_simulate.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def _interaction_sample_simulate(
skip_choice=False,
*,
chunk_sizer: chunk.ChunkSizer,
overflow_protection: bool | None = None,
):
"""
Run a MNL simulation in the situation in which alternatives must
Expand Down Expand Up @@ -72,6 +73,15 @@ def _interaction_sample_simulate(
the index is same as choosers
and the series value is the alternative df index of chosen alternative

overflow_protection : bool, optional
If True, use overflow-protected exponentiation and normalization to
compute probabilities, which will help prevent problems if the utility
values are very large in magnitude (positive or negative). If False,
overflow-protected exponentiation and normalization will not be used,
and the model might fail if the utility values are very large in magnitude.
If None (the default), overflow-protection is turned on if
`allow_zero_probs` is False, and left off otherwise.

Returns
-------
if want_logsums is False:
Expand Down Expand Up @@ -251,6 +261,9 @@ def _interaction_sample_simulate(
column_labels=["alternative", "utility"],
)

if overflow_protection is None:
overflow_protection = not allow_zero_probs

# convert to probabilities (utilities exponentiated and normalized to probs)
# probs is same shape as utilities, one row per chooser and one column for alternative
if want_logsums:
Expand All @@ -260,7 +273,7 @@ def _interaction_sample_simulate(
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
overflow_protection=overflow_protection,
return_logsums=True,
)
chunk_sizer.log_df(trace_label, "logsums", logsums)
Expand All @@ -271,7 +284,7 @@ def _interaction_sample_simulate(
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
overflow_protection=overflow_protection,
)
chunk_sizer.log_df(trace_label, "probs", probs)

Expand Down
10 changes: 4 additions & 6 deletions activitysim/core/logit.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def utils_to_probs(
exponentiated=False,
allow_zero_probs=False,
trace_choosers=None,
overflow_protection: bool = True,
overflow_protection: bool = False,
return_logsums: bool = False,
):
"""
Expand All @@ -158,7 +158,7 @@ def utils_to_probs(
by report_bad_choices because it can't deduce hh_id from the interaction_dataset
which is indexed on index values from alternatives df

overflow_protection : bool, default True
overflow_protection : bool, default False
Always shift utility values such that the maximum utility in each row is
zero. This constant per-row shift should not fundamentally alter the
computed probabilities, but will ensure that an overflow does not occur
Expand Down Expand Up @@ -194,10 +194,8 @@ def utils_to_probs(
raise ValueError(
"cannot prevent expected overflow with allow_zero_probs"
)
else:
overflow_protection = overflow_protection or (
utils_arr.dtype == np.float32 and utils_arr.max() > 85
)
elif overflow_protection is None:
overflow_protection = utils_arr.dtype == np.float32 and utils_arr.max() > 85

if overflow_protection:
# exponentiated utils will overflow, downshift them
Expand Down
Loading
Loading