Skip to content

ProBP Optimization

Optimization is the most fundamental part of revenue management systems, is it is the process used to tailor the set of products being offered to maximize revenue. It typically occurs after untruncation and forecasting.

PassengerSim offers several different optimization algorithms, including probabilistic bid price (ProBP) optimization.

Bases: RmAction

ProBP (ProbabilisticBidPrice) is a path-based optimization algorithm.

Source code in passengersim/rm/probp.py
class ProbabilisticBidPrice(RmAction):
    """
    ProBP (ProbabilisticBidPrice) is a path-based optimization algorithm.
    """

    requires: set[str] = {"path_forecast"}
    frequency = "daily_pre_dep"

    def __init__(
        self,
        *,
        carrier: str = "",
        cabins: str | list[str] | None = None,
        minimum_sample: int = 10,
        cfg: Config | None = None,
        capacity_sharing: bool | None = False,
        capacity_sharing_start_dcp_index: int | None = 0,
        use_adjusted_fares: bool = False,
        bid_price_vector: bool | None = False,
        maxiter: int = 10,
        use_sub_bp: bool = False,
    ):
        super().__init__(
            carrier=carrier,
            minimum_sample=minimum_sample,
            cfg=cfg,
        )

        self._pro_bp_engine = None

        self.cabins = cabins
        """Optional list of cabin codes to optimize.

        If not provided, this tool will optimize on the leg as a whole."""

        self.capacity_sharing = capacity_sharing
        """ Capacity sharing flag between cabins.

        When set to True, will use method 3 from Peter Belobaba's presentation.
        Higher cabin(s) will get max of combined cabins or itself alone.
        Lower cabin(s) will get min of combined cabins or itself alone."""

        self.capacity_sharing_start_dcp_index = capacity_sharing_start_dcp_index

        self.use_adjusted_fares = use_adjusted_fares
        """
        If True, ProBP will use the adjusted fares for the optimization.

        The default is False, which means that ProBP will use the original fares.  This
        should be set to True if fare adjustment is being used for this carrier.
        """

        self.bid_price_vector = bid_price_vector
        """
        If True, we create a bid price vector in ProBP,
        rather than just keep a constant bid-price untiol daily re-optimization
        """

        self.maxiter = maxiter
        """
        The maximum number of iterations to run ProBP.

        If the algorithm has not converged by the time this number of iterations has
        been reached, it will stop and return the current results.
        """

        self.use_sub_bp: bool = use_sub_bp
        """Whether to use SubBP (True) or ProBP (False)."""

    def rm_engine(self, sim: Simulation) -> ProBP:
        # We a reference to a ProBP object, as the CoreProBP code caches
        # the data structures it needs for each iteration
        if self._pro_bp_engine is None:
            engine = ProBP(sim.eng, self.carrier)
            if self.use_sub_bp:
                engine.use_sub_bp = True
            self._pro_bp_engine = engine
        return self._pro_bp_engine

    def run(self, sim: Simulation, days_prior: int):
        if not self.should_run(sim, days_prior):
            return

        if self.capacity_sharing:
            if sim.last_dcp_index < self.capacity_sharing_start_dcp_index:
                for leg in sim.legs.set_filters(carrier=self.carrier):
                    leg.capacity_sharing = False
            else:
                for leg in sim.legs.set_filters(carrier=self.carrier):
                    leg.capacity_sharing = True

        z = self.rm_engine(sim)

        # Update the decision fares if needed
        dcp_index = self.get_dcp_index(days_prior, allow_between=True)
        if self.use_adjusted_fares:
            z.update_decision_fares(dcp_index)

        snapshot_instruction = None
        _info = ""
        if sim.snapshot_filters is not None:
            snapshot_filters = sim.snapshot_filters
            _debug_output_title = None
            for sf in snapshot_filters:
                if sf.type != "pro_bp":
                    continue
                snapshot_instruction = sf.run(sim.eng, carrier=self.carrier)
                _info = getattr(sf, "_last_run_info", "")
                if snapshot_instruction and (sf.carrier == self.carrier or sf.carrier == ""):
                    z.debug_fltno = sf.flt_no[0] if len(sf.flt_no) > 0 else 0
                    _debug_output_title = sf.title
                    break
        # num_cabins = len(self.cabins) if self.cabins is not None else 0
        debug_output = z.run("", self.maxiter, self.bid_price_vector, snapshot_instruction)
        if self.cabins:
            for c in self.cabins:
                debug_output = z.run(c, self.maxiter, self.bid_price_vector, snapshot_instruction)

        if debug_output:
            print(debug_output)

bid_price_vector instance-attribute

bid_price_vector = bid_price_vector

If True, we create a bid price vector in ProBP, rather than just keep a constant bid-price untiol daily re-optimization

cabins instance-attribute

cabins = cabins

Optional list of cabin codes to optimize.

If not provided, this tool will optimize on the leg as a whole.

capacity_sharing instance-attribute

capacity_sharing = capacity_sharing

Capacity sharing flag between cabins.

When set to True, will use method 3 from Peter Belobaba's presentation. Higher cabin(s) will get max of combined cabins or itself alone. Lower cabin(s) will get min of combined cabins or itself alone.

capacity_sharing_start_dcp_index instance-attribute

capacity_sharing_start_dcp_index = (
    capacity_sharing_start_dcp_index
)

