Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
141 commits
Select commit Hold shift + click to select a range
ec8ae75
notebook dev, adds nest coefficient in logit.py
May 3, 2022
c973be8
prototype work - return leaf and node (exp max) utilities, nest spec
May 4, 2022
a4a7630
adds choice based on walking nest levels
May 4, 2022
c6b609a
prototype simple_simulate
May 4, 2022
5dc1a44
baby steps towards validation
May 4, 2022
83a86f5
scale of leaf utility
May 4, 2022
5a7ad3c
pull out cruft
May 4, 2022
5e8bf16
towards validation of probs for single trip
May 5, 2022
a31d97f
probs trip set up
May 5, 2022
ab8a212
probs validation clean up
May 5, 2022
fd5ca14
fixes scale of nest utilities
May 5, 2022
f2cea72
two level still off
May 6, 2022
4883c33
recursive utility calculation fix
May 6, 2022
b60702b
clean up
May 6, 2022
7c21407
adds nested utility scale
May 6, 2022
932d5ad
scaling in recursive nest utilities
May 6, 2022
1286183
output clean up
May 6, 2022
c95f68d
fixes bug in choice for tree
May 8, 2022
9f4d262
check
May 8, 2022
6c17d89
caching nest level names and alternatives
May 8, 2022
7984392
removes trace in runtime tests
May 8, 2022
ebf64c6
larch notebook to calculate probs a different way
May 9, 2022
e02b46a
compare to larch probs
May 9, 2022
4ee74ea
clean up
May 9, 2022
4d0d823
inspecting a couple of test cases
May 9, 2022
fba8728
nest scales in asim seem to be all between 0 and 1, so not like in la…
May 10, 2022
e821b57
nest scaling comment
May 10, 2022
7bfb80a
adds comment regarding potential random choice runtime improvement
May 15, 2022
42901bf
adds goodness of fit test and loop over all trips
May 16, 2022
3700a9e
some validation metrics
May 16, 2022
75c16a2
validation
May 16, 2022
8668383
nicer comparison
May 16, 2022
921983f
adds more convergence analysis
May 17, 2022
7694563
simple siumlate mnl frozen rand ind util
Jun 15, 2022
0c6f4c8
nested now with alternative numbers, not names, change structure to b…
Jun 15, 2022
f725ca6
pass through frozen_impl flag
Jun 15, 2022
1a786f2
implement flag in settings, apply to all simple simulate models
Jun 15, 2022
1d4ca9a
froxen rand ind util in interaction simulate
Jun 15, 2022
f3e1610
add setting to interaction_simulate call
Jun 15, 2022
9ac51c9
usage of interaction_sample_simulate
Jun 15, 2022
e65635d
interaction sample notebook
Jun 16, 2022
9950f84
interaction_sample dev work
Jun 16, 2022
36789a3
interaction_sample dev work
Jun 16, 2022
64c4e6e
fixes alternative index as chosen value
Jun 16, 2022
e3e4326
interaction sample frozen rand indiv util
Jun 16, 2022
00208e7
adds interaction_sample at the module level, not individual model
Jun 17, 2022
005b131
adds interaction_sample at the module level, not individual model
Jun 17, 2022
bbce6aa
nested index instead of name
Jun 19, 2022
7fbba93
fies frozen_rand setting call
Jun 19, 2022
8c1d1ed
pass arguments in
Jun 19, 2022
9685aca
make place of call consistent
Jun 19, 2022
19d1222
remove debug print statement
Jun 19, 2022
61a314f
move reporting in make choices
Jun 19, 2022
8c16765
make choice indexing for nested logit consistent with mnl and cumsum MC
Jun 19, 2022
087ec0e
use fru in models that use logit.make_choices w/o simulate wrapper
Jun 19, 2022
c26c80c
no fru for probabilitic lookup tables
Jun 19, 2022
d14fac3
memory saving work - delete dfs wherever possible
Jun 20, 2022
f890799
fake rand tracing
Jun 20, 2022
043a0bd
working through all make_choices calls to implement memory saving hac…
Jun 20, 2022
f9b1e2e
working through all make_choices calls to implement memory saving hac…
Jun 20, 2022
4b5cad2
working through all make_choices calls to implement memory saving hac…
Jun 20, 2022
b48dd71
working through all make_choices calls to implement memory saving hac…
Jun 20, 2022
17792f5
working through all make_choices calls to implement memory saving hac…
Jun 20, 2022
affe2b6
remove usage of choose_individual_max_utility
Jun 20, 2022
6b38f12
remove usage of choose_individual_max_utility
Jun 20, 2022
0387c35
remove usage of choose_individual_max_utility
Jun 20, 2022
8ac91f1
remove usage of choose_individual_max_utility
Jun 20, 2022
eea84ca
remove usage of choose_individual_max_utility
Jun 20, 2022
cb807d1
remove usage of choose_individual_max_utility
Jun 20, 2022
f0cc563
interaction_sample util based w/p probs
Jun 20, 2022
417e90f
wrong method in cdap
Jun 20, 2022
4f825ae
style fixes
Jun 20, 2022
7a6cc29
need probs in interaction_sample - try it the quick way
Jun 20, 2022
f0143e9
remove false comparison
Jun 20, 2022
f6d81d6
bug fix
Jun 20, 2022
b449593
bug fix
Jun 20, 2022
dbc809a
add utility based choice option for transit virtual pathbuilder
Jun 20, 2022
4c6a524
custom chooser
Jun 20, 2022
2ec41b1
mem tracing in interaction_sample
Jun 20, 2022
47601e3
bug fix
Jun 20, 2022
e0555d4
interaction_sample memory saving implementation
Jun 20, 2022
2568b72
memory work
Jun 20, 2022
78630be
spelling
Jun 20, 2022
c6d75d1
delete after last ref, not before
Jun 20, 2022
7a66a67
set index
Jun 20, 2022
7df4c8d
to numpy once
Jun 20, 2022
d6f4f97
remove comment
Jun 20, 2022
26a7090
interaction sample dev
Jun 21, 2022
313bcd0
remove scale and location params from ev1 quartile fct
Jun 21, 2022
6451abd
use numpy's gumbel to draw from ev1
Jun 24, 2022
3973bcd
clean up
Jun 24, 2022
c0d1ca1
gumbel by hand by inverse cdf
Jun 24, 2022
6b0b4e9
memory logging
Jun 27, 2022
9a17395
resolves merge conflicts with main v1.2
Jan 31, 2023
b86093f
Merge branch 'master' into janzill/utilit_based_choices_2025
janzill Mar 29, 2025
792d183
merge bug
janzill Mar 29, 2025
08d013a
more merge bugs
janzill Mar 29, 2025
75bb84d
more merge fixes, example runs through
janzill Mar 29, 2025
c6feaef
log eet setting on start up
janzill Mar 29, 2025
114b747
some variation checks, dev notebooks, to delete
janzill Apr 2, 2025
e1e9d08
seeding corr test
janzill Apr 2, 2025
14441a6
corr
janzill Apr 2, 2025
73b8c32
sign on logsum
janzill Apr 2, 2025
f5f365f
removes notebook folder
janzill Apr 2, 2025
0f54cec
compute_setting overrides for eet, currently only for interaction_sam…
janzill Apr 6, 2025
11d86a1
Adds subcomponent-specific eet setting
janzill Apr 6, 2025
30ddb3e
default compute settings set before, no need to check if none
janzill Apr 6, 2025
5105b17
Merge remote-tracking branch 'origin/master' into janzill/utilit_base…
janzill Apr 6, 2025
c6d60c6
comment clean up
janzill Apr 11, 2025
6a0987d
identifies eet todos
janzill Apr 11, 2025
5637fb0
test SOA MC for disagg access
janzill Apr 11, 2025
6f8958b
lint
janzill Apr 11, 2025
9d12b08
comments
janzill May 24, 2025
9a0fb66
more comments
janzill May 24, 2025
0fa183e
jtp custom chooser for EET
janzill May 25, 2025
9900f48
lint
janzill May 25, 2025
892c3fb
move interaction sample without sampling out of MC loop to enable for…
janzill May 25, 2025
d6ecb0d
update comments
janzill May 25, 2025
3793ec2
fix no sampling in interaction_sample
janzill May 25, 2025
fc68697
fix _interaction_sample return doc
janzill May 25, 2025
28142cd
fix bool conversion of None for eet compute settings in sub-components
janzill May 25, 2025
5de1d23
compute settings in disaggreagte accessibility w/o hack
janzill May 25, 2025
2a563ff
lint
janzill May 25, 2025
ef0be21
fix nest_spec is None bug
janzill May 26, 2025
1f815e8
remove comment
janzill May 26, 2025
41d49ff
clean up comments
janzill May 26, 2025
582c998
comment clean up
janzill May 26, 2025
49fcaa5
Merge remote-tracking branch 'origin/master' into janzill/utilit_base…
janzill May 29, 2025
c40b989
avoid choosing alternatives corresponding to padded utilities in inte…
janzill May 29, 2025
5636cd9
lint
janzill May 29, 2025
94e629a
patch other instance of padded_utility for eet
janzill May 29, 2025
e102a9e
adds validate utility method to align with unavailable choices in MC …
janzill May 29, 2025
66cdac9
consistently treat utilities that would lead to zero choices in MC si…
janzill May 30, 2025
3b48a07
lint
janzill May 30, 2025
6f31c76
fix allow_zero_probs for prob calculation in EET interaction_sample
janzill May 30, 2025
2b4eb81
Merge tag 'v1.5.1' into matt/explicit_error_terms_update_v151
m-richards Jan 23, 2026
14fe80d
refactor estimation mode into method
m-richards Jan 12, 2026
2a8c882
make estimation choice set preservation work for eet
m-richards Jan 12, 2026
4f67fc6
Merge branch 'matt/explicit_error_terms_update_v151' into matt/eet_on…
m-richards Jan 23, 2026
71d9481
fix zero probs with new arg types
m-richards Feb 3, 2026
d115b3f
minor type checking
m-richards Feb 3, 2026
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
13 changes: 12 additions & 1 deletion activitysim/abm/models/disaggregate_accessibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@
from activitysim.abm.models.util import tour_destination
from activitysim.abm.tables import shadow_pricing
from activitysim.core import estimation, los, tracing, util, workflow
from activitysim.core.configuration.base import PreprocessorSettings, PydanticReadable
from activitysim.core.configuration.base import (
ComputeSettings,
PreprocessorSettings,
PydanticReadable,
)
from activitysim.core.configuration.logit import TourLocationComponentSettings
from activitysim.core.expressions import assign_columns

