supply_uncertainty Module

Overview

Stockpyl contains code to solve the following types of single-echelon inventory optimization problems in the supply_uncertainty module:

  • Economic order quantity (EOQ)-based models
    • with disruptions

    • with yield uncertainty

  • Newsvendor-based models
    • with disruptions

    • with yield uncertainty

Note

The notation and references (equations, sections, examples, etc.) used below refer to Snyder and Shen, Fundamentals of Supply Chain Theory (FoSCT), 2nd edition (2019).

See Also

For an overview of supply uncertainty in Stockpyl, see the tutorial page for supply uncertainty.

API Reference

eoq_with_disruptions(fixed_cost, holding_cost, stockout_cost, demand_rate, disruption_rate, recovery_rate, approximate=False)[source]

Solve the economic order quantity problem with disruptions (EOQD) as presented by Parlar and Berkin (1991) and Berk and Arreola-Risa (1994). Problem is solved numerically using golden section search.

Set approximate to True to use the approximation by Snyder (2014).

Parameters
  • fixed_cost (float) – Fixed cost per order. [\(K\)]

  • holding_cost (float) – Holding cost per item per unit time. [\(h\)]

  • stockout_cost (float) – Stockout cost per item. [\(p\)]

  • demand_rate (float) – Demand (items) per unit time. [\(d\)]

  • disruption_rate (float) – Parameter of exponential distribution governing length of “up” intervals. [\(\lambda\)]

  • recovery_rate (float) – Parameter of exponential distribution governing length of “down” intervals. [\(\mu\)]

  • approximate (bool, optional) – Use approximate cost function?

Returns

  • order_quantity (float) – Optimal order quantity (items). [\(Q^*\)]

  • cost (float) – Optimal cost per unit time. [\(g^*\)]

Raises
  • ValueError – If fixed_cost or demand_rate < 0, or if holding_cost or stockout_cost <= 0.

  • ValueError – If disruption_rate <= 0 or recovery_rate <= 0.

Equations Used (equation (9.5)):

\[g(Q) = \frac{K + hQ^2/2d + pd\psi/\mu}{Q/d + \psi/\mu},\]

where

\[\psi = \frac{\lambda}{\lambda+\mu} \left(1 - e^{-\frac{(\lambda+\mu)Q}{d}}\right)\]

if approximate is False. If approximate is True, then

\[Q^* = \frac{\sqrt{(\psi d h)^2 + 2h\mu(Kd\mu + d^2p\psi)} - \psi dh}{h\mu},\]

where

\[\psi = \frac{\lambda}{\lambda+\mu},\]

and

\[g(Q^*) = hQ^*.\]

(See Snyder (2014).)

References

  1. Parlar and D. Berkin. Future supply uncertainty in EOQ models. Naval Research Logistics, 38 (1):107–121, 1991.

  1. Berk and A. Arreola-Risa. Note on “Future supply uncertainty in EOQ models”. Naval Research Logistics, 41(1):129–132, 1994.

    1. Snyder. A tight approximation for a continuousreview inventory model with supplier disruptions. International Journal of Production Economics, 155:91–108, 2014.

Example (Example 9.1-9.2):

>>> eoq_with_disruptions(8, 0.225, 5, 1300, 1.5, 14)
(772.8110739983106, 173.95000257319708)

>>> eoq_with_disruptions(8, 0.225, 5, 1300, 1.5, 14, approximate=True)
(773.1432417118889, 173.957229385175)
eoq_with_disruptions_cost(order_quantity, fixed_cost, holding_cost, stockout_cost, demand_rate, disruption_rate, recovery_rate, approximate=False)[source]

Calculate the cost of using order_quantity as the solution to the economic order quantity problem with disruptions (EOQD) as presented by Parlar and Berkin (1991) and Berk and Arreola-Risa (1994).

Set approximate to True to use the approximation by Snyder (2014).

Parameters
  • order_quantity (float) – Order quantity for cost evaluation. [\(Q\)]

  • fixed_cost (float) – Fixed cost per order. [\(K\)]

  • holding_cost (float) – Holding cost per item per unit time. [\(h\)]

  • stockout_cost (float) – Stockout cost per item. [\(p\)]

  • demand_rate (float) – Demand (items) per unit time. [\(d\)]

  • disruption_rate (float) – Parameter of exponential distribution governing length of “up” intervals. [\(\lambda\)]

  • recovery_rate (float) – Parameter of exponential distribution governing length of “down” intervals. [\(\mu\)]

  • approximate (bool, optional) – Use approximate cost function?

Returns

cost – Optimal cost per unit time. [\(g^*\)]

Return type

float

Raises
  • ValueError – If fixed_cost or demand_rate < 0, or if holding_cost, stockout_cost, or order_quantity <= 0.

  • ValueError – If disruption_rate <= 0 or recovery_rate <= 0.

Equations Used (equation (9.5)):

\[g(Q) = \frac{K + hQ^2/2d + pd\psi/\mu}{Q/d + \psi/\mu},\]

where

\[\psi = \frac{\lambda}{\lambda+\mu} \left(1 - e^{-\frac{(\lambda+\mu)Q}{d}}\right)\]

