Merged
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/core/interaction_sample.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -404,6 +404,7 @@ def _interaction_sample(
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
)
chunk_sizer.log_df(trace_label, "probs", probs)

Expand Down
29 changes: 18 additions & 11 deletions activitysim/core/interaction_sample_simulate.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -248,20 +248,27 @@ def _interaction_sample_simulate(

# 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,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
)
chunk_sizer.log_df(trace_label, "probs", probs)

if want_logsums:
logsums = logit.utils_to_logsums(
utilities_df, allow_zero_probs=allow_zero_probs
probs, logsums = logit.utils_to_probs(
state,
utilities_df,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
return_logsums=True,
)
chunk_sizer.log_df(trace_label, "logsums", logsums)
else:
probs = logit.utils_to_probs(
state,
utilities_df,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
)
chunk_sizer.log_df(trace_label, "probs", probs)

del utilities_df
chunk_sizer.log_df(trace_label, "utilities_df", None)
Expand Down
50 changes: 48 additions & 2 deletions activitysim/core/logit.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
from __future__ import annotations

import logging
import warnings
from builtins import object

import numpy as np
Expand DownExpand Up@@ -130,6 +131,8 @@ def utils_to_probs(
exponentiated=False,
allow_zero_probs=False,
trace_choosers=None,
overflow_protection: bool = True,
return_logsums: bool = False,
):
"""
Convert a table of utilities to probabilities.
Expand All@@ -155,6 +158,20 @@ 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
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
that will create infinite or NaN values. This will also provide effective
protection against underflow; extremely rare probabilities will round to
zero, but by definition they are extremely rare and losing them entirely
should not impact the simulation in a measureable fashion, and at least one
(and sometimes only one) alternative is guaranteed to have non-zero
probability, as long as at least one alternative has a finite utility value.
If utility values are certain to be well-behaved and non-extreme, enabling
overflow_protection will have no benefit but impose a modest computational
overhead cost.

Returns
-------
probs : pandas.DataFrame
Expand All@@ -167,9 +184,27 @@ def utils_to_probs(
# utils_arr = utils.values.astype('float')
utils_arr = utils.values

if utils_arr.dtype == np.float32 and utils_arr.max() > 85:
if allow_zero_probs:
if overflow_protection:
warnings.warn(
"cannot set overflow_protection with allow_zero_probs", stacklevel=2
)
overflow_protection = utils_arr.dtype == np.float32 and utils_arr.max() > 85
if overflow_protection:
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
)

if overflow_protection:
# exponentiated utils will overflow, downshift them
utils_arr -= utils_arr.max(1, keepdims=True)
shifts = utils_arr.max(1, keepdims=True)
utils_arr -= shifts
else:
shifts = None

if not exponentiated:
# TODO: reduce memory usage by exponentiating in-place.
Expand All@@ -185,6 +220,15 @@ def utils_to_probs(

arr_sum = utils_arr.sum(axis=1)

if return_logsums:
with np.errstate(divide="ignore" if allow_zero_probs else "warn"):
logsums = np.log(arr_sum)
if shifts is not None:
logsums += np.squeeze(shifts, 1)
logsums = pd.Series(logsums, index=utils.index)
else:
logsums = None

if not allow_zero_probs:
zero_probs = arr_sum == 0.0
if zero_probs.any():
Expand DownExpand Up@@ -222,6 +266,8 @@ def utils_to_probs(

probs = pd.DataFrame(utils_arr, columns=utils.columns, index=utils.index)

if return_logsums:
return probs, logsums
return probs


Expand Down
1 change: 1 addition & 0 deletions activitysim/core/pathbuilder.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -994,6 +994,7 @@ def build_virtual_path(
utilities_df,
allow_zero_probs=True,
trace_label=trace_label,
overflow_protection=False,
)
chunk_sizer.log_df(trace_label, "probs", probs)

Expand Down
1 change: 1 addition & 0 deletions activitysim/core/simulate.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -1032,6 +1032,7 @@ def compute_nested_probabilities(
trace_label=trace_label,
exponentiated=True,
allow_zero_probs=True,
overflow_protection=False,
)

nested_probabilities = pd.concat([nested_probabilities, probs], axis=1)
Expand Down
28 changes: 26 additions & 2 deletions activitysim/core/test/test_logit.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,16 +81,40 @@ def test_utils_to_probs_raises():
idx = pd.Index(name="household_id", data=[1])
with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state, pd.DataFrame([[1, 2, np.inf, 3]], index=idx), trace_label=None
state,
pd.DataFrame([[1, 2, np.inf, 3]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "infinite exponentiated utilities" in str(excinfo.value)

with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state, pd.DataFrame([[-999, -999, -999, -999]], index=idx), trace_label=None
state,
pd.DataFrame([[1, 2, 9999, 3]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "infinite exponentiated utilities" in str(excinfo.value)

with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state,
pd.DataFrame([[-999, -999, -999, -999]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "all probabilities are zero" in str(excinfo.value)

# test that overflow protection works
z = logit.utils_to_probs(
state,
pd.DataFrame([[1, 2, 9999, 3]], index=idx),
trace_label=None,
overflow_protection=True,
)
assert np.asarray(z).ravel() == pytest.approx(np.asarray([0.0, 0.0, 1.0, 0.0]))


def test_make_choices_only_one():
state = workflow.State().default_settings()
Expand Down
2 changes: 1 addition & 1 deletion activitysim/estimation/test/test_larch_estimation.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -131,7 +131,7 @@ def test_location_model(
[
("non_mandatory_tour_scheduling", "SLSQP"),
("joint_tour_scheduling", "SLSQP"),
("atwork_subtour_scheduling", "SLSQP"),
# ("atwork_subtour_scheduling", "SLSQP"), # TODO this test is unstable, needs to be updated with better data
("mandatory_tour_scheduling_work", "SLSQP"),
("mandatory_tour_scheduling_school", "SLSQP"),
],
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@ tour_id,person_id,tour_type,tour_type_count,tour_type_num,tour_num,tour_count,to
2373898,57899,work,1,1,1,1,mandatory,1,3402.0,3746.0,20552,47.0,7.0,17.0,10.0,,,WALK,1.0388895039783694,no_subtours,,0out_0in,work
2373980,57901,work,2,1,1,2,mandatory,1,3115.0,3746.0,20552,25.0,6.0,12.0,6.0,,,SHARED3FREE,0.6022315390131013,no_subtours,,0out_0in,work
2373981,57901,work,2,2,2,2,mandatory,1,3115.0,3746.0,20552,150.0,15.0,20.0,5.0,,,SHARED2FREE,0.6232767878249469,no_subtours,,1out_0in,work
2563802,62531,school,1,1,1,1,mandatory,1,3460.0,3316.0,21869,180.0,20.0,20.0,0.0,,,SHARED3FREE,-0.7094603590463964,,,0out_0in,school
2563802,62531,school,1,1,1,1,mandatory,1,3460.0,3316.0,21869,181.0,20.0,21.0,1.0,,,SHARED3FREE,-0.7094603590463964,,,0out_0in,school
2563821,62532,escort,1,1,1,1,non_mandatory,1,3398.0,3316.0,21869,20.0,6.0,7.0,1.0,,12.499268454965652,SHARED2FREE,-1.4604154628072699,,,0out_0in,escort
2563862,62533,escort,3,1,1,4,non_mandatory,1,3402.0,3316.0,21869,1.0,5.0,6.0,1.0,,12.534424209198946,SHARED3FREE,-1.2940574569954848,,,0out_3in,escort
2563863,62533,escort,3,2,2,4,non_mandatory,1,3519.0,3316.0,21869,99.0,11.0,11.0,0.0,,12.466623656700463,SHARED2FREE,-0.9326373013150777,,,0out_0in,escort
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,7 +55,7 @@ trip_id,person_id,household_id,primary_purpose,trip_num,outbound,trip_count,dest
18991850,57901,20552,work,2,True,2,3115,3460,2373981,work,,16,DRIVEALONEFREE,0.10597046751418379
18991853,57901,20552,work,1,False,1,3746,3115,2373981,home,,20,SHARED2FREE,0.23660752783217825
20510417,62531,21869,school,1,True,1,3460,3316,2563802,school,,20,SHARED3FREE,-1.4448137456466916
20510421,62531,21869,school,1,False,1,3316,3460,2563802,home,,20,WALK,-1.5207459403958272
20510421,62531,21869,school,1,False,1,3316,3460,2563802,home,,21,WALK,-1.5207459403958272
20510569,62532,21869,escort,1,True,1,3398,3316,2563821,escort,,6,SHARED2FREE,0.17869598454022895
20510573,62532,21869,escort,1,False,1,3316,3398,2563821,home,,7,DRIVEALONEFREE,0.20045149458253975
20510897,62533,21869,escort,1,True,1,3402,3316,2563862,escort,,5,SHARED3FREE,0.7112775892674524
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
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/core/interaction_sample.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -404,6 +404,7 @@ def _interaction_sample(
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
)
chunk_sizer.log_df(trace_label, "probs", probs)

Expand Down
29 changes: 18 additions & 11 deletions activitysim/core/interaction_sample_simulate.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -248,20 +248,27 @@ def _interaction_sample_simulate(

# 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,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
)
chunk_sizer.log_df(trace_label, "probs", probs)

if want_logsums:
logsums = logit.utils_to_logsums(
utilities_df, allow_zero_probs=allow_zero_probs
probs, logsums = logit.utils_to_probs(
state,
utilities_df,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
return_logsums=True,
)
chunk_sizer.log_df(trace_label, "logsums", logsums)
else:
probs = logit.utils_to_probs(
state,
utilities_df,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
)
chunk_sizer.log_df(trace_label, "probs", probs)

del utilities_df
chunk_sizer.log_df(trace_label, "utilities_df", None)
Expand Down
50 changes: 48 additions & 2 deletions activitysim/core/logit.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
from __future__ import annotations

import logging
import warnings
from builtins import object

import numpy as np
Expand DownExpand Up@@ -130,6 +131,8 @@ def utils_to_probs(
exponentiated=False,
allow_zero_probs=False,
trace_choosers=None,
overflow_protection: bool = True,
return_logsums: bool = False,
):
"""
Convert a table of utilities to probabilities.
Expand All@@ -155,6 +158,20 @@ 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
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
that will create infinite or NaN values. This will also provide effective
protection against underflow; extremely rare probabilities will round to
zero, but by definition they are extremely rare and losing them entirely
should not impact the simulation in a measureable fashion, and at least one
(and sometimes only one) alternative is guaranteed to have non-zero
probability, as long as at least one alternative has a finite utility value.
If utility values are certain to be well-behaved and non-extreme, enabling
overflow_protection will have no benefit but impose a modest computational
overhead cost.

Returns
-------
probs : pandas.DataFrame
Expand All@@ -167,9 +184,27 @@ def utils_to_probs(
# utils_arr = utils.values.astype('float')
utils_arr = utils.values

if utils_arr.dtype == np.float32 and utils_arr.max() > 85:
if allow_zero_probs:
if overflow_protection:
warnings.warn(
"cannot set overflow_protection with allow_zero_probs", stacklevel=2
)
overflow_protection = utils_arr.dtype == np.float32 and utils_arr.max() > 85
if overflow_protection:
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
)

if overflow_protection:
# exponentiated utils will overflow, downshift them
utils_arr -= utils_arr.max(1, keepdims=True)
shifts = utils_arr.max(1, keepdims=True)
utils_arr -= shifts
else:
shifts = None

if not exponentiated:
# TODO: reduce memory usage by exponentiating in-place.
Expand All@@ -185,6 +220,15 @@ def utils_to_probs(

arr_sum = utils_arr.sum(axis=1)

if return_logsums:
with np.errstate(divide="ignore" if allow_zero_probs else "warn"):
logsums = np.log(arr_sum)
if shifts is not None:
logsums += np.squeeze(shifts, 1)
logsums = pd.Series(logsums, index=utils.index)
else:
logsums = None

if not allow_zero_probs:
zero_probs = arr_sum == 0.0
if zero_probs.any():
Expand DownExpand Up@@ -222,6 +266,8 @@ def utils_to_probs(

probs = pd.DataFrame(utils_arr, columns=utils.columns, index=utils.index)

if return_logsums:
return probs, logsums
return probs


Expand Down
1 change: 1 addition & 0 deletions activitysim/core/pathbuilder.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -994,6 +994,7 @@ def build_virtual_path(
utilities_df,
allow_zero_probs=True,
trace_label=trace_label,
overflow_protection=False,
)
chunk_sizer.log_df(trace_label, "probs", probs)

Expand Down
1 change: 1 addition & 0 deletions activitysim/core/simulate.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -1032,6 +1032,7 @@ def compute_nested_probabilities(
trace_label=trace_label,
exponentiated=True,
allow_zero_probs=True,
overflow_protection=False,
)

nested_probabilities = pd.concat([nested_probabilities, probs], axis=1)
Expand Down
28 changes: 26 additions & 2 deletions activitysim/core/test/test_logit.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,16 +81,40 @@ def test_utils_to_probs_raises():
idx = pd.Index(name="household_id", data=[1])
with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state, pd.DataFrame([[1, 2, np.inf, 3]], index=idx), trace_label=None
state,
pd.DataFrame([[1, 2, np.inf, 3]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "infinite exponentiated utilities" in str(excinfo.value)

with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state, pd.DataFrame([[-999, -999, -999, -999]], index=idx), trace_label=None
state,
pd.DataFrame([[1, 2, 9999, 3]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "infinite exponentiated utilities" in str(excinfo.value)

with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state,
pd.DataFrame([[-999, -999, -999, -999]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "all probabilities are zero" in str(excinfo.value)

# test that overflow protection works
z = logit.utils_to_probs(
state,
pd.DataFrame([[1, 2, 9999, 3]], index=idx),
trace_label=None,
overflow_protection=True,
)
assert np.asarray(z).ravel() == pytest.approx(np.asarray([0.0, 0.0, 1.0, 0.0]))


def test_make_choices_only_one():
state = workflow.State().default_settings()
Expand Down
2 changes: 1 addition & 1 deletion activitysim/estimation/test/test_larch_estimation.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -131,7 +131,7 @@ def test_location_model(
[
("non_mandatory_tour_scheduling", "SLSQP"),
("joint_tour_scheduling", "SLSQP"),
("atwork_subtour_scheduling", "SLSQP"),
# ("atwork_subtour_scheduling", "SLSQP"), # TODO this test is unstable, needs to be updated with better data
("mandatory_tour_scheduling_work", "SLSQP"),
("mandatory_tour_scheduling_school", "SLSQP"),
],
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@ tour_id,person_id,tour_type,tour_type_count,tour_type_num,tour_num,tour_count,to
2373898,57899,work,1,1,1,1,mandatory,1,3402.0,3746.0,20552,47.0,7.0,17.0,10.0,,,WALK,1.0388895039783694,no_subtours,,0out_0in,work
2373980,57901,work,2,1,1,2,mandatory,1,3115.0,3746.0,20552,25.0,6.0,12.0,6.0,,,SHARED3FREE,0.6022315390131013,no_subtours,,0out_0in,work
2373981,57901,work,2,2,2,2,mandatory,1,3115.0,3746.0,20552,150.0,15.0,20.0,5.0,,,SHARED2FREE,0.6232767878249469,no_subtours,,1out_0in,work
2563802,62531,school,1,1,1,1,mandatory,1,3460.0,3316.0,21869,180.0,20.0,20.0,0.0,,,SHARED3FREE,-0.7094603590463964,,,0out_0in,school
2563802,62531,school,1,1,1,1,mandatory,1,3460.0,3316.0,21869,181.0,20.0,21.0,1.0,,,SHARED3FREE,-0.7094603590463964,,,0out_0in,school
2563821,62532,escort,1,1,1,1,non_mandatory,1,3398.0,3316.0,21869,20.0,6.0,7.0,1.0,,12.499268454965652,SHARED2FREE,-1.4604154628072699,,,0out_0in,escort
2563862,62533,escort,3,1,1,4,non_mandatory,1,3402.0,3316.0,21869,1.0,5.0,6.0,1.0,,12.534424209198946,SHARED3FREE,-1.2940574569954848,,,0out_3in,escort
2563863,62533,escort,3,2,2,4,non_mandatory,1,3519.0,3316.0,21869,99.0,11.0,11.0,0.0,,12.466623656700463,SHARED2FREE,-0.9326373013150777,,,0out_0in,escort
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,7 +55,7 @@ trip_id,person_id,household_id,primary_purpose,trip_num,outbound,trip_count,dest
18991850,57901,20552,work,2,True,2,3115,3460,2373981,work,,16,DRIVEALONEFREE,0.10597046751418379
18991853,57901,20552,work,1,False,1,3746,3115,2373981,home,,20,SHARED2FREE,0.23660752783217825
20510417,62531,21869,school,1,True,1,3460,3316,2563802,school,,20,SHARED3FREE,-1.4448137456466916
20510421,62531,21869,school,1,False,1,3316,3460,2563802,home,,20,WALK,-1.5207459403958272
20510421,62531,21869,school,1,False,1,3316,3460,2563802,home,,21,WALK,-1.5207459403958272
20510569,62532,21869,escort,1,True,1,3398,3316,2563821,escort,,6,SHARED2FREE,0.17869598454022895
20510573,62532,21869,escort,1,False,1,3316,3398,2563821,home,,7,DRIVEALONEFREE,0.20045149458253975
20510897,62533,21869,escort,1,True,1,3402,3316,2563862,escort,,5,SHARED3FREE,0.7112775892674524
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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/core/interaction_sample.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -404,6 +404,7 @@ def _interaction_sample(
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
)
chunk_sizer.log_df(trace_label, "probs", probs)

Expand Down
29 changes: 18 additions & 11 deletions activitysim/core/interaction_sample_simulate.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -248,20 +248,27 @@ def _interaction_sample_simulate(

# 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,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
)
chunk_sizer.log_df(trace_label, "probs", probs)

if want_logsums:
logsums = logit.utils_to_logsums(
utilities_df, allow_zero_probs=allow_zero_probs
probs, logsums = logit.utils_to_probs(
state,
utilities_df,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
return_logsums=True,
)
chunk_sizer.log_df(trace_label, "logsums", logsums)
else:
probs = logit.utils_to_probs(
state,
utilities_df,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
)
chunk_sizer.log_df(trace_label, "probs", probs)

del utilities_df
chunk_sizer.log_df(trace_label, "utilities_df", None)
Expand Down
50 changes: 48 additions & 2 deletions activitysim/core/logit.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
from __future__ import annotations

import logging
import warnings
from builtins import object

import numpy as np
Expand DownExpand Up@@ -130,6 +131,8 @@ def utils_to_probs(
exponentiated=False,
allow_zero_probs=False,
trace_choosers=None,
overflow_protection: bool = True,
return_logsums: bool = False,
):
"""
Convert a table of utilities to probabilities.
Expand All@@ -155,6 +158,20 @@ 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
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
that will create infinite or NaN values. This will also provide effective
protection against underflow; extremely rare probabilities will round to
zero, but by definition they are extremely rare and losing them entirely
should not impact the simulation in a measureable fashion, and at least one
(and sometimes only one) alternative is guaranteed to have non-zero
probability, as long as at least one alternative has a finite utility value.
If utility values are certain to be well-behaved and non-extreme, enabling
overflow_protection will have no benefit but impose a modest computational
overhead cost.

Returns
-------
probs : pandas.DataFrame
Expand All@@ -167,9 +184,27 @@ def utils_to_probs(
# utils_arr = utils.values.astype('float')
utils_arr = utils.values

if utils_arr.dtype == np.float32 and utils_arr.max() > 85:
if allow_zero_probs:
if overflow_protection:
warnings.warn(
"cannot set overflow_protection with allow_zero_probs", stacklevel=2
)
overflow_protection = utils_arr.dtype == np.float32 and utils_arr.max() > 85
if overflow_protection:
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
)

if overflow_protection:
# exponentiated utils will overflow, downshift them
utils_arr -= utils_arr.max(1, keepdims=True)
shifts = utils_arr.max(1, keepdims=True)
utils_arr -= shifts
else:
shifts = None

if not exponentiated:
# TODO: reduce memory usage by exponentiating in-place.
Expand All@@ -185,6 +220,15 @@ def utils_to_probs(

arr_sum = utils_arr.sum(axis=1)

if return_logsums:
with np.errstate(divide="ignore" if allow_zero_probs else "warn"):
logsums = np.log(arr_sum)
if shifts is not None:
logsums += np.squeeze(shifts, 1)
logsums = pd.Series(logsums, index=utils.index)
else:
logsums = None

if not allow_zero_probs:
zero_probs = arr_sum == 0.0
if zero_probs.any():
Expand DownExpand Up@@ -222,6 +266,8 @@ def utils_to_probs(

probs = pd.DataFrame(utils_arr, columns=utils.columns, index=utils.index)

if return_logsums:
return probs, logsums
return probs


Expand Down
1 change: 1 addition & 0 deletions activitysim/core/pathbuilder.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -994,6 +994,7 @@ def build_virtual_path(
utilities_df,
allow_zero_probs=True,
trace_label=trace_label,
overflow_protection=False,
)
chunk_sizer.log_df(trace_label, "probs", probs)

Expand Down
1 change: 1 addition & 0 deletions activitysim/core/simulate.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -1032,6 +1032,7 @@ def compute_nested_probabilities(
trace_label=trace_label,
exponentiated=True,
allow_zero_probs=True,
overflow_protection=False,
)

nested_probabilities = pd.concat([nested_probabilities, probs], axis=1)
Expand Down
28 changes: 26 additions & 2 deletions activitysim/core/test/test_logit.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,16 +81,40 @@ def test_utils_to_probs_raises():
idx = pd.Index(name="household_id", data=[1])
with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state, pd.DataFrame([[1, 2, np.inf, 3]], index=idx), trace_label=None
state,
pd.DataFrame([[1, 2, np.inf, 3]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "infinite exponentiated utilities" in str(excinfo.value)

with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state, pd.DataFrame([[-999, -999, -999, -999]], index=idx), trace_label=None
state,
pd.DataFrame([[1, 2, 9999, 3]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "infinite exponentiated utilities" in str(excinfo.value)

with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state,
pd.DataFrame([[-999, -999, -999, -999]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "all probabilities are zero" in str(excinfo.value)

# test that overflow protection works
z = logit.utils_to_probs(
state,
pd.DataFrame([[1, 2, 9999, 3]], index=idx),
trace_label=None,
overflow_protection=True,
)
assert np.asarray(z).ravel() == pytest.approx(np.asarray([0.0, 0.0, 1.0, 0.0]))


def test_make_choices_only_one():
state = workflow.State().default_settings()
Expand Down
2 changes: 1 addition & 1 deletion activitysim/estimation/test/test_larch_estimation.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -131,7 +131,7 @@ def test_location_model(
[
("non_mandatory_tour_scheduling", "SLSQP"),
("joint_tour_scheduling", "SLSQP"),
("atwork_subtour_scheduling", "SLSQP"),
# ("atwork_subtour_scheduling", "SLSQP"), # TODO this test is unstable, needs to be updated with better data
("mandatory_tour_scheduling_work", "SLSQP"),
("mandatory_tour_scheduling_school", "SLSQP"),
],
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@ tour_id,person_id,tour_type,tour_type_count,tour_type_num,tour_num,tour_count,to
2373898,57899,work,1,1,1,1,mandatory,1,3402.0,3746.0,20552,47.0,7.0,17.0,10.0,,,WALK,1.0388895039783694,no_subtours,,0out_0in,work
2373980,57901,work,2,1,1,2,mandatory,1,3115.0,3746.0,20552,25.0,6.0,12.0,6.0,,,SHARED3FREE,0.6022315390131013,no_subtours,,0out_0in,work
2373981,57901,work,2,2,2,2,mandatory,1,3115.0,3746.0,20552,150.0,15.0,20.0,5.0,,,SHARED2FREE,0.6232767878249469,no_subtours,,1out_0in,work
2563802,62531,school,1,1,1,1,mandatory,1,3460.0,3316.0,21869,180.0,20.0,20.0,0.0,,,SHARED3FREE,-0.7094603590463964,,,0out_0in,school
2563802,62531,school,1,1,1,1,mandatory,1,3460.0,3316.0,21869,181.0,20.0,21.0,1.0,,,SHARED3FREE,-0.7094603590463964,,,0out_0in,school
2563821,62532,escort,1,1,1,1,non_mandatory,1,3398.0,3316.0,21869,20.0,6.0,7.0,1.0,,12.499268454965652,SHARED2FREE,-1.4604154628072699,,,0out_0in,escort
2563862,62533,escort,3,1,1,4,non_mandatory,1,3402.0,3316.0,21869,1.0,5.0,6.0,1.0,,12.534424209198946,SHARED3FREE,-1.2940574569954848,,,0out_3in,escort
2563863,62533,escort,3,2,2,4,non_mandatory,1,3519.0,3316.0,21869,99.0,11.0,11.0,0.0,,12.466623656700463,SHARED2FREE,-0.9326373013150777,,,0out_0in,escort
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,7 +55,7 @@ trip_id,person_id,household_id,primary_purpose,trip_num,outbound,trip_count,dest
18991850,57901,20552,work,2,True,2,3115,3460,2373981,work,,16,DRIVEALONEFREE,0.10597046751418379
18991853,57901,20552,work,1,False,1,3746,3115,2373981,home,,20,SHARED2FREE,0.23660752783217825
20510417,62531,21869,school,1,True,1,3460,3316,2563802,school,,20,SHARED3FREE,-1.4448137456466916
20510421,62531,21869,school,1,False,1,3316,3460,2563802,home,,20,WALK,-1.5207459403958272
20510421,62531,21869,school,1,False,1,3316,3460,2563802,home,,21,WALK,-1.5207459403958272
20510569,62532,21869,escort,1,True,1,3398,3316,2563821,escort,,6,SHARED2FREE,0.17869598454022895
20510573,62532,21869,escort,1,False,1,3316,3398,2563821,home,,7,DRIVEALONEFREE,0.20045149458253975
20510897,62533,21869,escort,1,True,1,3402,3316,2563862,escort,,5,SHARED3FREE,0.7112775892674524
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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/core/interaction_sample.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -404,6 +404,7 @@ def _interaction_sample(
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
)
chunk_sizer.log_df(trace_label, "probs", probs)

Expand Down
29 changes: 18 additions & 11 deletions activitysim/core/interaction_sample_simulate.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -248,20 +248,27 @@ def _interaction_sample_simulate(

# 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,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
)
chunk_sizer.log_df(trace_label, "probs", probs)

if want_logsums:
logsums = logit.utils_to_logsums(
utilities_df, allow_zero_probs=allow_zero_probs
probs, logsums = logit.utils_to_probs(
state,
utilities_df,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
return_logsums=True,
)
chunk_sizer.log_df(trace_label, "logsums", logsums)
else:
probs = logit.utils_to_probs(
state,
utilities_df,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
)
chunk_sizer.log_df(trace_label, "probs", probs)

del utilities_df
chunk_sizer.log_df(trace_label, "utilities_df", None)
Expand Down
50 changes: 48 additions & 2 deletions activitysim/core/logit.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
from __future__ import annotations

import logging
import warnings
from builtins import object

import numpy as np
Expand DownExpand Up@@ -130,6 +131,8 @@ def utils_to_probs(
exponentiated=False,
allow_zero_probs=False,
trace_choosers=None,
overflow_protection: bool = True,
return_logsums: bool = False,
):
"""
Convert a table of utilities to probabilities.
Expand All@@ -155,6 +158,20 @@ 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
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
that will create infinite or NaN values. This will also provide effective
protection against underflow; extremely rare probabilities will round to
zero, but by definition they are extremely rare and losing them entirely
should not impact the simulation in a measureable fashion, and at least one
(and sometimes only one) alternative is guaranteed to have non-zero
probability, as long as at least one alternative has a finite utility value.
If utility values are certain to be well-behaved and non-extreme, enabling
overflow_protection will have no benefit but impose a modest computational
overhead cost.

Returns
-------
probs : pandas.DataFrame
Expand All@@ -167,9 +184,27 @@ def utils_to_probs(
# utils_arr = utils.values.astype('float')
utils_arr = utils.values

if utils_arr.dtype == np.float32 and utils_arr.max() > 85:
if allow_zero_probs:
if overflow_protection:
warnings.warn(
"cannot set overflow_protection with allow_zero_probs", stacklevel=2
)
overflow_protection = utils_arr.dtype == np.float32 and utils_arr.max() > 85
if overflow_protection:
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
)

if overflow_protection:
# exponentiated utils will overflow, downshift them
utils_arr -= utils_arr.max(1, keepdims=True)
shifts = utils_arr.max(1, keepdims=True)
utils_arr -= shifts
else:
shifts = None

if not exponentiated:
# TODO: reduce memory usage by exponentiating in-place.
Expand All@@ -185,6 +220,15 @@ def utils_to_probs(

arr_sum = utils_arr.sum(axis=1)

if return_logsums:
with np.errstate(divide="ignore" if allow_zero_probs else "warn"):
logsums = np.log(arr_sum)
if shifts is not None:
logsums += np.squeeze(shifts, 1)
logsums = pd.Series(logsums, index=utils.index)
else:
logsums = None

if not allow_zero_probs:
zero_probs = arr_sum == 0.0
if zero_probs.any():
Expand DownExpand Up@@ -222,6 +266,8 @@ def utils_to_probs(

probs = pd.DataFrame(utils_arr, columns=utils.columns, index=utils.index)

if return_logsums:
return probs, logsums
return probs


Expand Down
1 change: 1 addition & 0 deletions activitysim/core/pathbuilder.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -994,6 +994,7 @@ def build_virtual_path(
utilities_df,
allow_zero_probs=True,
trace_label=trace_label,
overflow_protection=False,
)
chunk_sizer.log_df(trace_label, "probs", probs)

Expand Down
1 change: 1 addition & 0 deletions activitysim/core/simulate.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -1032,6 +1032,7 @@ def compute_nested_probabilities(
trace_label=trace_label,
exponentiated=True,
allow_zero_probs=True,
overflow_protection=False,
)

nested_probabilities = pd.concat([nested_probabilities, probs], axis=1)
Expand Down
28 changes: 26 additions & 2 deletions activitysim/core/test/test_logit.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,16 +81,40 @@ def test_utils_to_probs_raises():
idx = pd.Index(name="household_id", data=[1])
with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state, pd.DataFrame([[1, 2, np.inf, 3]], index=idx), trace_label=None
state,
pd.DataFrame([[1, 2, np.inf, 3]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "infinite exponentiated utilities" in str(excinfo.value)

with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state, pd.DataFrame([[-999, -999, -999, -999]], index=idx), trace_label=None
state,
pd.DataFrame([[1, 2, 9999, 3]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "infinite exponentiated utilities" in str(excinfo.value)

with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state,
pd.DataFrame([[-999, -999, -999, -999]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "all probabilities are zero" in str(excinfo.value)

# test that overflow protection works
z = logit.utils_to_probs(
state,
pd.DataFrame([[1, 2, 9999, 3]], index=idx),
trace_label=None,
overflow_protection=True,
)
assert np.asarray(z).ravel() == pytest.approx(np.asarray([0.0, 0.0, 1.0, 0.0]))


def test_make_choices_only_one():
state = workflow.State().default_settings()
Expand Down
2 changes: 1 addition & 1 deletion activitysim/estimation/test/test_larch_estimation.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -131,7 +131,7 @@ def test_location_model(
[
("non_mandatory_tour_scheduling", "SLSQP"),
("joint_tour_scheduling", "SLSQP"),
("atwork_subtour_scheduling", "SLSQP"),
# ("atwork_subtour_scheduling", "SLSQP"), # TODO this test is unstable, needs to be updated with better data
("mandatory_tour_scheduling_work", "SLSQP"),
("mandatory_tour_scheduling_school", "SLSQP"),
],
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@ tour_id,person_id,tour_type,tour_type_count,tour_type_num,tour_num,tour_count,to
2373898,57899,work,1,1,1,1,mandatory,1,3402.0,3746.0,20552,47.0,7.0,17.0,10.0,,,WALK,1.0388895039783694,no_subtours,,0out_0in,work
2373980,57901,work,2,1,1,2,mandatory,1,3115.0,3746.0,20552,25.0,6.0,12.0,6.0,,,SHARED3FREE,0.6022315390131013,no_subtours,,0out_0in,work
2373981,57901,work,2,2,2,2,mandatory,1,3115.0,3746.0,20552,150.0,15.0,20.0,5.0,,,SHARED2FREE,0.6232767878249469,no_subtours,,1out_0in,work
2563802,62531,school,1,1,1,1,mandatory,1,3460.0,3316.0,21869,180.0,20.0,20.0,0.0,,,SHARED3FREE,-0.7094603590463964,,,0out_0in,school
2563802,62531,school,1,1,1,1,mandatory,1,3460.0,3316.0,21869,181.0,20.0,21.0,1.0,,,SHARED3FREE,-0.7094603590463964,,,0out_0in,school
2563821,62532,escort,1,1,1,1,non_mandatory,1,3398.0,3316.0,21869,20.0,6.0,7.0,1.0,,12.499268454965652,SHARED2FREE,-1.4604154628072699,,,0out_0in,escort
2563862,62533,escort,3,1,1,4,non_mandatory,1,3402.0,3316.0,21869,1.0,5.0,6.0,1.0,,12.534424209198946,SHARED3FREE,-1.2940574569954848,,,0out_3in,escort
2563863,62533,escort,3,2,2,4,non_mandatory,1,3519.0,3316.0,21869,99.0,11.0,11.0,0.0,,12.466623656700463,SHARED2FREE,-0.9326373013150777,,,0out_0in,escort
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,7 +55,7 @@ trip_id,person_id,household_id,primary_purpose,trip_num,outbound,trip_count,dest
18991850,57901,20552,work,2,True,2,3115,3460,2373981,work,,16,DRIVEALONEFREE,0.10597046751418379
18991853,57901,20552,work,1,False,1,3746,3115,2373981,home,,20,SHARED2FREE,0.23660752783217825
20510417,62531,21869,school,1,True,1,3460,3316,2563802,school,,20,SHARED3FREE,-1.4448137456466916
20510421,62531,21869,school,1,False,1,3316,3460,2563802,home,,20,WALK,-1.5207459403958272
20510421,62531,21869,school,1,False,1,3316,3460,2563802,home,,21,WALK,-1.5207459403958272
20510569,62532,21869,escort,1,True,1,3398,3316,2563821,escort,,6,SHARED2FREE,0.17869598454022895
20510573,62532,21869,escort,1,False,1,3316,3398,2563821,home,,7,DRIVEALONEFREE,0.20045149458253975
20510897,62533,21869,escort,1,True,1,3402,3316,2563862,escort,,5,SHARED3FREE,0.7112775892674524
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
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/core/interaction_sample.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -404,6 +404,7 @@ def _interaction_sample(
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
)
chunk_sizer.log_df(trace_label, "probs", probs)

Expand Down
29 changes: 18 additions & 11 deletions activitysim/core/interaction_sample_simulate.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -248,20 +248,27 @@ def _interaction_sample_simulate(

# 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,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
)
chunk_sizer.log_df(trace_label, "probs", probs)

if want_logsums:
logsums = logit.utils_to_logsums(
utilities_df, allow_zero_probs=allow_zero_probs
probs, logsums = logit.utils_to_probs(
state,
utilities_df,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
return_logsums=True,
)
chunk_sizer.log_df(trace_label, "logsums", logsums)
else:
probs = logit.utils_to_probs(
state,
utilities_df,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
)
chunk_sizer.log_df(trace_label, "probs", probs)

del utilities_df
chunk_sizer.log_df(trace_label, "utilities_df", None)
Expand Down
50 changes: 48 additions & 2 deletions activitysim/core/logit.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
from __future__ import annotations

import logging
import warnings
from builtins import object

import numpy as np
Expand DownExpand Up@@ -130,6 +131,8 @@ def utils_to_probs(
exponentiated=False,
allow_zero_probs=False,
trace_choosers=None,
overflow_protection: bool = True,
return_logsums: bool = False,
):
"""
Convert a table of utilities to probabilities.
Expand All@@ -155,6 +158,20 @@ 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
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
that will create infinite or NaN values. This will also provide effective
protection against underflow; extremely rare probabilities will round to
zero, but by definition they are extremely rare and losing them entirely
should not impact the simulation in a measureable fashion, and at least one
(and sometimes only one) alternative is guaranteed to have non-zero
probability, as long as at least one alternative has a finite utility value.
If utility values are certain to be well-behaved and non-extreme, enabling
overflow_protection will have no benefit but impose a modest computational
overhead cost.

Returns
-------
probs : pandas.DataFrame
Expand All@@ -167,9 +184,27 @@ def utils_to_probs(
# utils_arr = utils.values.astype('float')
utils_arr = utils.values

if utils_arr.dtype == np.float32 and utils_arr.max() > 85:
if allow_zero_probs:
if overflow_protection:
warnings.warn(
"cannot set overflow_protection with allow_zero_probs", stacklevel=2
)
overflow_protection = utils_arr.dtype == np.float32 and utils_arr.max() > 85
if overflow_protection:
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
)

if overflow_protection:
# exponentiated utils will overflow, downshift them
utils_arr -= utils_arr.max(1, keepdims=True)
shifts = utils_arr.max(1, keepdims=True)
utils_arr -= shifts
else:
shifts = None

if not exponentiated:
# TODO: reduce memory usage by exponentiating in-place.
Expand All@@ -185,6 +220,15 @@ def utils_to_probs(

arr_sum = utils_arr.sum(axis=1)

if return_logsums:
with np.errstate(divide="ignore" if allow_zero_probs else "warn"):
logsums = np.log(arr_sum)
if shifts is not None:
logsums += np.squeeze(shifts, 1)
logsums = pd.Series(logsums, index=utils.index)
else:
logsums = None

if not allow_zero_probs:
zero_probs = arr_sum == 0.0
if zero_probs.any():
Expand DownExpand Up@@ -222,6 +266,8 @@ def utils_to_probs(

probs = pd.DataFrame(utils_arr, columns=utils.columns, index=utils.index)

if return_logsums:
return probs, logsums
return probs


Expand Down
1 change: 1 addition & 0 deletions activitysim/core/pathbuilder.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -994,6 +994,7 @@ def build_virtual_path(
utilities_df,
allow_zero_probs=True,
trace_label=trace_label,
overflow_protection=False,
)
chunk_sizer.log_df(trace_label, "probs", probs)

Expand Down
1 change: 1 addition & 0 deletions activitysim/core/simulate.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -1032,6 +1032,7 @@ def compute_nested_probabilities(
trace_label=trace_label,
exponentiated=True,
allow_zero_probs=True,
overflow_protection=False,
)

nested_probabilities = pd.concat([nested_probabilities, probs], axis=1)
Expand Down
28 changes: 26 additions & 2 deletions activitysim/core/test/test_logit.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,16 +81,40 @@ def test_utils_to_probs_raises():
idx = pd.Index(name="household_id", data=[1])
with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state, pd.DataFrame([[1, 2, np.inf, 3]], index=idx), trace_label=None
state,
pd.DataFrame([[1, 2, np.inf, 3]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "infinite exponentiated utilities" in str(excinfo.value)

with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state, pd.DataFrame([[-999, -999, -999, -999]], index=idx), trace_label=None
state,
pd.DataFrame([[1, 2, 9999, 3]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "infinite exponentiated utilities" in str(excinfo.value)

with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state,
pd.DataFrame([[-999, -999, -999, -999]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "all probabilities are zero" in str(excinfo.value)

# test that overflow protection works
z = logit.utils_to_probs(
state,
pd.DataFrame([[1, 2, 9999, 3]], index=idx),
trace_label=None,
overflow_protection=True,
)
assert np.asarray(z).ravel() == pytest.approx(np.asarray([0.0, 0.0, 1.0, 0.0]))


def test_make_choices_only_one():
state = workflow.State().default_settings()
Expand Down
2 changes: 1 addition & 1 deletion activitysim/estimation/test/test_larch_estimation.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -131,7 +131,7 @@ def test_location_model(
[
("non_mandatory_tour_scheduling", "SLSQP"),
("joint_tour_scheduling", "SLSQP"),
("atwork_subtour_scheduling", "SLSQP"),
# ("atwork_subtour_scheduling", "SLSQP"), # TODO this test is unstable, needs to be updated with better data
("mandatory_tour_scheduling_work", "SLSQP"),
("mandatory_tour_scheduling_school", "SLSQP"),
],
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@ tour_id,person_id,tour_type,tour_type_count,tour_type_num,tour_num,tour_count,to
2373898,57899,work,1,1,1,1,mandatory,1,3402.0,3746.0,20552,47.0,7.0,17.0,10.0,,,WALK,1.0388895039783694,no_subtours,,0out_0in,work
2373980,57901,work,2,1,1,2,mandatory,1,3115.0,3746.0,20552,25.0,6.0,12.0,6.0,,,SHARED3FREE,0.6022315390131013,no_subtours,,0out_0in,work
2373981,57901,work,2,2,2,2,mandatory,1,3115.0,3746.0,20552,150.0,15.0,20.0,5.0,,,SHARED2FREE,0.6232767878249469,no_subtours,,1out_0in,work
2563802,62531,school,1,1,1,1,mandatory,1,3460.0,3316.0,21869,180.0,20.0,20.0,0.0,,,SHARED3FREE,-0.7094603590463964,,,0out_0in,school
2563802,62531,school,1,1,1,1,mandatory,1,3460.0,3316.0,21869,181.0,20.0,21.0,1.0,,,SHARED3FREE,-0.7094603590463964,,,0out_0in,school
2563821,62532,escort,1,1,1,1,non_mandatory,1,3398.0,3316.0,21869,20.0,6.0,7.0,1.0,,12.499268454965652,SHARED2FREE,-1.4604154628072699,,,0out_0in,escort
2563862,62533,escort,3,1,1,4,non_mandatory,1,3402.0,3316.0,21869,1.0,5.0,6.0,1.0,,12.534424209198946,SHARED3FREE,-1.2940574569954848,,,0out_3in,escort
2563863,62533,escort,3,2,2,4,non_mandatory,1,3519.0,3316.0,21869,99.0,11.0,11.0,0.0,,12.466623656700463,SHARED2FREE,-0.9326373013150777,,,0out_0in,escort
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,7 +55,7 @@ trip_id,person_id,household_id,primary_purpose,trip_num,outbound,trip_count,dest
18991850,57901,20552,work,2,True,2,3115,3460,2373981,work,,16,DRIVEALONEFREE,0.10597046751418379
18991853,57901,20552,work,1,False,1,3746,3115,2373981,home,,20,SHARED2FREE,0.23660752783217825
20510417,62531,21869,school,1,True,1,3460,3316,2563802,school,,20,SHARED3FREE,-1.4448137456466916
20510421,62531,21869,school,1,False,1,3316,3460,2563802,home,,20,WALK,-1.5207459403958272
20510421,62531,21869,school,1,False,1,3316,3460,2563802,home,,21,WALK,-1.5207459403958272
20510569,62532,21869,escort,1,True,1,3398,3316,2563821,escort,,6,SHARED2FREE,0.17869598454022895
20510573,62532,21869,escort,1,False,1,3316,3398,2563821,home,,7,DRIVEALONEFREE,0.20045149458253975
20510897,62533,21869,escort,1,True,1,3402,3316,2563862,escort,,5,SHARED3FREE,0.7112775892674524
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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/core/interaction_sample.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -404,6 +404,7 @@ def _interaction_sample(
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
)
chunk_sizer.log_df(trace_label, "probs", probs)

Expand Down
29 changes: 18 additions & 11 deletions activitysim/core/interaction_sample_simulate.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -248,20 +248,27 @@ def _interaction_sample_simulate(

# 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,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
)
chunk_sizer.log_df(trace_label, "probs", probs)

if want_logsums:
logsums = logit.utils_to_logsums(
utilities_df, allow_zero_probs=allow_zero_probs
probs, logsums = logit.utils_to_probs(
state,
utilities_df,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
return_logsums=True,
)
chunk_sizer.log_df(trace_label, "logsums", logsums)
else:
probs = logit.utils_to_probs(
state,
utilities_df,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
)
chunk_sizer.log_df(trace_label, "probs", probs)

del utilities_df
chunk_sizer.log_df(trace_label, "utilities_df", None)
Expand Down
50 changes: 48 additions & 2 deletions activitysim/core/logit.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
from __future__ import annotations

import logging
import warnings
from builtins import object

import numpy as np
Expand DownExpand Up@@ -130,6 +131,8 @@ def utils_to_probs(
exponentiated=False,
allow_zero_probs=False,
trace_choosers=None,
overflow_protection: bool = True,
return_logsums: bool = False,
):
"""
Convert a table of utilities to probabilities.
Expand All@@ -155,6 +158,20 @@ 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
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
that will create infinite or NaN values. This will also provide effective
protection against underflow; extremely rare probabilities will round to
zero, but by definition they are extremely rare and losing them entirely
should not impact the simulation in a measureable fashion, and at least one
(and sometimes only one) alternative is guaranteed to have non-zero
probability, as long as at least one alternative has a finite utility value.
If utility values are certain to be well-behaved and non-extreme, enabling
overflow_protection will have no benefit but impose a modest computational
overhead cost.

Returns
-------
probs : pandas.DataFrame
Expand All@@ -167,9 +184,27 @@ def utils_to_probs(
# utils_arr = utils.values.astype('float')
utils_arr = utils.values

if utils_arr.dtype == np.float32 and utils_arr.max() > 85:
if allow_zero_probs:
if overflow_protection:
warnings.warn(
"cannot set overflow_protection with allow_zero_probs", stacklevel=2
)
overflow_protection = utils_arr.dtype == np.float32 and utils_arr.max() > 85
if overflow_protection:
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
)

if overflow_protection:
# exponentiated utils will overflow, downshift them
utils_arr -= utils_arr.max(1, keepdims=True)
shifts = utils_arr.max(1, keepdims=True)
utils_arr -= shifts
else:
shifts = None

if not exponentiated:
# TODO: reduce memory usage by exponentiating in-place.
Expand All@@ -185,6 +220,15 @@ def utils_to_probs(

arr_sum = utils_arr.sum(axis=1)

if return_logsums:
with np.errstate(divide="ignore" if allow_zero_probs else "warn"):
logsums = np.log(arr_sum)
if shifts is not None:
logsums += np.squeeze(shifts, 1)
logsums = pd.Series(logsums, index=utils.index)
else:
logsums = None

if not allow_zero_probs:
zero_probs = arr_sum == 0.0
if zero_probs.any():
Expand DownExpand Up@@ -222,6 +266,8 @@ def utils_to_probs(

probs = pd.DataFrame(utils_arr, columns=utils.columns, index=utils.index)

if return_logsums:
return probs, logsums
return probs


Expand Down
1 change: 1 addition & 0 deletions activitysim/core/pathbuilder.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -994,6 +994,7 @@ def build_virtual_path(
utilities_df,
allow_zero_probs=True,
trace_label=trace_label,
overflow_protection=False,
)
chunk_sizer.log_df(trace_label, "probs", probs)

Expand Down
1 change: 1 addition & 0 deletions activitysim/core/simulate.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -1032,6 +1032,7 @@ def compute_nested_probabilities(
trace_label=trace_label,
exponentiated=True,
allow_zero_probs=True,
overflow_protection=False,
)

nested_probabilities = pd.concat([nested_probabilities, probs], axis=1)
Expand Down
28 changes: 26 additions & 2 deletions activitysim/core/test/test_logit.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,16 +81,40 @@ def test_utils_to_probs_raises():
idx = pd.Index(name="household_id", data=[1])
with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state, pd.DataFrame([[1, 2, np.inf, 3]], index=idx), trace_label=None
state,
pd.DataFrame([[1, 2, np.inf, 3]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "infinite exponentiated utilities" in str(excinfo.value)

with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state, pd.DataFrame([[-999, -999, -999, -999]], index=idx), trace_label=None
state,
pd.DataFrame([[1, 2, 9999, 3]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "infinite exponentiated utilities" in str(excinfo.value)

with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state,
pd.DataFrame([[-999, -999, -999, -999]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "all probabilities are zero" in str(excinfo.value)

# test that overflow protection works
z = logit.utils_to_probs(
state,
pd.DataFrame([[1, 2, 9999, 3]], index=idx),
trace_label=None,
overflow_protection=True,
)
assert np.asarray(z).ravel() == pytest.approx(np.asarray([0.0, 0.0, 1.0, 0.0]))


def test_make_choices_only_one():
state = workflow.State().default_settings()
Expand Down
2 changes: 1 addition & 1 deletion activitysim/estimation/test/test_larch_estimation.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -131,7 +131,7 @@ def test_location_model(
[
("non_mandatory_tour_scheduling", "SLSQP"),
("joint_tour_scheduling", "SLSQP"),
("atwork_subtour_scheduling", "SLSQP"),
# ("atwork_subtour_scheduling", "SLSQP"), # TODO this test is unstable, needs to be updated with better data
("mandatory_tour_scheduling_work", "SLSQP"),
("mandatory_tour_scheduling_school", "SLSQP"),
],
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@ tour_id,person_id,tour_type,tour_type_count,tour_type_num,tour_num,tour_count,to
2373898,57899,work,1,1,1,1,mandatory,1,3402.0,3746.0,20552,47.0,7.0,17.0,10.0,,,WALK,1.0388895039783694,no_subtours,,0out_0in,work
2373980,57901,work,2,1,1,2,mandatory,1,3115.0,3746.0,20552,25.0,6.0,12.0,6.0,,,SHARED3FREE,0.6022315390131013,no_subtours,,0out_0in,work
2373981,57901,work,2,2,2,2,mandatory,1,3115.0,3746.0,20552,150.0,15.0,20.0,5.0,,,SHARED2FREE,0.6232767878249469,no_subtours,,1out_0in,work
2563802,62531,school,1,1,1,1,mandatory,1,3460.0,3316.0,21869,180.0,20.0,20.0,0.0,,,SHARED3FREE,-0.7094603590463964,,,0out_0in,school
2563802,62531,school,1,1,1,1,mandatory,1,3460.0,3316.0,21869,181.0,20.0,21.0,1.0,,,SHARED3FREE,-0.7094603590463964,,,0out_0in,school
2563821,62532,escort,1,1,1,1,non_mandatory,1,3398.0,3316.0,21869,20.0,6.0,7.0,1.0,,12.499268454965652,SHARED2FREE,-1.4604154628072699,,,0out_0in,escort
2563862,62533,escort,3,1,1,4,non_mandatory,1,3402.0,3316.0,21869,1.0,5.0,6.0,1.0,,12.534424209198946,SHARED3FREE,-1.2940574569954848,,,0out_3in,escort
2563863,62533,escort,3,2,2,4,non_mandatory,1,3519.0,3316.0,21869,99.0,11.0,11.0,0.0,,12.466623656700463,SHARED2FREE,-0.9326373013150777,,,0out_0in,escort
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,7 +55,7 @@ trip_id,person_id,household_id,primary_purpose,trip_num,outbound,trip_count,dest
18991850,57901,20552,work,2,True,2,3115,3460,2373981,work,,16,DRIVEALONEFREE,0.10597046751418379
18991853,57901,20552,work,1,False,1,3746,3115,2373981,home,,20,SHARED2FREE,0.23660752783217825
20510417,62531,21869,school,1,True,1,3460,3316,2563802,school,,20,SHARED3FREE,-1.4448137456466916
20510421,62531,21869,school,1,False,1,3316,3460,2563802,home,,20,WALK,-1.5207459403958272
20510421,62531,21869,school,1,False,1,3316,3460,2563802,home,,21,WALK,-1.5207459403958272
20510569,62532,21869,escort,1,True,1,3398,3316,2563821,escort,,6,SHARED2FREE,0.17869598454022895
20510573,62532,21869,escort,1,False,1,3316,3398,2563821,home,,7,DRIVEALONEFREE,0.20045149458253975
20510897,62533,21869,escort,1,True,1,3402,3316,2563862,escort,,5,SHARED3FREE,0.7112775892674524
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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/core/interaction_sample.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -404,6 +404,7 @@ def _interaction_sample(
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
)
chunk_sizer.log_df(trace_label, "probs", probs)

Expand Down
29 changes: 18 additions & 11 deletions activitysim/core/interaction_sample_simulate.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -248,20 +248,27 @@ def _interaction_sample_simulate(

# 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,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
)
chunk_sizer.log_df(trace_label, "probs", probs)

if want_logsums:
logsums = logit.utils_to_logsums(
utilities_df, allow_zero_probs=allow_zero_probs
probs, logsums = logit.utils_to_probs(
state,
utilities_df,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
return_logsums=True,
)
chunk_sizer.log_df(trace_label, "logsums", logsums)
else:
probs = logit.utils_to_probs(
state,
utilities_df,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
)
chunk_sizer.log_df(trace_label, "probs", probs)

del utilities_df
chunk_sizer.log_df(trace_label, "utilities_df", None)
Expand Down
50 changes: 48 additions & 2 deletions activitysim/core/logit.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
from __future__ import annotations

import logging
import warnings
from builtins import object

import numpy as np
Expand DownExpand Up@@ -130,6 +131,8 @@ def utils_to_probs(
exponentiated=False,
allow_zero_probs=False,
trace_choosers=None,
overflow_protection: bool = True,
return_logsums: bool = False,
):
"""
Convert a table of utilities to probabilities.
Expand All@@ -155,6 +158,20 @@ 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
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
that will create infinite or NaN values. This will also provide effective
protection against underflow; extremely rare probabilities will round to
zero, but by definition they are extremely rare and losing them entirely
should not impact the simulation in a measureable fashion, and at least one
(and sometimes only one) alternative is guaranteed to have non-zero
probability, as long as at least one alternative has a finite utility value.
If utility values are certain to be well-behaved and non-extreme, enabling
overflow_protection will have no benefit but impose a modest computational
overhead cost.

Returns
-------
probs : pandas.DataFrame
Expand All@@ -167,9 +184,27 @@ def utils_to_probs(
# utils_arr = utils.values.astype('float')
utils_arr = utils.values

if utils_arr.dtype == np.float32 and utils_arr.max() > 85:
if allow_zero_probs:
if overflow_protection:
warnings.warn(
"cannot set overflow_protection with allow_zero_probs", stacklevel=2
)
overflow_protection = utils_arr.dtype == np.float32 and utils_arr.max() > 85
if overflow_protection:
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
)

if overflow_protection:
# exponentiated utils will overflow, downshift them
utils_arr -= utils_arr.max(1, keepdims=True)
shifts = utils_arr.max(1, keepdims=True)
utils_arr -= shifts
else:
shifts = None

if not exponentiated:
# TODO: reduce memory usage by exponentiating in-place.
Expand All@@ -185,6 +220,15 @@ def utils_to_probs(

arr_sum = utils_arr.sum(axis=1)

if return_logsums:
with np.errstate(divide="ignore" if allow_zero_probs else "warn"):
logsums = np.log(arr_sum)
if shifts is not None:
logsums += np.squeeze(shifts, 1)
logsums = pd.Series(logsums, index=utils.index)
else:
logsums = None

if not allow_zero_probs:
zero_probs = arr_sum == 0.0
if zero_probs.any():
Expand DownExpand Up@@ -222,6 +266,8 @@ def utils_to_probs(

probs = pd.DataFrame(utils_arr, columns=utils.columns, index=utils.index)

if return_logsums:
return probs, logsums
return probs


Expand Down
1 change: 1 addition & 0 deletions activitysim/core/pathbuilder.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -994,6 +994,7 @@ def build_virtual_path(
utilities_df,
allow_zero_probs=True,
trace_label=trace_label,
overflow_protection=False,
)
chunk_sizer.log_df(trace_label, "probs", probs)

Expand Down
1 change: 1 addition & 0 deletions activitysim/core/simulate.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -1032,6 +1032,7 @@ def compute_nested_probabilities(
trace_label=trace_label,
exponentiated=True,
allow_zero_probs=True,
overflow_protection=False,
)

nested_probabilities = pd.concat([nested_probabilities, probs], axis=1)
Expand Down
28 changes: 26 additions & 2 deletions activitysim/core/test/test_logit.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,16 +81,40 @@ def test_utils_to_probs_raises():
idx = pd.Index(name="household_id", data=[1])
with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state, pd.DataFrame([[1, 2, np.inf, 3]], index=idx), trace_label=None
state,
pd.DataFrame([[1, 2, np.inf, 3]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "infinite exponentiated utilities" in str(excinfo.value)

with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state, pd.DataFrame([[-999, -999, -999, -999]], index=idx), trace_label=None
state,
pd.DataFrame([[1, 2, 9999, 3]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "infinite exponentiated utilities" in str(excinfo.value)

with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state,
pd.DataFrame([[-999, -999, -999, -999]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "all probabilities are zero" in str(excinfo.value)

# test that overflow protection works
z = logit.utils_to_probs(
state,
pd.DataFrame([[1, 2, 9999, 3]], index=idx),
trace_label=None,
overflow_protection=True,
)
assert np.asarray(z).ravel() == pytest.approx(np.asarray([0.0, 0.0, 1.0, 0.0]))


def test_make_choices_only_one():
state = workflow.State().default_settings()
Expand Down
2 changes: 1 addition & 1 deletion activitysim/estimation/test/test_larch_estimation.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -131,7 +131,7 @@ def test_location_model(
[
("non_mandatory_tour_scheduling", "SLSQP"),
("joint_tour_scheduling", "SLSQP"),
("atwork_subtour_scheduling", "SLSQP"),
# ("atwork_subtour_scheduling", "SLSQP"), # TODO this test is unstable, needs to be updated with better data
("mandatory_tour_scheduling_work", "SLSQP"),
("mandatory_tour_scheduling_school", "SLSQP"),
],
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@ tour_id,person_id,tour_type,tour_type_count,tour_type_num,tour_num,tour_count,to
2373898,57899,work,1,1,1,1,mandatory,1,3402.0,3746.0,20552,47.0,7.0,17.0,10.0,,,WALK,1.0388895039783694,no_subtours,,0out_0in,work
2373980,57901,work,2,1,1,2,mandatory,1,3115.0,3746.0,20552,25.0,6.0,12.0,6.0,,,SHARED3FREE,0.6022315390131013,no_subtours,,0out_0in,work
2373981,57901,work,2,2,2,2,mandatory,1,3115.0,3746.0,20552,150.0,15.0,20.0,5.0,,,SHARED2FREE,0.6232767878249469,no_subtours,,1out_0in,work
2563802,62531,school,1,1,1,1,mandatory,1,3460.0,3316.0,21869,180.0,20.0,20.0,0.0,,,SHARED3FREE,-0.7094603590463964,,,0out_0in,school
2563802,62531,school,1,1,1,1,mandatory,1,3460.0,3316.0,21869,181.0,20.0,21.0,1.0,,,SHARED3FREE,-0.7094603590463964,,,0out_0in,school
2563821,62532,escort,1,1,1,1,non_mandatory,1,3398.0,3316.0,21869,20.0,6.0,7.0,1.0,,12.499268454965652,SHARED2FREE,-1.4604154628072699,,,0out_0in,escort
2563862,62533,escort,3,1,1,4,non_mandatory,1,3402.0,3316.0,21869,1.0,5.0,6.0,1.0,,12.534424209198946,SHARED3FREE,-1.2940574569954848,,,0out_3in,escort
2563863,62533,escort,3,2,2,4,non_mandatory,1,3519.0,3316.0,21869,99.0,11.0,11.0,0.0,,12.466623656700463,SHARED2FREE,-0.9326373013150777,,,0out_0in,escort
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,7 +55,7 @@ trip_id,person_id,household_id,primary_purpose,trip_num,outbound,trip_count,dest
18991850,57901,20552,work,2,True,2,3115,3460,2373981,work,,16,DRIVEALONEFREE,0.10597046751418379
18991853,57901,20552,work,1,False,1,3746,3115,2373981,home,,20,SHARED2FREE,0.23660752783217825
20510417,62531,21869,school,1,True,1,3460,3316,2563802,school,,20,SHARED3FREE,-1.4448137456466916
20510421,62531,21869,school,1,False,1,3316,3460,2563802,home,,20,WALK,-1.5207459403958272
20510421,62531,21869,school,1,False,1,3316,3460,2563802,home,,21,WALK,-1.5207459403958272
20510569,62532,21869,escort,1,True,1,3398,3316,2563821,escort,,6,SHARED2FREE,0.17869598454022895
20510573,62532,21869,escort,1,False,1,3316,3398,2563821,home,,7,DRIVEALONEFREE,0.20045149458253975
20510897,62533,21869,escort,1,True,1,3402,3316,2563862,escort,,5,SHARED3FREE,0.7112775892674524
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
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/core/interaction_sample.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -404,6 +404,7 @@ def _interaction_sample(
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
)
chunk_sizer.log_df(trace_label, "probs", probs)

Expand Down
29 changes: 18 additions & 11 deletions activitysim/core/interaction_sample_simulate.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -248,20 +248,27 @@ def _interaction_sample_simulate(

# 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,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
)
chunk_sizer.log_df(trace_label, "probs", probs)

if want_logsums:
logsums = logit.utils_to_logsums(
utilities_df, allow_zero_probs=allow_zero_probs
probs, logsums = logit.utils_to_probs(
state,
utilities_df,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
return_logsums=True,
)
chunk_sizer.log_df(trace_label, "logsums", logsums)
else:
probs = logit.utils_to_probs(
state,
utilities_df,
allow_zero_probs=allow_zero_probs,
trace_label=trace_label,
trace_choosers=choosers,
overflow_protection=not allow_zero_probs,
)
chunk_sizer.log_df(trace_label, "probs", probs)

del utilities_df
chunk_sizer.log_df(trace_label, "utilities_df", None)
Expand Down
50 changes: 48 additions & 2 deletions activitysim/core/logit.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
from __future__ import annotations

import logging
import warnings
from builtins import object

import numpy as np
Expand DownExpand Up@@ -130,6 +131,8 @@ def utils_to_probs(
exponentiated=False,
allow_zero_probs=False,
trace_choosers=None,
overflow_protection: bool = True,
return_logsums: bool = False,
):
"""
Convert a table of utilities to probabilities.
Expand All@@ -155,6 +158,20 @@ 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
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
that will create infinite or NaN values. This will also provide effective
protection against underflow; extremely rare probabilities will round to
zero, but by definition they are extremely rare and losing them entirely
should not impact the simulation in a measureable fashion, and at least one
(and sometimes only one) alternative is guaranteed to have non-zero
probability, as long as at least one alternative has a finite utility value.
If utility values are certain to be well-behaved and non-extreme, enabling
overflow_protection will have no benefit but impose a modest computational
overhead cost.

Returns
-------
probs : pandas.DataFrame
Expand All@@ -167,9 +184,27 @@ def utils_to_probs(
# utils_arr = utils.values.astype('float')
utils_arr = utils.values

if utils_arr.dtype == np.float32 and utils_arr.max() > 85:
if allow_zero_probs:
if overflow_protection:
warnings.warn(
"cannot set overflow_protection with allow_zero_probs", stacklevel=2
)
overflow_protection = utils_arr.dtype == np.float32 and utils_arr.max() > 85
if overflow_protection:
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
)

if overflow_protection:
# exponentiated utils will overflow, downshift them
utils_arr -= utils_arr.max(1, keepdims=True)
shifts = utils_arr.max(1, keepdims=True)
utils_arr -= shifts
else:
shifts = None

if not exponentiated:
# TODO: reduce memory usage by exponentiating in-place.
Expand All@@ -185,6 +220,15 @@ def utils_to_probs(

arr_sum = utils_arr.sum(axis=1)

if return_logsums:
with np.errstate(divide="ignore" if allow_zero_probs else "warn"):
logsums = np.log(arr_sum)
if shifts is not None:
logsums += np.squeeze(shifts, 1)
logsums = pd.Series(logsums, index=utils.index)
else:
logsums = None

if not allow_zero_probs:
zero_probs = arr_sum == 0.0
if zero_probs.any():
Expand DownExpand Up@@ -222,6 +266,8 @@ def utils_to_probs(

probs = pd.DataFrame(utils_arr, columns=utils.columns, index=utils.index)

if return_logsums:
return probs, logsums
return probs


Expand Down
1 change: 1 addition & 0 deletions activitysim/core/pathbuilder.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -994,6 +994,7 @@ def build_virtual_path(
utilities_df,
allow_zero_probs=True,
trace_label=trace_label,
overflow_protection=False,
)
chunk_sizer.log_df(trace_label, "probs", probs)

Expand Down
1 change: 1 addition & 0 deletions activitysim/core/simulate.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -1032,6 +1032,7 @@ def compute_nested_probabilities(
trace_label=trace_label,
exponentiated=True,
allow_zero_probs=True,
overflow_protection=False,
)

nested_probabilities = pd.concat([nested_probabilities, probs], axis=1)
Expand Down
28 changes: 26 additions & 2 deletions activitysim/core/test/test_logit.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,16 +81,40 @@ def test_utils_to_probs_raises():
idx = pd.Index(name="household_id", data=[1])
with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state, pd.DataFrame([[1, 2, np.inf, 3]], index=idx), trace_label=None
state,
pd.DataFrame([[1, 2, np.inf, 3]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "infinite exponentiated utilities" in str(excinfo.value)

with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state, pd.DataFrame([[-999, -999, -999, -999]], index=idx), trace_label=None
state,
pd.DataFrame([[1, 2, 9999, 3]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "infinite exponentiated utilities" in str(excinfo.value)

with pytest.raises(RuntimeError) as excinfo:
logit.utils_to_probs(
state,
pd.DataFrame([[-999, -999, -999, -999]], index=idx),
trace_label=None,
overflow_protection=False,
)
assert "all probabilities are zero" in str(excinfo.value)

# test that overflow protection works
z = logit.utils_to_probs(
state,
pd.DataFrame([[1, 2, 9999, 3]], index=idx),
trace_label=None,
overflow_protection=True,
)
assert np.asarray(z).ravel() == pytest.approx(np.asarray([0.0, 0.0, 1.0, 0.0]))


def test_make_choices_only_one():
state = workflow.State().default_settings()
Expand Down
2 changes: 1 addition & 1 deletion activitysim/estimation/test/test_larch_estimation.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -131,7 +131,7 @@ def test_location_model(
[
("non_mandatory_tour_scheduling", "SLSQP"),
("joint_tour_scheduling", "SLSQP"),
("atwork_subtour_scheduling", "SLSQP"),
# ("atwork_subtour_scheduling", "SLSQP"), # TODO this test is unstable, needs to be updated with better data
("mandatory_tour_scheduling_work", "SLSQP"),
("mandatory_tour_scheduling_school", "SLSQP"),
],
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -20,7 +20,7 @@ tour_id,person_id,tour_type,tour_type_count,tour_type_num,tour_num,tour_count,to
2373898,57899,work,1,1,1,1,mandatory,1,3402.0,3746.0,20552,47.0,7.0,17.0,10.0,,,WALK,1.0388895039783694,no_subtours,,0out_0in,work
2373980,57901,work,2,1,1,2,mandatory,1,3115.0,3746.0,20552,25.0,6.0,12.0,6.0,,,SHARED3FREE,0.6022315390131013,no_subtours,,0out_0in,work
2373981,57901,work,2,2,2,2,mandatory,1,3115.0,3746.0,20552,150.0,15.0,20.0,5.0,,,SHARED2FREE,0.6232767878249469,no_subtours,,1out_0in,work
2563802,62531,school,1,1,1,1,mandatory,1,3460.0,3316.0,21869,180.0,20.0,20.0,0.0,,,SHARED3FREE,-0.7094603590463964,,,0out_0in,school
2563802,62531,school,1,1,1,1,mandatory,1,3460.0,3316.0,21869,181.0,20.0,21.0,1.0,,,SHARED3FREE,-0.7094603590463964,,,0out_0in,school
2563821,62532,escort,1,1,1,1,non_mandatory,1,3398.0,3316.0,21869,20.0,6.0,7.0,1.0,,12.499268454965652,SHARED2FREE,-1.4604154628072699,,,0out_0in,escort
2563862,62533,escort,3,1,1,4,non_mandatory,1,3402.0,3316.0,21869,1.0,5.0,6.0,1.0,,12.534424209198946,SHARED3FREE,-1.2940574569954848,,,0out_3in,escort
2563863,62533,escort,3,2,2,4,non_mandatory,1,3519.0,3316.0,21869,99.0,11.0,11.0,0.0,,12.466623656700463,SHARED2FREE,-0.9326373013150777,,,0out_0in,escort
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,7 +55,7 @@ trip_id,person_id,household_id,primary_purpose,trip_num,outbound,trip_count,dest
18991850,57901,20552,work,2,True,2,3115,3460,2373981,work,,16,DRIVEALONEFREE,0.10597046751418379
18991853,57901,20552,work,1,False,1,3746,3115,2373981,home,,20,SHARED2FREE,0.23660752783217825
20510417,62531,21869,school,1,True,1,3460,3316,2563802,school,,20,SHARED3FREE,-1.4448137456466916
20510421,62531,21869,school,1,False,1,3316,3460,2563802,home,,20,WALK,-1.5207459403958272
20510421,62531,21869,school,1,False,1,3316,3460,2563802,home,,21,WALK,-1.5207459403958272
20510569,62532,21869,escort,1,True,1,3398,3316,2563821,escort,,6,SHARED2FREE,0.17869598454022895
20510573,62532,21869,escort,1,False,1,3316,3398,2563821,home,,7,DRIVEALONEFREE,0.20045149458253975
20510897,62533,21869,escort,1,True,1,3402,3316,2563862,escort,,5,SHARED3FREE,0.7112775892674524
Expand Down