Park-and-Ride Lot Choice#

The park-and-ride lot choice model selects a candidate parking zone for a tour, so that mode choice can evaluate driving to that lot and taking transit to the primary destination. Optional iteration with tour mode choice redistributes excess demand at lots with limited capacity. This functionality was added in PR #1001.

A selected lot does not mean that the tour uses park-and-ride: tour mode choice still selects the mode. Use both tour_mode and pnr_zone_id to identify actual park-and-ride demand. Lot choice is distinct from Parking Location Choice, which selects parking for individual trips near their destinations.

Structure#

  • Configuration File: park_and_ride_lot_choice.yaml

  • Core Table: tours, with chooser attributes from tours_merged

  • Alternatives: land_use zones with positive park-and-ride spaces

  • Result Field: pnr_zone_id on tours; -1 indicates no lot choice

  • Chooser Keys: home_zone_id, destination, start, end

The model evaluates a utility specification for each eligible chooser and lot using interaction simulation. Lots are represented at the land-use zone level; spaces at multiple facilities in the same zone must be aggregated. Lot zone IDs must be compatible with the default skim dictionary.

Configuring an Implementation#

The fragments below illustrate the configuration structure; the land-use fields, skim names, utility expressions, coefficients, and mode names must be supplied for the implementing model.

  1. Add a column to land_use representing the number of park-and-ride spaces (e.g. pnr_spaces) in that zone. Only zones >= 0 park-and-ride spaces are eligible alternatives for the model.

  2. Add park_and_ride_lot_choice to the models list in settings.yaml after the relevant tour destinations and schedules are available and before tour_mode_choice_simulate. Keep this step in the model list when enabling capacity iteration; multiprocessing also uses its presence to allocate shared capacity buffers.

  3. Create park_and_ride_lot_choice.yaml and its specification and coefficient files. For example:

    SPEC: park_and_ride_lot_choice.csv
    COEFFICIENTS: park_and_ride_lot_choice_coefficients.csv
    LANDUSE_PNR_SPACES_COLUMN: pnr_spaces
    LANDUSE_COL_FOR_PNR_ELIGIBLE_DEST: pnr_eligible_destination
    ITERATE_WITH_TOUR_MODE_CHOICE: false
    

    Here, pnr_eligible_destination is a boolean column in land_use. The model first filters destinations for transit eligibility, then runs the chooser preprocessor, applies the optional CHOOSER_FILTER_EXPR as a pandas query, and runs alts_preprocessor on lot alternatives.

  4. Update the tour mode choice specification and preprocessors to use the selected lot and its access/transit skims. Make park-and-ride modes unavailable when pnr_zone_id == -1, and guard skim expressions against that sentinel. The lot-choice step does not impose mode availability.

  5. Configure trip mode choice and matrix output consistently with the tour mode and selected lot. See Write Trip Matrices for assigning the drive and transit portions to different origin/destination pairs.

Instead of a land-use eligibility column, set TRANSIT_SKIMS_FOR_ELIGIBILITY to a list of skim names. A destination is eligible if at least one lot has a positive value to it in any listed skim. Time-dependent skim names use the form TRANSIT_TIME__AM. If both eligibility settings are supplied, the land-use column takes precedence; if neither is supplied, all destinations are eligible. This is a destination screen, so the utility specification must still handle unavailable paths for individual lot/destination pairs. Filtered-out tours receive pnr_zone_id = -1.

Skims and Utility Expressions#

The following wrappers are available in lot choice and, when pnr_zone_id is present, primary tour mode choice. Here, origin is home_zone_id, lot is pnr_zone_id, and destination is the tour’s primary destination.

Wrapper

From / to

Time period

olt_skims

Origin to lot

Outbound (start)

ldt_skims

Lot to destination

Outbound (start)

dlt_skims

Destination to lot

Inbound (end)

lot_skims

Lot to origin

Inbound (end)

ol_skims

Origin to lot

Time independent

ld_skims

Lot to destination

Time independent

For example, a lot-choice CSV specification can include @olt_skims['DRIVE_TIME'] and @ldt_skims['TRANSIT_TIME'], as well as the return-leg equivalents, with the implementing model’s skim names and coefficients. The alternative attributes include pnr_zone_id and pnr_lot_full. When iteration is disabled, pnr_lot_full is zero.

Capacity Iteration#

To enable capacity feedback, add the following to park_and_ride_lot_choice.yaml, replacing PNR_TRANSIT with the applicable mode names from the tour mode choice specification:

ITERATE_WITH_TOUR_MODE_CHOICE: true
MAX_ITERATIONS: 5
PARK_AND_RIDE_MODES: [PNR_TRANSIT]
ACCEPTED_TOLERANCE: 0.95
RESAMPLE_STRATEGY: latest
TRACE_PNR_CAPACITIES_PER_ITERATION: true

The first mode-choice pass includes all primary tours. Capacity accounting then counts only tours choosing a mode in PARK_AND_RIDE_MODES. Excess tours are selected for a new lot choice and mode choice; other tours keep their results. The final lot and mode choices are written back to tours.