if approximate is False and

\[\psi = \frac{\lambda}{\lambda+\mu}\]

if approximate is True.

References

  1. Parlar and D. Berkin. Future supply uncertainty in EOQ models. Naval Research Logistics, 38 (1):107–121, 1991.

  1. Berk and A. Arreola-Risa. Note on “Future supply uncertainty in EOQ models”. Naval Research Logistics, 41(1):129–132, 1994.

    1. Snyder. A tight approximation for a continuousreview inventory model with supplier disruptions. International Journal of Production Economics, 155:91–108, 2014.

Example (Example 9.1):

>>> eoq_with_disruptions_cost(700, 8, 0.225, 5, 1300, 1.5, 14)
174.78711738886236

>>> eoq_with_disruptions_cost(700, 8, 0.225, 5, 1300, 1.5, 14, approximate=True)
174.80614234644133
newsvendor_with_disruptions(holding_cost, stockout_cost, demand, disruption_prob, recovery_prob, base_stock_level=None)[source]

Solve the newsvendor problem with disruptions and deterministic demand, or (if base_stock_level is supplied) calculate expected cost of given solution.

Parameters
  • holding_cost (float) – Holding cost per item per period. [\(h\)]

  • stockout_cost (float) – Stockout cost per item per period. [\(p\)]

  • demand (float) – Demand per period. [\(d\)]

  • disruption_prob (float) – Probability of disruption in period \(t+1\) given that there is no disruption in period \(t\). [\(\alpha\)]

  • recovery_prob (float) – Probability of no disruption in period \(t+1\) given that there is a disruption in period \(t\). [\(\beta\)]

  • base_stock_level (float, optional) – Base-stock level for cost evaluation. If supplied, no optimization will be performed. [\(S\)]

Returns

  • base_stock_level (float) – Optimal base-stock level (or base-stock level supplied). [\(S^*\)]

  • cost (float) – Expected cost per period attained by base_stock_level. [\(g^*\)]

Raises
  • ValueError – If holding_cost, stockout_cost, or demand <= 0.

  • ValueError – If disruption_prob or recovery_prob is not in (0,1).

Equations Used ((9.18), (9.14), and Lemma 9.2):

\[S^* = d + dF^{-1}\left(\frac{p}{p+h}\right)\]
\[g(S) = \sum_{n=0}^\infty \pi_n \left[h\left[S-(n+1)d\right]^+ + p\left[(n+1)d-S\right]^+\right],\]

where

\[\pi_0 = \frac{\beta}{\alpha+\beta}\]
\[\pi_n = \frac{\alpha\beta}{\alpha+\beta}(1-\beta)^{n-1}, \quad n \ge 1\]
\[F(n) = 1 - \frac{\alpha}{\alpha+\beta}(1-\beta)^n.\]

Example (Example 9.3):

>>> newsvendor_with_disruptions(0.25, 3, 2000, 0.04, 0.25)
(8000, 2737.0689302470355)
>>> newsvendor_with_disruptions(0.25, 3, 2000, 0.04, 0.25, base_stock_level=1200)
(1200, 5710.344790717086)
eoq_with_additive_yield_uncertainty(fixed_cost, holding_cost, demand_rate, yield_mean, yield_sd, order_quantity=None)[source]

Solve the EOQ problem with additive yield uncertainty and deterministic demand, or (if order_quantity is supplied) calculate expected cost of given solution.

Note that the optimal solution depends only on the mean and standard deviation of the yield distribution, not the distribution itself.

Parameters
  • fixed_cost (float) – Fixed cost per order. [\(K\)]

  • holding_cost (float) – Holding cost per item per unit time. [\(h\)]

  • demand_rate (float) – Demand (items) per unit time. [\(d\)]

  • yield_mean (float) – Mean of yield distribution. [\(E[Y]\)]

  • yield_sd (float) – Standard deviation of yield distribution. [\(\text{SD}[Y]\)]

  • order_quantity (float, optional) – Order quantity for cost evaluation. If supplied, no optimization will be performed. [\(Q\)]

Returns

  • order_quantity (float) – Optimal order quantity (or order quantity supplied). [\(Q^*\)]

  • cost (float) – Expected cost per unit time attained by order_quantity. [\(g^*\)]

Raises
  • ValueError – If fixed_cost or demand_rate, or if holding_cost <= 0.

  • ValueError – If yield_sd < 0.

Equations Used ((9.23) and (9.22)):

\[Q^* = \sqrt{\frac{2Kd}{h} + \text{Var}[Y]} - E[Y]\]
\[g(Q) = \frac{2Kd + h\text{Var}[Y]}{2(Q+E[Y])} + \frac{h(Q+E[Y])}{2}\]

Example (Example 9.4):

>>> eoq_with_additive_yield_uncertainty(18500, 0.06, 75000, -15000, 9000)
(230246.37046881882, 12914.78222812913)
>>> eoq_with_additive_yield_uncertainty(18500, 0.06, 75000, -15000, 9000, order_quantity=300000)
(300000, 13426.947368421053)
eoq_with_multiplicative_yield_uncertainty(fixed_cost, holding_cost, demand_rate, yield_mean, yield_sd, order_quantity=None)[source]