Expand Down Expand Up @@ -184,6 +188,8 @@ class DisaggregateAccessibilitySettings(PydanticReadable, extra="forbid"):
If not supplied or None, will default to the chunk size in the location choice model settings.
"""

compute_settings: ComputeSettings | None = None


def read_disaggregate_accessibility_yaml(
state: workflow.State, file_name
Expand Down Expand Up @@ -783,6 +789,11 @@ def get_disaggregate_logsums(
if disagg_model_settings.explicit_chunk is not None:
model_settings.explicit_chunk = disagg_model_settings.explicit_chunk

# Can set compute settings for disaggregate accessibility
# Otherwise this will be set to whatever is in the location model settings
if disagg_model_settings.compute_settings is not None:
model_settings.compute_settings = disagg_model_settings.compute_settings

# Include the suffix tags to pass onto downstream logsum models (e.g., tour mode choice)
if model_settings.LOGSUM_SETTINGS:
suffixes = util.concat_suffix_dict(disagg_model_settings.suffixes)
Expand Down
64 changes: 51 additions & 13 deletions activitysim/abm/models/joint_tour_participation.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
)
from activitysim.core.configuration.base import ComputeSettings, PreprocessorSettings
from activitysim.core.configuration.logit import LogitComponentSettings
from activitysim.core.util import assign_in_place, reindex
from activitysim.core.exceptions import InvalidTravelError
from activitysim.core.util import assign_in_place, reindex

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -127,7 +127,7 @@ def get_tour_satisfaction(candidates, participate):

def participants_chooser(
state: workflow.State,
probs: pd.DataFrame,
probs_or_utils: pd.DataFrame,
choosers: pd.DataFrame,
spec: pd.DataFrame,
trace_label: str,
Expand All @@ -147,9 +147,10 @@ def participants_chooser(

Parameters
----------
probs : pandas.DataFrame
probs_or_utils : pandas.DataFrame
Rows for choosers and columns for the alternatives from which they
are choosing. Values are expected to be valid probabilities across
are choosing. If running with explicit_error_terms, these are utilities.
Otherwise, values are expected to be valid probabilities across
each row, e.g. they should sum to 1.
choosers : pandas.dataframe
simple_simulate choosers df
Expand All @@ -166,7 +167,7 @@ def participants_chooser(

"""

