Source code for passengersim.config.simulation_controls

# TITLE: Simulation Controls
# DOC-NAME: 01-simulation-controls
from __future__ import annotations

import warnings
from typing import Annotated, Any, Literal

from pydantic import Field, ValidationInfo, confloat, conint, field_validator, model_validator

from passengersim.utils import iso_to_unix

from .pretty import PrettyModel
from .speed_limits import SpeedLimits


[docs] class ConnectionBuilderSettings(PrettyModel, extra="forbid", validate_assignment=True): """ Settings for the automatic connection builder. This tool generates connections in the simulation, converting legs into paths. """ max_legs: Annotated[int, Field(ge=1, le=6)] = 2 """The maximum number of legs to include in any generated path.""" max_legs_if_nonstop_exists: Annotated[int, Field(ge=1, le=6)] = 2 """The maximum number of legs to include in any generated path if a nonstop path exists in that market. The nonstop path can be on any carrier.""" existing_paths: Literal["keep", "replace", "required", "none"] = "keep" """What to do with existing paths when generating new ones. The default value is "keep", which means that for any market where paths already exist they will be used, and no new paths will be generated. For markets where no paths exist, new paths will be generated as normal. Alternatively, set this to "none", which has the same behavior as "keep" but will raise an error if the configuration includes any defined paths. Other options are "replace", which will remove all existing paths and then generating new ones for all markets, and "required", which will prevent the generation of any new paths. If set to "required", the connection builder will only serve as a check that all markets have paths, and will raise an error if any market is missing paths. """ circuity_function: str = "default_circuity_function" """The function to use when deciding if a path is allowable due to circuity. Circuity is the ratio of the total distance of the path to the direct distance between the origin and destination. The default function disallows paths that are excessively circuitous, with thresholds that vary based on the direct distance. Users can provide their own function with the same signature to implement custom circuity rules. The circuity function is specified by name here and should be a registered circuity function. See `passengersim.connection_builder.circuity` for more details on circuity functions and how to register custom ones. """ nonstop_leg_path_id_alignment: bool = True """Whether to align path IDs with leg IDs for nonstop paths. By default, this is set to True, which means that any nonstop path (corresponds to a single leg) will be assigned the same ID as that leg by the path building algorithm. This can make it easier to identify and analyze nonstop paths in the simulation results. If set to False, nonstop paths will be assigned unique IDs that do not necessarily align with leg IDs. This generally corresponds to the behavior of the previous path building algorithm, and may be desirable in cases where there are existing results to compare against. """ verbosity: int = 0 """The level of detail to include in connection builder logging.""" min_paths_per_market: int = 1 """The minimum number of paths to generate for each market. This is not a hard minimum, but the connection builder will make an effort to generate at least this many paths for each market, if possible given the other settings. This could be by progressively relaxing circuity rules, maximum connection times, or other tweaks. If the connection builder is unable to generate at least this many paths for a market, it will log a warning. """ extra_max_connect_time_per_iteration: int = 0 """Extra time added to all maximum connection times at each iteration. The connection builder iterates when the `min_paths_per_market` value is not met, potentially relaxing circuity rules at each iteration. This setting also allows for the relaxation of maximum connect times, by adding this many minutes to all maximum connection times at each iteration. """
[docs] class SimulationSettings(PrettyModel, extra="allow", validate_assignment=True): num_trials: conint(ge=1, le=1000) = 1 """The overall number of trials to run. Each trial is a complete simulation, including burn-in training time as well as study time. It will have a number of sequentially developed samples, each of which represents one "typical" day of travel. See [Counting Simulations][counting-simulations] for more details. """ num_samples: conint(ge=1, le=10000) = 600 """The number of samples to run within each trial. Each sample represents one "typical" day of travel. See [Counting Simulations][counting-simulations] for more details. """ burn_samples: conint(ge=0, le=10000) = 100 """The number of samples to burn when starting each trial. Burned samples are used to populate a stable history of data to support forecasting and optimization algorithms, but are not used to evaluate performance results. See [Counting Simulations][counting-simulations] for more details. """ double_capacity_until: int | None = None """ Double the capacity on all legs until this sample. The extra capacity may reduce the statistical noise of untruncation within the burn period and allow the simulation to achieve a stable steady state faster. If used, this should be set to a value at least 26 below the `burn_samples` value to avoid polluting the results. """ @field_validator("double_capacity_until") @classmethod def _avoid_capacity_pollution(cls, v: int | None, info: ValidationInfo): if v and v >= info.data["burn_samples"] - 25: raise ValueError("doubled capacity will pollute results") return v sys_k_factor: confloat(ge=0, le=5.0) = 0.10 """ System-level randomness factor. This factor controls the level of correlation in demand levels across the entire system. See :ref:`demand-generation-k-factors` for more details. """ mkt_k_factor: confloat(ge=0, le=5.0) = 0.20 """ Market-level randomness factor. This factor controls the level of correlation in demand levels across origin- destination markets. See :ref:`demand-generation-k-factors` for more details. """ pax_type_k_factor: confloat(ge=0, le=5.0) = 0.0 """ Passenger-type randomness factor. Deprecated: use `simple_k_factor` instead. This factor add uncorrelated variance to every demand, unless there are multiple demands in the same market and with the same passenger segment. See :ref:`demand-generation-k-factors` for more details. """ segment_k_factor: confloat(ge=0, le=5.0) = 0.0 """ Passenger segment randomness factor. This factor controls the level of correlation in demand levels across passenger segments. """ simple_k_factor: confloat(ge=0, le=5.0) = 0.40 """ Passenger-type randomness factor. This factor add uncorrelated variance to every demand. See :ref:`demand-generation-k-factors` for more details. """ @model_validator(mode="after") def _migrate_pax_type_k_factor(self): """Migrate deprecated pax_type_k_factor to simple_k_factor. If a non-zero value is provided for pax_type_k_factor, emit a DeprecationWarning and move the value to simple_k_factor. If the user also supplies a non-zero simple_k_factor, that is an error because we cannot determine which value should take precedence. """ if self.pax_type_k_factor != 0.0: if self.simple_k_factor != 0.0: raise ValueError( "Cannot set both `pax_type_k_factor` (deprecated) and " "`simple_k_factor` to non-zero values. " "Use `simple_k_factor` only." ) warnings.warn( "`pax_type_k_factor` is deprecated and will be removed in a " "future version. Use `simple_k_factor` instead.", DeprecationWarning, stacklevel=2, ) # Use object.__setattr__ to bypass Pydantic's validate_assignment, # which would otherwise re-trigger this model validator and cause # a spurious "both non-zero" conflict error mid-migration. object.__setattr__(self, "simple_k_factor", self.pax_type_k_factor) object.__setattr__(self, "pax_type_k_factor", 0.0) return self simple_cv100: confloat(ge=0, le=1.0) = 0.0 """THIS IS A TEST""" tf_k_factor: confloat(ge=0) = 0.1 """ Time frame randomness factor. This factor controls the dispersion of bookings over time, given a previously identified level of total demand. See [k-factors]() for more details. """ tot_z_factor: confloat(ge=0, le=100.0) = 2.0 """ Base level demand variance control. This factor scales the variance in the amount of total demand for any given market segment. See :ref:`demand-generation-k-factors` for more details. """ tf_z_factor: confloat(ge=0, le=100.0) = 2.0 """ Timeframe demand variance control. This factor scales the variance in the allocation of total demand to the various arrival timeframes. See :ref:`demand-generation-k-factors` for more details. """ prorate_revenue: bool = True revenue_alpha: float = 0.0 """Used to exponentially smooth revenue per PathClass, to get optimizationFare""" save_orders: bool = False save_all_offers: bool = False """ This will save all Offers, including those that would fail fare rules or availability. The output choice set data will have all of these, so you can find first choice demand, recapture, etc. False by default """ dwm_lite: bool = True """Deprecated, has no effect.""" use_standard_todd_curves: bool = True """ Use the "standard" time of day departure (TODD) curves. Each demand, unless otherwise given an explicit TODD curve to use, will use the standard TODD curve associated with the market's `delta_t`. """ max_connect_time: conint(ge=0) = 240 """ Maximum connection time for automatically generated paths. Any generated path that has a connection time greater than this value (expressed in minutes) is invalidated. """ disable_ap: bool = False """ Remove all advance purchase settings used in the simulation. This applies to all carriers and all fare products. If active, this filter is applied to all Fare definitions at the time the Config is loaded into to a Simulation object. """ demand_multiplier: confloat(gt=0) = 1.0 """ Scale all demand by this value. Setting to a value other than 1.0 will increase or decrease all demand inputs uniformly by the same multiplicative amount. This is helpful when exploring how simulation results vary when you have "low demand" scenarios (e.g, demand_multiplier = 0.8), or "high demand" scenarios (e.g., demand multiplier = 1.1). """ capacity_multiplier: confloat(gt=0) = 1.0 """ Scale all capacities by this value. Setting to a value other than 1.0 will increase or decrease all capacity inputs uniformly by the same multiplicative amount. Business class and/or first class can be quickly simulated with this option """ connection_builder: ConnectionBuilderSettings = ConnectionBuilderSettings() """Settings related to the automatic generation of paths and connections in the simulation.""" manual_paths: Annotated[ bool | None, Field( deprecated=( "`manual_paths` is deprecated and will be removed in a future version. " "Use `connection_builder.existing_paths` instead" ) ), ] = None """ Deprecated. See `connection_builder.existing_paths` instead. """ @model_validator(mode="after") def _migrate_manual_paths(self): """Migrate manual_paths to connection_builder.existing_paths.""" # Access via __dict__ to avoid triggering the deprecation warning # when the value is just None (i.e. the user didn't set it). manual_paths_value = self.__dict__.get("manual_paths") if manual_paths_value is not None: warnings.warn( "`manual_paths` is deprecated and will be removed in a future " "version. Use `connection_builder.existing_paths` instead", DeprecationWarning, stacklevel=2, ) # Migrate the value to connection_builder if manual_paths_value: self.connection_builder.existing_paths = "required" else: self.connection_builder.existing_paths = "none" # Clear the deprecated field self.__dict__["manual_paths"] = None return self generate_3seg: bool | None = False """ Use the new A* search to build connections, it can create 3seg connects """ @property def use_3seg(self) -> bool: return self.generate_3seg @use_3seg.setter def use_3seg(self, value: bool): # deprecated if value: warnings.warn( "`use_3seg` is deprecated, use `generate_3seg` instead", DeprecationWarning, stacklevel=2, ) self.generate_3seg = bool(value) write_raw_files: bool = False random_seed: int | None = 42 """ Integer used to control the reproducibility of simulation results. A seed is base value used by a pseudo-random generator to generate random numbers. A fixed random seed is used to ensure the same randomness pattern is reproducible and does not change between simulation runs, i.e. allows subsequent runs to be conducted with the same randomness pattern as a previous one. Any value set here will allow results to be repeated. The random number generator is re-seeded at the beginning of every sample in every trial with a fixed tuple of three values: this "global" random seed, plus the sample number and trial number. This ensures that partial results are also reproducible: the simulation of sample 234 in trial 2 will be the same regardless of how many samples are in trial 1. """ update_frequency: int | None = None controller_time_zone: int | float = -21600 """ The reference time zone for the controller (seconds relative to UTC). Data collection points will be trigger at approximately midnight in this time zone. This value can be input in hours instead of seconds, any absolute value less than or equal to 12 will be assumed to be hours and scaled to seconds. The default value is -6 hours, or US Central Standard Time. """ base_date: str = "2020-03-01" """ The default date used to compute relative times for travel. Future enhancements may include multi-day modeling. """ dcp_hour: float = 0.0 """ The hour of the day that the RM recalculation events are triggered. If set to zero, the events happen at midnight. Other values can delay the recalculation into later in the night (or the next day). """ capture_competitor_data: bool = False """ Turns on the capturing of competitor data. This feature captures lowest available fare data captured by market, for potential use in competitive analysis RM strategies. """ capture_choice_set_file: str = "" """ Turns on the capturing of the choice set and writes the data to the specified file """ capture_choice_set_obs: int | None = None """ If this is set, PassengerSim will randomly sample the ChoiceSet data and output APPROXIMATELY this many choice sets (each will have multiple items and all items for the choice set will be saved and output) """ capture_choice_set_mkts: list[tuple] | None = [] """Capture only these markets (O&D pairs)""" capture_demand_history: bool = False """Store the demand history for each demand. This can be activated to populate the `demand_history` summary table, if desired. """ show_progress_bar: bool = True """ Show a progress bar while running. The progress display requires `rich` is installed. """ # A bunch of debug flags, these are only used for development !!! debug_availability: bool | None = False debug_choice: bool | None = False debug_connections: bool | None = False debug_events: bool | None = False debug_fares: bool | None = False debug_offers: bool | None = False debug_orders: bool | None = False additional_settings: dict[str, Any] = {} """ Additional settings to pass to the simulation. These settings are passed directly to the simulation object and can be used to set various parameters that are not directly exposed in the configuration. """ @field_validator("controller_time_zone", mode="before") def _time_zone_convert_hours_to_seconds(cls, v): if -12 <= v <= 12: v *= 3600 return v
[docs] def reference_epoch(self) -> int: """Get the reference travel datetime in unix time.""" return iso_to_unix(self.base_date) - self.controller_time_zone
timeframe_demand_allocation: Literal["v2", "pods"] = "v2" """ Which algorithm to use for time frame demand allocation. """ allow_unused_restrictions: bool = False """ Allow restrictions to be defined but not used. If set to False, any restriction that is defined as a parameter of a choice model but not present on any fare, or vice versa, will raise a ValueError. Users may override this behavior by setting this parameter to True, which will emit a warning instead of an error. """ speed_limits: SpeedLimits = SpeedLimits() """ Speed limits for short, medium, and long travel legs. These are only used for data quality checks at Config load time. The limits should be set quite generously. If you set do speed limits, then legs that violate the limits (i.e., they appear abnormally fast or slow) will be flagged as validation errors when your data is loaded. Most of the time this will be due to data transcription errors (e.g. failure to account for crossing the international date line, incorrect assignment of airports to time zones, etc.) """