Solve the EOQ problem with multiplicative yield uncertainty and deterministic demand, or (if order_quantity is supplied) calculate expected cost of given solution.

Note that the optimal solution depends only on the mean and standard deviation of the yield distribution, not the distribution itself.

Parameters
  • fixed_cost (float) – Fixed cost per order. [\(K\)]

  • holding_cost (float) – Holding cost per item per unit time. [\(h\)]

  • demand_rate (float) – Demand (items) per unit time. [\(d\)]

  • yield_mean (float) – Mean of yield distribution. [\(E[Z]\)]

  • yield_sd (float) – Standard deviation of yield distribution. [\(\text{SD}[Z]\)]

  • order_quantity (float, optional) – Order quantity for cost evaluation. If supplied, no optimization will be performed. [\(Q\)]

Returns

  • order_quantity (float) – Optimal order quantity (or order quantity supplied). [\(Q^*\)]

  • cost (float) – Expected cost per unit time attained by order_quantity. [\(g^*\)]

Raises
  • ValueError – If fixed_cost or demand_rate, or if holding_cost <= 0.

  • ValueError – If yield_sd < 0.

Equations Used ((9.25) and (9.24)):

\[Q^* = \sqrt{\frac{2Kd}{h(\text{Var}[Z] + E[Z]^2)}}\]
\[g(Q) = \frac{Kd}{QE[Z]} + \frac{hQ(\text{Var}[Z] + E[Z]^2)}{2E[Z]}\]

Example (Example 9.5):

>>> eoq_with_multiplicative_yield_uncertainty(18500, 0.06, 75000, 0.8333, math.sqrt(0.0198))
(254477.46130342316, 13086.16169098594)
>>> eoq_with_multiplicative_yield_uncertainty(18500, 0.06, 75000, 0.8333, math.sqrt(0.0198), order_quantity=300000)
(300000, 13263.770562822512)
newsvendor_with_additive_yield_uncertainty(holding_cost, stockout_cost, demand, yield_mean=None, yield_sd=None, yield_distribution=None, loss_function=None, base_stock_level=None)[source]

Solve the newsvendor problem with additive yield uncertainty and deterministic demand, or (if base_stock_level is supplied) calculate expected cost of given solution.

Either provide yield_mean and yield_sd to use a normal yield distribution, or provide yield_distribution as a frozen rv_continuous or rv_discrete object. If yield_distribution is provided, then loss functions are calculated using loss_functions.continuous_loss() or loss_functions.discrete_loss(), unless loss_function is provided. (Loss functions are used in expected-cost calculation.)

Parameters
  • holding_cost (float) – Holding cost per item per period. [\(h\)]

  • stockout_cost (float) – Stockout cost per item per period. [\(p\)]

  • demand (float) – Demand per period. [\(d\)]

  • yield_mean (float, optional) – Mean of yield distribution. [\(E[Y]\)]

  • yield_sd (float, optional) – Standard deviation of yield distribution. [\(\text{SD}[Y]\)]

  • yield_distribution (rv_continuous or rv_discrete, optional) – Yield distribution. Required if yield_mean or yield_sd is None.

  • loss_function (function, optional) – Function that takes a single argument and returns a tuple consisting of the loss function and complementary loss function value of that argument. Ignored if yield_distribution is None.

  • base_stock_level (float, optional) – Base-stock level for cost evaluation. If supplied, no optimization will be performed. [\(S\)]

Returns

  • base_stock_level (float) – Optimal base-stock level (or base-stock level supplied). [\(S^*\)]

  • cost (float) – Expected cost per unit time attained by base_stock_level. [\(g^*\)]

Raises
  • ValueError – If holding_cost, stockout_cost, or demand <= 0.

  • ValueError – If yield_sd <= 0.

  • ValueError – If (yield_mean is None or yield_sd is None) and yield_distribution is None.

Equations Used ((9.28) and (9.27)):

\[S^* = d - F_Y^{-1}\left(\frac{h}{h+p}\right)\]
\[g(S) = p\bar{n}(d-S) + hn(d-S),\]

where \(n(\cdot)\) and \(\bar{n}(\cdot)\) are the loss function and complementary loss function, respectively, of the yield distribution.

Example (Example 9.6):

Using generic function to calculate loss functions:

>>> from scipy.stats import uniform
>>> newsvendor_with_additive_yield_uncertainty(15, 75, 1.5e6, yield_distribution=uniform(-500000, 1000000))
(1833333.3333333335, 6249999.997499999)

Using uniform_loss() to calculate loss functions, which is more accurate:

>>> from stockpyl.loss_functions import uniform_loss
>>> loss_function = lambda x: uniform_loss(x, -500000, 500000)
>>> newsvendor_with_additive_yield_uncertainty(15, 75, 1.5e6, yield_distribution=uniform(-500000, 1000000), loss_function=loss_function)
(1833333.3333333335, 6250000.000000001)