assert probs.index.equals(choosers.index)
assert probs_or_utils.index.equals(choosers.index)

# choice is boolean (participate or not)
model_settings = JointTourParticipationSettings.read_settings_file(
Expand Down Expand Up @@ -202,7 +203,7 @@ def participants_chooser(
"%s max iterations exceeded (%s).", trace_label, MAX_ITERATIONS
)
diagnostic_cols = ["tour_id", "household_id", "composition", "adult"]
unsatisfied_candidates = candidates[diagnostic_cols].join(probs)
unsatisfied_candidates = candidates[diagnostic_cols].join(probs_or_utils)
state.tracing.write_csv(
unsatisfied_candidates,
file_name="%s.UNSATISFIED" % trace_label,
Expand All @@ -215,9 +216,31 @@ def participants_chooser(
f"Forcing joint tour participation for {num_tours_remaining} tours."
)
# anybody with probability > 0 is forced to join the joint tour
probs[choice_col] = np.where(probs[choice_col] > 0, 1, 0)
non_choice_col = [col for col in probs.columns if col != choice_col][0]
probs[non_choice_col] = 1 - probs[choice_col]
if state.settings.use_explicit_error_terms:
# need "is valid choice" such that we certainly choose those with non-zero values,
# and do not choose others. Let's use 3.0 as large value here.
probs_or_utils[choice_col] = np.where(
probs_or_utils[choice_col] > logit.UTIL_MIN,
3.0,
logit.UTIL_UNAVAILABLE,
)
non_choice_col = [
col for col in probs_or_utils.columns if col != choice_col
][0]
probs_or_utils[non_choice_col] = np.where(
probs_or_utils[choice_col] <= logit.UTIL_MIN,
3.0,
logit.UTIL_UNAVAILABLE,
)
else:
probs_or_utils[choice_col] = np.where(
probs_or_utils[choice_col] > 0, 1, 0
)
non_choice_col = [
col for col in probs_or_utils.columns if col != choice_col
][0]
probs_or_utils[non_choice_col] = 1 - probs_or_utils[choice_col]

if iter > MAX_ITERATIONS + 1:
raise InvalidTravelError(
f"{num_tours_remaining} tours could not be satisfied even with forcing participation"
Expand All @@ -227,8 +250,13 @@ def participants_chooser(
f"{num_tours_remaining} tours could not be satisfied after {iter} iterations"
)

choices, rands = logit.make_choices(
state, probs, trace_label=trace_label, trace_choosers=choosers
choice_function = (
logit.make_choices_utility_based
if state.settings.use_explicit_error_terms
else logit.make_choices
)
choices, rands = choice_function(
state, probs_or_utils, trace_label=trace_label, trace_choosers=choosers
)
participate = choices == PARTICIPATE_CHOICE

Expand All @@ -252,7 +280,7 @@ def participants_chooser(
rands_list.append(rands[satisfied])

# remove candidates of satisfied tours
probs = probs[~satisfied]
probs_or_utils = probs_or_utils[~satisfied]
candidates = candidates[~satisfied]

logger.debug(
Expand Down Expand Up @@ -401,6 +429,16 @@ def joint_tour_participation(
if i not in model_settings.compute_settings.protect_columns:
model_settings.compute_settings.protect_columns.append(i)

# TODO EET: this is related to the difference in nested logit and logit choice as per comment in
# make_choices_utility_based. As soon as alt_order_array is removed from arguments to
# make_choices_explicit_error_term_nl this guard can be removed
if state.settings.use_explicit_error_terms:
assert (
nest_spec is None
), "Nested logit model custom chooser for EET requires name_mapping, currently not implemented in jtp"

custom_chooser = participants_chooser

choices = simulate.simple_simulate_by_chunk_id(
state,
choosers=candidates,
Expand All @@ -409,7 +447,7 @@ def joint_tour_participation(
locals_d=constants,
trace_label=trace_label,
trace_choice_name="participation",
custom_chooser=participants_chooser,
custom_chooser=custom_chooser,
estimator=estimator,
compute_settings=model_settings.compute_settings,
)
Expand Down
60 changes: 37 additions & 23 deletions activitysim/abm/models/trip_departure_choice.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
PreprocessorSettings,
PydanticCompute,
)
from activitysim.core.exceptions import SegmentedSpecificationError
from activitysim.core.skim_dataset import SkimDataset
from activitysim.core.skim_dictionary import SkimDict
from activitysim.core.util import reindex
from activitysim.core.exceptions import SegmentedSpecificationError

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -351,37 +351,51 @@ def choose_tour_leg_pattern(
column_labels=["alternative", "utility"],
)

# 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(
state, utilities_df, trace_label=trace_label, trace_choosers=trip_segment
)
if state.settings.use_explicit_error_terms:
utilities_df = logit.validate_utils(
state, utilities_df, trace_label=trace_label, trace_choosers=trip_segment
)
# make choices
# positions is series with the chosen alternative represented as a column index in probs
# which is an integer between zero and num alternatives in the alternative sample
positions, rands = logit.make_choices_utility_based(
state, utilities_df, trace_label=trace_label, trace_choosers=trip_segment
)

del utilities_df
chunk_sizer.log_df(trace_label, "utilities_df", None)
else:
# 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(
state, utilities_df, trace_label=trace_label, trace_choosers=trip_segment
)

chunk_sizer.log_df(trace_label, "probs", probs)
chunk_sizer.log_df(trace_label, "probs", probs)

del utilities_df
chunk_sizer.log_df(trace_label, "utilities_df", None)
del utilities_df
chunk_sizer.log_df(trace_label, "utilities_df", None)

if have_trace_targets:
state.tracing.trace_df(
probs,
tracing.extend_trace_label(trace_label, "probs"),
column_labels=["alternative", "probability"],
if have_trace_targets:
state.tracing.trace_df(
probs,
tracing.extend_trace_label(trace_label, "probs"),
column_labels=["alternative", "probability"],
)

# make choices
# positions is series with the chosen alternative represented as a column index in probs
# which is an integer between zero and num alternatives in the alternative sample
positions, rands = logit.make_choices(
state, probs, trace_label=trace_label, trace_choosers=trip_segment
)

# make choices
# positions is series with the chosen alternative represented as a column index in probs
# which is an integer between zero and num alternatives in the alternative sample
positions, rands = logit.make_choices(
state, probs, trace_label=trace_label, trace_choosers=trip_segment
)
del probs
chunk_sizer.log_df(trace_label, "probs", None)

chunk_sizer.log_df(trace_label, "positions", positions)
chunk_sizer.log_df(trace_label, "rands", rands)

del probs
chunk_sizer.log_df(trace_label, "probs", None)

# shouldn't have chosen any of the dummy pad utilities
assert positions.max() < max_sample_count

Expand Down
29 changes: 20 additions & 9 deletions activitysim/abm/models/util/cdap.py
Original file line number Diff line number Diff line change
Expand Up @@ -999,11 +999,18 @@ def household_activity_choices(
# add joint util to util
utils = utils.add(joint_tour_utils)

probs = logit.utils_to_probs(state, utils, trace_label=trace_label)
if state.settings.use_explicit_error_terms:
utils = logit.validate_utils(state, utils, trace_label=trace_label)

# select an activity pattern alternative for each household based on probability
# result is a series indexed on _hh_index_ with the (0 based) index of the column from probs
idx_choices, rands = logit.make_choices(state, probs, trace_label=trace_label)
idx_choices, rands = logit.make_choices_utility_based(
state, utils, trace_label=trace_label
)
else:
probs = logit.utils_to_probs(state, utils, trace_label=trace_label)

# select an activity pattern alternative for each household based on probability
# result is a series indexed on _hh_index_ with the (0 based) index of the column from probs
idx_choices, rands = logit.make_choices(state, probs, trace_label=trace_label)

# convert choice expressed as index into alternative name from util column label
choices = pd.Series(utils.columns[idx_choices].values, index=utils.index)
Expand All @@ -1021,16 +1028,20 @@ def household_activity_choices(
"%s.hhsize%d_utils" % (trace_label, hhsize),
column_labels=["expression", "household"],
)
state.tracing.trace_df(
probs,
"%s.hhsize%d_probs" % (trace_label, hhsize),
column_labels=["expression", "household"],
)

if not state.settings.use_explicit_error_terms:
state.tracing.trace_df(
probs,
"%s.hhsize%d_probs" % (trace_label, hhsize),
column_labels=["expression", "household"],
)

state.tracing.trace_df(
choices,
"%s.hhsize%d_activity_choices" % (trace_label, hhsize),
column_labels=["expression", "household"],
)

state.tracing.trace_df(
rands, "%s.hhsize%d_rands" % (trace_label, hhsize), columns=[None, "rand"]
)
Expand Down
12 changes: 12 additions & 0 deletions activitysim/core/configuration/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ class ComputeSettings(PydanticBase):
Sharrow settings for a component.
"""

# Make this more general compute settings and use for explicit error term overrides
# Default None work for sub-components defined in getter below (eet_subcomponent)
use_explicit_error_terms: None | bool | dict[str, bool] = None

sharrow_skip: bool | dict[str, bool] = False
"""Skip sharrow when evaluating this component.

Expand Down Expand Up @@ -218,6 +222,13 @@ def should_skip(self, subcomponent: str) -> bool:
else:
return bool(self.sharrow_skip)

def eet_subcomponent(self, subcomponent: str) -> bool:
"""Check for EET overrides for a particular subcomponent."""
if isinstance(self.use_explicit_error_terms, dict):
return self.use_explicit_error_terms.get(subcomponent, None)
else:
return self.use_explicit_error_terms

@contextmanager
def pandas_option_context(self):
"""Context manager to set pandas options for compute settings."""
Expand Down Expand Up @@ -266,6 +277,7 @@ def subcomponent_settings(self, subcomponent: str) -> ComputeSettings:
use_numba=self.use_numba,
drop_unused_columns=self.drop_unused_columns,
protect_columns=self.protect_columns,
use_explicit_error_terms=self.eet_subcomponent(subcomponent),
)


Expand Down
14 changes: 13 additions & 1 deletion activitysim/core/configuration/top.py
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,7 @@ def _check_store_skims_in_shm(self):
"memory_profile",
"instrument",
"sharrow",
"use_explicit_error_terms",
)
"""
Setting to log on startup.
Expand Down Expand Up @@ -778,7 +779,18 @@ def _check_store_skims_in_shm(self):
"""
run checks to validate that YAML settings files are loadable and spec and coefficent csv can be resolved.

should catch many common errors early, including missing required configurations or specified coefficient labels without defined values.
should catch many common errors early, including missing required configurations or specified coefficient labels without defined values.
"""

use_explicit_error_terms: bool = False
"""
Make choice from random utility model by drawing from distribution of unobserved
part of utility and taking the maximum of total utility.

Defaults to standard Monte Carlo method, i.e., calculating probabilities and then
drawing a single uniform random number to draw from cumulative probabily.

.. versionadded:: 1.x
"""

other_settings: dict[str, Any] = None
Expand Down
Loading