Skip to content

Commit

Permalink
Trip Scheduling (#51)
Browse files Browse the repository at this point in the history
* Differentiate examples between quality and validity of example models (ActivitySim#571)

* change examples that are no longer tied to an agency to fictional place names

* change name of full example also

* add back missing output dir

* restore test output dir also

* more empty dirs that got lost

* clean up docs

* example_mtc -> prototype_mtc

* Prototype MTC extended

* add all the ignored files

* add test output dirs

* remove superfluous example_test

* prototype_sf

* prototype_arc

* prototype_marin

* move dirs

* psrc

* semcog

* sandag_xborder

* placeholder_sandag

* placeholder_multiple_zone

* no more coveralls

* repair docs

* clean up example names

* black and isort (ActivitySim#578)

* black and isort

* stop emitting output dir

it fails later tests

* trace files in nested directories

because windows

* swap files for xborder model

* repair ARC MP

* print checksum even when not used

* add hashes for sandag_xborder_full

* fix dtype in university hack

* fix persons to match tours

* repair ARC

* Bump version: 1.0.4 → 1.1.0

* Added mwcog small area

* missed one edit

* reformatting

* Update README.MD

* call as module

* github actions tests

* pre commit hooks

* pyproject toml

* limit numpy

* docs for mamba instead of conda

* ignore generated files

* add nbmake to test env

* fix dupe line

* repair test multiple zones for github actions

* publish develop docs

* fix docbuild env

* Update .travis.yml

* fixed testing files

* fixed testing files (again)

* fixed test script again

* publish docs to branch name

* updated testing scripts (note shorter travis script for now)

* fixed slash (windows vs. linux testing issue)

* added output folders

* updated travis script to run all tests, should pass

* docs cleaning

* docs re-style

* rebuild

* dynamic versioning docs

* version switcher

* blacken

* fix switcher url

* fix conf

* switcher update

* master to main

* deployment actions

* actions

* build wheel

* fix for testpypi

* blacken

* manual switcher

* branch docs service [makedocs]

* syntax [makedocs]

* travis depends

* checkout v3, fix versioning in docs

* only build develop docs once

* failsafe version

* documentation repairs

* python-simplified-semver

* front cards

* end testing w travis

* add mwcog test to gh-actions

* add mwcog to docs

* point to data not copy it

* sort dependencies

* req sh 2.2.4

* account for variance across platforms in trip dest est

* copy bike skims for sandag test

* param on rtol

* fix sandag_2 test files

* fix test file names

* added pre-processor option to trip scheduling

* trip scheduling relative mode initial commit

* moved everything to mwcog example

* adding output analysis notebook

* adding additional segmentation

* testing and documentation

* blacken

* not assuming scheduling mode is set

* fixing merge trip scheduling

* still fixing merge

* reverting regression trips

* fixing bad merge and updating test

Co-authored-by: Jeffrey Newman <jeffnewman@camsys.com>
Co-authored-by: Andrew Rohne <andrew@siliconcreek.net>
  • Loading branch information
3 people authored Dec 21, 2022
1 parent 7386a4d commit 9506f16
Show file tree
Hide file tree
Showing 9 changed files with 414 additions and 62 deletions.
2 changes: 0 additions & 2 deletions activitysim/abm/models/non_mandatory_scheduling.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
from .util.school_escort_tours_trips import create_pure_school_escort_tours

from .util.vectorize_tour_scheduling import vectorize_tour_scheduling
from activitysim.core.util import assign_in_place


logger = logging.getLogger(__name__)
DUMP = False
Expand Down
59 changes: 51 additions & 8 deletions activitysim/abm/models/trip_scheduling.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@

from activitysim.abm.models.util import estimation
from activitysim.abm.models.util.trip import cleanup_failed_trips, failed_trip_cohorts
from activitysim.core import chunk, config, inject, logit, pipeline, tracing
from activitysim.core import (
chunk,
config,
inject,
logit,
pipeline,
tracing,
expressions,
)
from activitysim.core.util import reindex

from .util.school_escort_tours_trips import split_out_school_escorting_trips
Expand Down Expand Up @@ -36,13 +44,15 @@

DEPARTURE_MODE = "departure"
DURATION_MODE = "stop_duration"
RELATIVE_MODE = "relative"
PROBS_JOIN_COLUMNS_DEPARTURE_BASED = [
"primary_purpose",
"outbound",
"tour_hour",
"trip_num",
]
PROBS_JOIN_COLUMNS_DURATION_BASED = ["outbound", "stop_num"]
PROBS_JOIN_COLUMNS_RELATIVE_BASED = ["outbound", "periods_left"]


def set_tour_hour(trips, tours):
Expand Down Expand Up @@ -181,6 +191,7 @@ def schedule_trips_in_leg(
failfix = model_settings.get(FAILFIX, FAILFIX_DEFAULT)
depart_alt_base = model_settings.get("DEPART_ALT_BASE", 0)
scheduling_mode = model_settings.get("scheduling_mode", "departure")
preprocessor_settings = model_settings.get("preprocessor", None)

if scheduling_mode == "departure":
probs_join_cols = model_settings.get(
Expand All @@ -190,10 +201,14 @@ def schedule_trips_in_leg(
probs_join_cols = model_settings.get(
"probs_join_cols", PROBS_JOIN_COLUMNS_DURATION_BASED
)
elif scheduling_mode == "relative":
probs_join_cols = model_settings.get(
"probs_join_cols", PROBS_JOIN_COLUMNS_RELATIVE_BASED
)
else:
logger.error(
"Invalid scheduling mode specified: {0}.".format(scheduling_mode),
"Please select one of ['departure', 'stop_duration'] and try again.",
"Please select one of ['departure', 'stop_duration', 'relative'] and try again.",
)

# logger.debug("%s scheduling %s trips" % (trace_label, trips.shape[0]))
Expand Down Expand Up @@ -232,18 +247,35 @@ def schedule_trips_in_leg(
ADJUST_NEXT_DEPART_COL = "latest"
trips.next_trip_id = trips.next_trip_id.where(~is_final, NO_TRIP_ID)

network_los = inject.get_injectable("network_los")
locals_dict = {"network_los": network_los}
locals_dict.update(config.get_model_constants(model_settings))

first_trip_in_leg = True
for i in range(trips.trip_num.min(), trips.trip_num.max() + 1):

if outbound or scheduling_mode == DURATION_MODE:
nth_trace_label = tracing.extend_trace_label(trace_label, "num_%s" % i)

# - annotate trips
if preprocessor_settings:
expressions.assign_columns(
df=trips,
model_settings=preprocessor_settings,
locals_dict=locals_dict,
trace_label=nth_trace_label,
)

if (
outbound
or (scheduling_mode == DURATION_MODE)
or (scheduling_mode == RELATIVE_MODE)
):
# iterate in ascending trip_num order
nth_trips = trips[trips.trip_num == i]
else:
# iterate over inbound trips in descending trip_num order, skipping the final trip
nth_trips = trips[trips.trip_num == trips.trip_count - i]

nth_trace_label = tracing.extend_trace_label(trace_label, "num_%s" % i)

choices = ps.make_scheduling_choices(
nth_trips,
scheduling_mode,
Expand All @@ -265,6 +297,12 @@ def schedule_trips_in_leg(
)
choices = choices.fillna(trips[ADJUST_NEXT_DEPART_COL])

if scheduling_mode == RELATIVE_MODE:
# choices are relative to the previous departure time
choices = nth_trips.earliest + choices
# need to update the departure time based on the choice
update_tour_earliest(trips, choices)

# adjust allowed depart range of next trip
has_next_trip = nth_trips.next_trip_id != NO_TRIP_ID
if has_next_trip.any():
Expand Down Expand Up @@ -437,9 +475,9 @@ def trip_scheduling(trips, tours, chunk_size, trace_hh_id):
]
estimator.write_choosers(trips_df[chooser_cols_for_estimation])

probs_spec = pd.read_csv(
config.config_file_path("trip_scheduling_probs.csv"), comment="#"
)
probs_spec_file = model_settings.get("PROBS_SPEC", "trip_scheduling_probs.csv")
logger.debug(f"probs_spec_file: {config.config_file_path(probs_spec_file)}")
probs_spec = pd.read_csv(config.config_file_path(probs_spec_file), comment="#")
# FIXME for now, not really doing estimation for probabilistic model - just overwriting choices
# besides, it isn't clear that named coefficients would be helpful if we had some form of estimation
# coefficients_df = simulate.read_model_coefficients(model_settings)
Expand Down Expand Up @@ -499,6 +537,11 @@ def trip_scheduling(trips, tours, chunk_size, trace_hh_id):
failed = choices.reindex(trips_chunk.index).isnull()
logger.info("%s %s failed", trace_label_i, failed.sum())

if (failed.sum() > 0) & (
model_settings.get("scheduling_mode") == "relative"
):
raise RuntimeError("failed trips with relative scheduling mode")

if not is_last_iteration:
# boolean series of trips whose leg scheduling failed
failed_cohorts = failed_trip_cohorts(trips_chunk, failed)
Expand Down
26 changes: 24 additions & 2 deletions activitysim/abm/models/util/probabilistic_scheduling.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,22 @@ def _preprocess_scheduling_probs(
)
elif scheduling_mode == "stop_duration":
chooser_probs = _preprocess_stop_duration_probs(choosers)
elif scheduling_mode == "relative":
# creating a dataframe with just the trip_id as index and alternatives as columns
probs_cols = [
c
for c in probs_spec.columns
if ((c not in probs_join_cols) & (c.isnumeric()))
]
chooser_probs = choosers.loc[:, probs_cols]
chooser_probs = chooser_probs.div(chooser_probs.sum(axis=1), axis=0)
assert (
~chooser_probs.isna().values.any()
), f"Missing probabilities for trips \n {chooser_probs[chooser_probs.isna().any(axis=1)].index}"
else:
logger.error(
"Invalid scheduling mode specified: {0}.".format(scheduling_mode),
"Please select one of ['departure', 'stop_duration'] and try again.",
"Please select one of ['departure', 'stop_duration', 'relative'] and try again.",
)

# probs should sum to 1 with residual probs resulting in choice of 'fail'
Expand All @@ -196,6 +208,12 @@ def _postprocess_scheduling_choices(
# convert alt choice index to depart time (setting failed choices to -1)
failed = choices == choice_cols.get_loc("fail")

if scheduling_mode == "relative":
if failed.any():
RuntimeError(
f"Failed trips in realtive mode for {failed.sum()} trips: {choosers[failed]}"
)

# For the stop duration-based probabilities, the alternatives are offsets that
# get applied to trip-specific departure and arrival times, so depart_alt_base
# is a column/series rather than a scalar.
Expand Down Expand Up @@ -334,7 +352,11 @@ def make_scheduling_choices(
if failed.any():
choices = choices[~failed]

if all([check_col in choosers_df.columns for check_col in ["earliest", "latest"]]):
if all(
[check_col in choosers_df.columns for check_col in ["earliest", "latest"]]
) & (scheduling_mode != "relative"):
# check to make sure choice does not come before previously scheduled trip or after end of tour
# does not apply if choices are relative to previous trip depart
assert (choices >= choosers_df.earliest[~failed]).all()
assert (choices <= choosers_df.latest[~failed]).all()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@

# int to add to probs column index to get time period it represents.
# e.g. depart_alt_base = 5 means first column (column 0) represents period 5
DEPART_ALT_BASE: 1
DEPART_ALT_BASE: 0

MAX_ITERATIONS: 100

#FAILFIX: drop_and_cleanup
FAILFIX: choose_most_initial

# --- relative trip scheduling settings
PROBS_SPEC: trip_scheduling_probs_purpose_stops.csv

scheduling_mode: relative

probs_join_cols:
- periods_left_min
- periods_left_max
- outbound
- tour_purpose_grouped
- half_tour_stops_remaining_grouped

preprocessor:
SPEC: trip_scheduling_preprocessor
DF: choosers
TABLES:
- tours
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Description,Target,Expression
,periods_left,(df.latest - df.earliest)
# binning the periods differently for inbound and outbound
,periods_left_min,"np.where(df['outbound'], periods_left.clip(upper=25), periods_left.clip(upper=34))"
,periods_left_max,"np.where(((periods_left >= 25) & (df['outbound'])) | ((periods_left >= 34) & (~df['outbound'])), 47, periods_left)"
,tour_purpose,"reindex(tours.tour_type, df.tour_id)"
,tour_purpose_grouped,"np.where(tour_purpose.isin(['work','school','univ']), 'mand', 'non_mand')"
,half_tour_stops_remaining_grouped,(df.trip_count - df.trip_num).clip(upper=1)
Loading

0 comments on commit 9506f16

Please sign in to comment.