Capacity is scaled to the simulated population as ceil(spaces * sample_rate), using the modal value of households.sample_rate. Occupancy counts tours, not persons or time-dependent parking stays. The implementation does not release spaces at tour end times. latest uses the tour start to select excess tours in descending start-time order, with tour ID breaking ties. random selects occupants with probability (occupancy - capacity) / occupancy, so the number resampled equals the excess in expectation.

ACCEPTED_TOLERANCE (default 0.95) flags a lot as full when occupancy reaches ceil(scaled_capacity * ACCEPTED_TOLERANCE). Resampling addresses demand above the full scaled capacity, not above the tolerance threshold. Flagged lots remain in the alternatives with pnr_lot_full = 1: the lot-choice specification must use this flag to discourage or exclude them. They are not automatically removed. If all alternatives are flagged full, lot choice returns -1 and the mode-choice specification must allow a non-park-and-ride alternative.

MAX_ITERATIONS (default 5) includes the initial mode-choice pass; use a value greater than one to allow resampling. Iteration can stop earlier when no choosers need resimulation. Capacity iteration is disabled during estimation. Reaching the iteration limit does not guarantee that every lot meets capacity.

When TRACE_PNR_CAPACITIES_PER_ITERATION is true (the default), capacity checks write pnr_capacity_snapshot_i<N> traces containing scaled capacity, occupancy, percent utilized, excess demand, and the capacitated flag. Checks occur before resampling, not after the final allowed mode-choice pass. Inspect final tour choices as well as these traces when assessing remaining excess demand.

Logsums and At-Work Subtours#

Set include_pnr_for_logsums: true in the tour mode settings used for logsum calculations to select a lot for each logsum chooser OD pair and expose the lot skims in the logsum utility calculations. This also supports disaggregate accessibility calculations. These lot choices are uncapacitated and can add runtime. Lot-choice preprocessors used here must tolerate tables such as tours not existing yet; guard expressions that depend on those tables.

Set run_atwork_pnr_lot_choice: true in the settings used by At-work Subtour Mode to select lots for at-work subtours. This uses workplace_zone_id as the origin and does not apply the primary-tour capacity iteration. Both settings default to false.

Configuration#

settings activitysim.abm.models.park_and_ride_lot_choice.ParkAndRideLotChoiceSettings#

Bases: LogitComponentSettings

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Config:
  • extra: str = forbid

Fields:
Validators:
  • update_sharrow_skip » all fields

field ACCEPTED_TOLERANCE: float = 0.95#

Lot is considered full if the lot is at least this percentage full.

Validated by:
  • update_sharrow_skip

field CHOOSER_FILTER_EXPR: str | None = None#

An optional expression to filter the chooser table before simulating the model. Applied after preprocessing as a .query() expression.

Validated by:
  • update_sharrow_skip

field COEFFICIENTS: Path | None = None#

Coefficients filename.

This is a CSV file giving named parameters for use in the utility expression. If it is not provided, then it is assumed that all model coefficients are given explicitly in the SPEC as numerical values instead of named parameters. This is perfectly acceptable for use with ActivitySim for typical simulation applications, but may be problematic if used with “estimation mode”.

Validated by:
  • update_sharrow_skip

field CONSTANTS: dict[str, Any] = {}#

Named constants usable in the utility expressions.

Validated by:
  • update_sharrow_skip

field ITERATE_WITH_TOUR_MODE_CHOICE: bool = False#

If True, iterate with tour mode choice to find un-capacitated pnr lots.

Validated by:
  • update_sharrow_skip

field LANDUSE_COL_FOR_PNR_ELIGIBLE_DEST: str | None = None#

The column name in the land use table that indicates whether a destination is eligible for park-and-ride. If supplied, then TRANSIT_SKIMS_FOR_ELIGIBILITY is not used.

Validated by:
  • update_sharrow_skip

field LANDUSE_PNR_SPACES_COLUMN: str [Required]#

lists the column name in the land use table that contains the number of park-and-ride spaces available in the zone

Validated by:
  • update_sharrow_skip

field LOGIT_TYPE: Literal['MNL', 'NL'] = 'MNL'#

Logit model mathematical form.

  • “MNL”

    Multinomial logit model.

  • “NL”

    Nested multinomial logit model.

Validated by:
  • update_sharrow_skip

field MAX_ITERATIONS: int = 5#

Maximum number of iterations to iterate park-and-ride choice with tour mode choice.

Validated by:
  • update_sharrow_skip

field NESTS: LogitNestSpec | None = None#

Nesting structure for a nested logit model.

The nesting structure is specified heirarchically from the top, so the value of this field should be the “root” level nest of the nested logit tree, which should contain references to lower level nests and/or the actual alternatives.

For example, this YAML defines a simple nesting structure for four alternatives (DRIVE, WALK, WALK_TO_TRANSIT, DRIVE_TO_TRANSIT) with the two transit alternatives grouped together in a nest:

NESTS:
  name: root
  coefficient: coef_nest_root
  alternatives:
    - DRIVE
    - WALK
    - name: TRANSIT
      coefficient: coef_nest_transit
      alternatives:
      - WALK_TO_TRANSIT
      - DRIVE_TO_TRANSIT
Validated by:
  • update_sharrow_skip

field PARK_AND_RIDE_MODES: list[str] | None = None#

List of modes that are considered park-and-ride modes. Needed for filtering choices to calculate park-and-ride lot capacities. Should correspond to the columns in the tour mode choice specification file.

Validated by:
  • update_sharrow_skip

field RESAMPLE_STRATEGY: Literal['latest', 'random'] = 'latest'#

Strategy to use when selecting tours for resampling park-and-ride lot choices. - latest: tours arriving the latest are selected for resampling. - random: randomly resample from all tours at the over-capacitated lot.

Validated by:
  • update_sharrow_skip

field SPEC: Path [Required]#

Utility specification filename.

This is sometimes alternatively called the utility expressions calculator (UEC). It is a CSV file giving all the functions for the terms of a linear-in-parameters utility expression.

Validated by:
  • update_sharrow_skip

field TRACE_PNR_CAPACITIES_PER_ITERATION: bool = True#

If True, output park-and-ride lot occupancy at each iteration to the trace folder.

Validated by:
  • update_sharrow_skip

field TRANSIT_SKIMS_FOR_ELIGIBILITY: list[str] | None = None#

A list of skim names to use for filtering choosers to only those with destinations that have transit access. If None, all tours are considered eligible for park-and-ride lot choice.

Validated by:
  • update_sharrow_skip

field alts_preprocessor: PreprocessorSettings | None = None#

Preprocessor settings for park-and-ride lot alternatives.

Validated by:
  • update_sharrow_skip

field annotate_households: PreprocessorSettings | None = None#

Annotate households output tables with additional columns.

These settings are used to add additional columns to the output tables after the logit model is run. They are typically used to add additional attributes that are derived from the model results.

Validated by:
  • update_sharrow_skip

field annotate_persons: PreprocessorSettings | None = None#

Annotate persons output tables with additional columns.

Validated by:
  • update_sharrow_skip

field annotate_tours: PreprocessorSettings | None = None#

Annotate tours output tables with additional columns.

Validated by:
  • update_sharrow_skip

field annotate_trips: PreprocessorSettings | None = None#

Annotate trips output tables with additional columns.

Validated by:
  • update_sharrow_skip

field annotate_vehicles: PreprocessorSettings | None = None#

Annotate vehicles output tables with additional columns.

Validated by:
  • update_sharrow_skip

field compute_settings: ComputeSettings = ComputeSettings(sample_method=None, sharrow_skip=False, fastmath=False, use_bottleneck=None, use_numexpr=None, use_numba=None, drop_unused_columns=True, protect_columns=[], performance_log=None)#

Sharrow settings for this component.

Validated by:
  • update_sharrow_skip

field explicit_chunk: float = 0#

If > 0, use this chunk size instead of adaptive chunking. If less than 1, use this fraction of the total number of rows.

Validated by:
  • update_sharrow_skip

field preprocessor: PreprocessorSettings | list[PreprocessorSettings] | None = None#

Chooser preprocessor settings.

This is a set of expressions to be evaluated on the choosers before the logit model is run. It is used to prepare the choosers for the logit model by adding columns that are used in the utility expressions.

Validated by:
  • update_sharrow_skip

field source_file_paths: list[Path] = None#

A list of source files from which these settings were loaded.

This value should not be set by the user within the YAML settings files, instead it is populated as those files are loaded. It is primarily provided for debugging purposes, and does not actually affect the operation of any model.

Validated by:
  • update_sharrow_skip

classmethod nests_are_for_nl(nests, values)#

Checks that nests are provided if (and only if) LOGIT_TYPE is NL.

validator update_sharrow_skip  »  all fields#

Implementation#

activitysim.abm.models.park_and_ride_lot_choice.park_and_ride_lot_choice(state: State, tours: DataFrame, tours_merged: DataFrame, land_use: DataFrame, network_los: Network_LOS, model_settings: ParkAndRideLotChoiceSettings | None = None, model_settings_file_name: str = 'park_and_ride_lot_choice.yaml', trace_label: str = 'park_and_ride_lot_choice', trace_hh_id: bool = False) → None#

This model predicts which lot location would be used for a park-and-ride tour.

activitysim.abm.models.park_and_ride_lot_choice.run_park_and_ride_lot_choice(state: State, choosers: DataFrame, land_use: DataFrame, network_los: Network_LOS, model_settings: ParkAndRideLotChoiceSettings | None = None, choosers_dest_col_name: str = 'destination', choosers_origin_col_name: str = 'home_zone_id', estimator=None, model_settings_file_name: str = 'park_and_ride_lot_choice.yaml', pnr_capacity_cls: ParkAndRideCapacity | None = None, trace_label: str = 'park_and_ride_lot_choice') → Series#

Run the park-and-ride lot choice model.

Provides another entry point for the model which is useful for getting pnr locations while creating logsums.