frequency class-attribute instance-attribute

frequency = 'daily_pre_dep'

maxiter instance-attribute

maxiter = maxiter

The maximum number of iterations to run ProBP.

If the algorithm has not converged by the time this number of iterations has been reached, it will stop and return the current results.

requires class-attribute instance-attribute

requires: set[str] = {'path_forecast'}

use_adjusted_fares instance-attribute

use_adjusted_fares = use_adjusted_fares

If True, ProBP will use the adjusted fares for the optimization.

The default is False, which means that ProBP will use the original fares. This should be set to True if fare adjustment is being used for this carrier.

use_sub_bp instance-attribute

use_sub_bp: bool = use_sub_bp

Whether to use SubBP (True) or ProBP (False).

__init__

__init__(
    *,
    carrier: str = "",
    cabins: str | list[str] | None = None,
    minimum_sample: int = 10,
    cfg: Config | None = None,
    capacity_sharing: bool | None = False,
    capacity_sharing_start_dcp_index: int | None = 0,
    use_adjusted_fares: bool = False,
    bid_price_vector: bool | None = False,
    maxiter: int = 10,
    use_sub_bp: bool = False,
)
Source code in passengersim/rm/probp.py
def __init__(
    self,
    *,
    carrier: str = "",
    cabins: str | list[str] | None = None,
    minimum_sample: int = 10,
    cfg: Config | None = None,
    capacity_sharing: bool | None = False,
    capacity_sharing_start_dcp_index: int | None = 0,
    use_adjusted_fares: bool = False,
    bid_price_vector: bool | None = False,
    maxiter: int = 10,
    use_sub_bp: bool = False,
):
    super().__init__(
        carrier=carrier,
        minimum_sample=minimum_sample,
        cfg=cfg,
    )

    self._pro_bp_engine = None

    self.cabins = cabins
    """Optional list of cabin codes to optimize.

    If not provided, this tool will optimize on the leg as a whole."""

    self.capacity_sharing = capacity_sharing
    """ Capacity sharing flag between cabins.

    When set to True, will use method 3 from Peter Belobaba's presentation.
    Higher cabin(s) will get max of combined cabins or itself alone.
    Lower cabin(s) will get min of combined cabins or itself alone."""

    self.capacity_sharing_start_dcp_index = capacity_sharing_start_dcp_index

    self.use_adjusted_fares = use_adjusted_fares
    """
    If True, ProBP will use the adjusted fares for the optimization.

    The default is False, which means that ProBP will use the original fares.  This
    should be set to True if fare adjustment is being used for this carrier.
    """

    self.bid_price_vector = bid_price_vector
    """
    If True, we create a bid price vector in ProBP,
    rather than just keep a constant bid-price untiol daily re-optimization
    """

    self.maxiter = maxiter
    """
    The maximum number of iterations to run ProBP.

    If the algorithm has not converged by the time this number of iterations has
    been reached, it will stop and return the current results.
    """

    self.use_sub_bp: bool = use_sub_bp
    """Whether to use SubBP (True) or ProBP (False)."""

rm_engine

rm_engine(sim: Simulation) -> ProBP
Source code in passengersim/rm/probp.py
def rm_engine(self, sim: Simulation) -> ProBP:
    # We a reference to a ProBP object, as the CoreProBP code caches
    # the data structures it needs for each iteration
    if self._pro_bp_engine is None:
        engine = ProBP(sim.eng, self.carrier)
        if self.use_sub_bp:
            engine.use_sub_bp = True
        self._pro_bp_engine = engine
    return self._pro_bp_engine

run

run(sim: Simulation, days_prior: int)
Source code in passengersim/rm/probp.py
def run(self, sim: Simulation, days_prior: int):
    if not self.should_run(sim, days_prior):
        return

    if self.capacity_sharing:
        if sim.last_dcp_index < self.capacity_sharing_start_dcp_index:
            for leg in sim.legs.set_filters(carrier=self.carrier):
                leg.capacity_sharing = False
        else:
            for leg in sim.legs.set_filters(carrier=self.carrier):
                leg.capacity_sharing = True

    z = self.rm_engine(sim)

    # Update the decision fares if needed
    dcp_index = self.get_dcp_index(days_prior, allow_between=True)
    if self.use_adjusted_fares:
        z.update_decision_fares(dcp_index)

    snapshot_instruction = None
    _info = ""
    if sim.snapshot_filters is not None:
        snapshot_filters = sim.snapshot_filters
        _debug_output_title = None
        for sf in snapshot_filters:
            if sf.type != "pro_bp":
                continue
            snapshot_instruction = sf.run(sim.eng, carrier=self.carrier)
            _info = getattr(sf, "_last_run_info", "")
            if snapshot_instruction and (sf.carrier == self.carrier or sf.carrier == ""):
                z.debug_fltno = sf.flt_no[0] if len(sf.flt_no) > 0 else 0
                _debug_output_title = sf.title
                break
    # num_cabins = len(self.cabins) if self.cabins is not None else 0
    debug_output = z.run("", self.maxiter, self.bid_price_vector, snapshot_instruction)
    if self.cabins:
        for c in self.cabins:
            debug_output = z.run(c, self.maxiter, self.bid_price_vector, snapshot_instruction)

    if debug_output:
        print(debug_output)