newsvendor Module

Overview

The newsvendor module contains code for solving the newsvendor problem and some of its variants.

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 single-echelon inventory optimization in Stockpyl, see the tutorial page for single-echelon inventory optimization.

API Reference

newsvendor_normal(holding_cost, stockout_cost, demand_mean, demand_sd, lead_time=0, base_stock_level=None)[source]

Solve the newsvendor problem with normal distribution, 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_mean (float) – Mean demand per period. [\(\mu\)]

  • demand_sd (float) – Standard deviation of demand per period. [\(\sigma\)]

  • lead_time (int, optional) – Lead time. Default = 0. [\(L\)]

  • 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 <= 0 or stockout_cost <= 0.

  • ValueError – If demand_mean <= 0 or demand_sd <= 0.

Equations Used (equations (4.30), (4.37), and (4.24), modified for non-zero lead time):

\[ \begin{align}\begin{aligned}S^* = \mu + z_{\alpha}\sigma\\g^* = (h+p)\phi(z_{\alpha})\sigma\end{aligned}\end{align} \]

where \(\mu\) and \(\sigma\) are the lead-time demand mean and standard deviation, and \(\alpha = p/(h+p)\),

or

\[g(S) = h\bar{n}(S) + pn(S),\]

where \(n(\cdot)\) and \(\bar{n}(\cdot)\) are the lead-time demand loss and complementary loss functions.

Example (Example 4.3):

>>> newsvendor_normal(0.18, 0.70, 50, 8)
(56.60395592743389, 1.9976051931766445)
newsvendor_normal_cost(base_stock_level, holding_cost, stockout_cost, demand_mean, demand_sd, lead_time=0)[source]

Calculate the cost of using base_stock_level as the solution to the newsvendor problem with normal distribution.

Parameters
  • base_stock_level (float) – Base-stock level for cost evaluation. [\(S\)]

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

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

  • demand_mean (float) – Mean demand per period. [\(\mu\)]

  • demand_sd (float) – Standard deviation of demand per period. [\(\sigma\)]

  • lead_time (int, optional) – Lead time. Default = 0. [\(L\)]

Returns

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

Return type

float

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

  • ValueError – If demand_mean <= 0 or demand_sd <= 0.

Equations Used (equation (4.24)):

\[g(S) = h\bar{n}(S) + pn(S),\]

where \(n(\cdot)\) and \(\bar{n}(\cdot)\) are the lead-time demand loss and complementary loss functions.

Example (Example 4.1):

>>> newsvendor_normal_cost(60, 0.18, 0.70, 50, 8)
2.156131552870387
newsvendor_poisson(holding_cost, stockout_cost, demand_mean, base_stock_level=None)[source]

Solve the newsvendor problem with Poisson distribution, or (if base_stock_level is supplied) calculate 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_mean (float) – Mean demand per period. [\(\mu\)]

  • 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) – Cost per period attained by base_stock_level. [\(g^*\)]

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

  • ValueError – If demand_mean <= 0.

  • ValueError – If base_stock_level is supplied and is not an integer.

Equations Used:

\[ \begin{align}\begin{aligned}S^* = \text{smallest } S \text{ such that } F(S) \ge \frac{p}{h+p}\\g(S^*) = h\bar{n}(S^*) + pn(S^*)\end{aligned}\end{align} \]

or

\[g(S) = h\bar{n}(S) + pn(S),\]

where \(F(\cdot)\), \(n(\cdot)\), and \(\bar{n}(\cdot)\) are the Poisson cdf, loss function, and complementary loss function, respectively.

Example:

>>> newsvendor_poisson(0.18, 0.70, 50)
(56.0, 1.797235211809178)
newsvendor_poisson_cost(base_stock_level, holding_cost, stockout_cost, demand_mean)[source]

Calculate the cost of using base_stock_level as the solution to the newsvendor problem with Poisson distribution.

Parameters
  • base_stock_level (float) – Base-stock level for cost evaluation. [\(S\)]

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

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

  • demand_mean (float) – Mean demand per period. [\(\mu\)]

  • lead_time (int, optional) – Lead time. Default = 0. [\(L\)]

Returns

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

Return type

float

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

  • ValueError – If demand_mean <= 0.

  • ValueError – If base_stock_level is not an integer.

Equations Used (equation (4.6)):

\[g(S) = h\bar{n}(S) + pn(S),\]

where \(n(\cdot)\) and \(\bar{n}(\cdot)\) are the lead-time demand loss and complementary loss functions.

Example (Example 4.1):

>>> newsvendor_poisson_cost(56, 0.18, 0.70, 50)
1.797235211809178
newsvendor_continuous(holding_cost, stockout_cost, demand_distrib=None, demand_pdf=None, base_stock_level=None)[source]

Solve the newsvendor problem with generic continuous distribution, or (if base_stock_level is supplied) calculate cost of given solution.

Must provide either rv_continuous distribution (in demand_distrib) or demand pdf (in demand_pdf, as a function).

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

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

  • demand_distrib (rv_continuous, optional) – Demand distribution object.

  • demand_pdf (function, optional) – Demand pdf, as a function. Ignored if demand_distrib is not None. [\(f(\cdot)\)]

  • 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) – Cost per period attained by base_stock_level. [\(g^*\)]

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

  • ValueError – If demand_distrib and demand_pdf are both None.

Equations Used (equations (4.27) and (4.24)):

\[ \begin{align}\begin{aligned}S^* = F^{-1}\left(\frac{p}{h+p}\right)\\g(S) = h\bar{n}(S^*) + pn(S)\end{aligned}\end{align} \]

where \(F(\cdot)\), \(n(\cdot)\), and \(\bar{n}(\cdot)\) are the demand cdf, loss function, and complementary loss function, respectively.

Example (Example 4.3):

>>> from scipy.stats import norm
>>> demand_distrib = norm(50, 8)
>>> newsvendor_continuous(0.18, 0.70, demand_distrib)
(56.60395592743389, 1.997605188935892)
>>> newsvendor_continuous(0.18, 0.70, demand_distrib, base_stock_level=40)
(40, 7.35613154776623)

Example (Problem 4.8(b)):

>>> from scipy.stats import lognorm
>>> demand_distrib = lognorm(0.3, 0, np.exp(6))
>>> newsvendor_continuous(1, 0.1765, demand_distrib)
(295.6266448071368, 29.44254351324322)
newsvendor_discrete(holding_cost, stockout_cost, demand_distrib=None, demand_pmf=None, base_stock_level=None)[source]

Solve the newsvendor problem with generic discrete distribution, or (if base_stock_level is supplied) calculate cost of given solution.

Must provide either rv_discrete distribution (in demand_distrib) or demand pmf (in demand_pmf, as a dict).

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

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

  • demand_distrib (rv_discrete, optional) – Demand distribution object.

  • demand_pmf (dict, optional) – Demand pmf, as a dict in which keys are possible demand values and values are their probabilities. Ignored if demand_distrib is not None. [\(f(\cdot)\)]

  • 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) – Cost per period attained by base_stock_level. [\(g^*\)]

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

  • ValueError – If demand_distrib and demand_pdf are both None.

Equations Used:

\[ \begin{align}\begin{aligned}S^* = \text{smallest } S \text{ such that } F(S) \ge \frac{p}{h+p}\\g(S) = h\bar{n}(S^*) + pn(S)\end{aligned}\end{align} \]

where \(F(\cdot)\), \(n(\cdot)\), and \(\bar{n}(\cdot)\) are the demand cdf, loss function, and complementary loss function, respectively.

Example (Example 4.7):

>>> from scipy.stats import poisson
>>> demand_distrib = poisson(6)
>>> newsvendor_discrete(1, 4, demand_distrib)
(8.0, 3.5701069457709416)
>>> newsvendor_discrete(1, 4, demand_distrib, base_stock_level=5)
(5, 6.590296024616343)
>>> from scipy.stats import poisson
>>> d = range(0, 41)
>>> f = [poisson.pmf(d_val, 6) for d_val in d]
>>> demand_pmf = dict(zip(d, f))
>>> newsvendor_discrete(1, 4, demand_pmf=demand_pmf)
(8, 3.570106945770941)
myopic(holding_cost, stockout_cost, purchase_cost, purchase_cost_next_per, demand_mean, demand_sd, discount_factor=1.0, base_stock_level=None)[source]

Find the optimizer of the myopic cost function, or (if base_stock_level is supplied) calculate the cost of given solution. Assumes demand is normally distributed.

The myopic cost function is denoted \(G_i(y)\) in Veinott (1966) and as \(C^+(t,y)\) in Zipkin (2000). It is not used in FoSCT, but the function is given in terms of Snyder-Shen notation below.

Parameters are singleton values for the current period, not arrays.

Parameters
  • holding_cost (float) – Holding cost in the current period. [\(h\)]

  • stockout_cost (float) – Stockout cost in the current period. [\(p\)]

  • purchase_cost (float) – Purchase cost in the current period. [\(c\)]

  • purchase_cost_next_per (float) – Purchase cost in the next period. [\(c_{t+1}\)]

  • demand_mean (float) – Mean demand in the current period. [\(\mu\)]

  • demand_sd (float) – Standard deviation of demand in the current period. [\(\sigma\)]

  • discount_factor (float, optional) – Discount factor in the current period, in \((0,1]\). Default = 1. [\(\gamma\)]

  • 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) – The myopic cost attained by base_stock_level. [\(G_t(S^*)\)]

Raises

ValueError – If \(-h_t > c_t - \gamma c_{t+1}\) or \(c_t - \gamma c_{t+1} > p_t\).

Equation Used:

\[ \begin{align}\begin{aligned}S^* = F^{-1}\left(\frac{p - c^+}{p + h}\right)\\c^+ = c_t - \gamma c_{t+1}\\G_t(y) = c_ty + g_t(y) - \gamma_tc_{t+1}(y - E[D_t]),\end{aligned}\end{align} \]

where \(g_t(\cdot)\) is the newsvendor cost function for period \(t\).

References

A. F. Veinott, Jr., On the Optimality of \((s,S)\) Inventory Policies: New Conditions and a New Proof, J. SIAM Appl. Math 14(5), 1067-1083 (1966).

    1. Zipkin, Foundations of Inventory Management, Irwin/McGraw-Hill (2000).

Example (Example 4.1):

>>> myopic(0.18, 0.70, 0.3, 0.35, 50, 8, 0.98)
(58.09891883213067, 16.682411764618777)
>>> myopic(0.18, 0.70, 0.3, 0.35, 50, 8, 0.98, base_stock_level=62)
(62, 16.850319828088736)
myopic_cost(base_stock_level, holding_cost, stockout_cost, purchase_cost, purchase_cost_next_per, demand_mean, demand_sd, discount_factor=1.0)[source]

Calculate “myopic” cost function. Assumes demand is normally distributed.

The myopic cost function is denoted \(G_i(y)\) in Veinott (1966) and as \(C^+(t,y)\) in Zipkin (2000). It is not used in FoSCT, but the function is given in terms of Snyder-Shen notation below.

Parameters are singleton values for the current period, not arrays.

Parameters
  • base_stock_level (float) – Base-stock level to calculate cost for. [\(S\)]

  • holding_cost (float) – Holding cost in the current period. [\(h\)]

  • stockout_cost (float) – Stockout cost in the current period. [\(p\)]

  • purchase_cost (float) – Purchase cost in the current period. [\(c\)]

  • purchase_cost_next_per (float) – Purchase cost in the next period. [\(c_{t+1}\)]

  • demand_mean (float) – Mean demand in the current period. [\(\mu\)]

  • demand_sd (float) – Standard deviation of demand in the current period. [\(\sigma\)]

  • discount_factor (float, optional) – Discount factor in the current period, in \((0,1]\). Default = 1. [\(\gamma\)]

Returns

cost – The myopic cost.

Return type

float

Equation Used:

\[G_t(y) = c_ty + g_t(y) - \gamma_tc_{t+1}(y - E[D_t]),\]

where \(g_t(\cdot)\) is the newsvendor cost function for period \(t\).

References

A. F. Veinott, Jr., On the Optimality of \((s,S)\) Inventory Policies: New Conditions and a New Proof, J. SIAM Appl. Math 14(5), 1067-1083 (1966).

    1. Zipkin, Foundations of Inventory Management, Irwin/McGraw-Hill (2000).

Example (Example 4.1):

>>> myopic_cost(60, 0.18, 0.70, 0.3, 0.35, 50, 8, 0.98)
16.726131552870388
set_myopic_cost_to(cost, holding_cost, stockout_cost, purchase_cost, purchase_cost_next_per, demand_mean, demand_sd, discount_factor=1.0, left_half=True)[source]

Find the value of \(y\) such that \(G_t(y)\) equals cost, where \(G_t(\cdot)\) is the myopic cost function for the current period, given by myopic_cost(). Assumes demand is normally distrbuted.

If left_half is True, requires \(y \le \underline{S}_t\), where \(\underline{S}_t\) is the minimizer of \(G_t(\cdot)\). Otherwise, requires \(S \ge \underline{S}_t\).

Parameters
  • cost (float) – The cost to set myopic_cost() equal to.

  • holding_cost (float) – Holding cost in the current period. [\(h\)]

  • stockout_cost (float) – Stockout cost in the current period. [\(p\)]

  • purchase_cost (float) – Purchase cost in the current period. [\(c\)]

  • purchase_cost_next_per (float) – Purchase cost in the next period. [\(c_{t+1}\)]

  • demand_mean (float) – Mean demand in the current period. [\(\mu\)]

  • demand_sd (float) – Standard deviation of demand in the current period. [\(\sigma\)]

  • discount_factor (float, optional) – Discount factor in the current period, in \((0,1]\). Default = 1. [\(\gamma\)]

  • left_half (bool, optional) – If True, requires \(y \le \underline{S}_t\); otherwise, requires \(y \ge \underline{S}_t\). Default = True.

Returns

base_stock_level – The \(y\) so that myopic_cost(y) equals cost.

Return type

float

Raises
  • ValueError – If \(-h_t > c_t - \gamma c_{t+1}\) or \(c_t - \gamma c_{t+1} > p_t\).

  • ValueError – If cost is less than \(G_t(\underline{S}_t)\).

Example (Example 4.1):

>>> set_myopic_cost_to(18, 0.18, 0.70, 0.3, 0.35, 50, 8, 0.98, left_half=True)
49.394684658734164
>>> myopic_cost(49.394684658734164, 0.18, 0.70, 0.3, 0.35, 50, 8, 0.98)
17.999999999999996
>>> set_myopic_cost_to(18, 0.18, 0.70, 0.3, 0.35, 50, 8, 0.98, left_half=False)
71.84861989932769
>>> myopic_cost(71.84861989932769, 0.18, 0.70, 0.3, 0.35, 50, 8, 0.98)
18.0
newsvendor_normal_explicit(revenue, purchase_cost, salvage_value, demand_mean, demand_sd, holding_cost=0, stockout_cost=0, lead_time=0, base_stock_level=None)[source]

Solve the “explicit”, profit-maximization version of the newsvendor problem with normal distribution, or (if base_stock_level is supplied) calculate profit of given solution.

Assumes salvage_value < purchase_cost < revenue (otherwise the solution is not well-defined).

Parameters
  • revenue (float) – Revenue per unit sold. [\(r\)]

  • purchase_cost (float) – Cost per unit purchased. [\(c\)]

  • salvage_value (float) – Revenue per unit unsold. [\(v\)]

  • demand_mean (float) – Mean demand per period. [\(\mu\)]

  • demand_sd (float) – Standard deviation of demand per period. [\(\sigma\)]

  • holding_cost (float, optional) – Holding cost per item per period, over and above any costs and revenues from buying, selling, or salvaging items. [\(h\)]

  • stockout_cost (float, optional) – Stockout cost per item per period, over and above any costs and revenues from buying, selling, or salvaging items. [\(p\)]

  • lead_time (int, optional) – Lead time. Default = 0. [\(L\)]

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

Returns

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

  • profit (float) – Profit per period attained by base_stock_level. [\(\pi^*\)]

Raises
  • ValueError – If r < c or c < v.

  • ValueError – If holding_cost < 0 or stockout_cost < 0.

  • ValueError – If demand_mean <= 0 or demand_sd <= 0.

Equations Used:

\[ \begin{align}\begin{aligned}S^* = \mu + z_{\alpha}\sigma\\\pi^* = (r-c)\mu - (r-v+h+p)\phi(z_{\alpha})\sigma\\\pi(S) = (r-c+p)S - p\mu + (v-r-h-p)\bar{n}(S),\end{aligned}\end{align} \]

where \(\mu\) and \(\sigma\) are the lead-time demand mean and standard deviation, \(\alpha = (p+r-c)/(h+p+r-v)\), and \(\bar{n}(\cdot)\) is the normal complementary loss function.

Example (Example 4.2):

>>> newsvendor_normal_explicit(1, 0.3, 0.12, 50, 8)
(56.60395592743389, 33.002394806823354)
newsvendor_poisson_explicit(revenue, purchase_cost, salvage_value, demand_mean, holding_cost=0, stockout_cost=0, lead_time=0, base_stock_level=None)[source]

Solve the “explicit”, profit-maximization version of the newsvendor problem with Poisson distribution, or (if base_stock_level is supplied) calculate profit of given solution.

Assumes salvage_value < purchase_cost < revenue (otherwise the solution is not well-defined).

Parameters
  • revenue (float) – Revenue per unit sold. [\(r\)]

  • purchase_cost (float) – Cost per unit purchased. [\(c\)]

  • salvage_value (float) – Revenue per unit unsold. [\(v\)]

  • demand_mean (float) – Mean demand per period. [\(\mu\)]

  • holding_cost (float, optional) – Holding cost per item per period, over and above any costs and revenues from buying, selling, or salvaging items. [\(h\)]

  • stockout_cost (float, optional) – Stockout cost per item per period, over and above any costs and revenues from buying, selling, or salvaging items. [\(p\)]

  • lead_time (int, optional) – Lead time. Default = 0. [\(L\)]

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

Returns

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

  • profit (float) – Profit per period attained by base_stock_level. [\(\pi^*\)]

Raises
  • ValueError – If r < c or c < v.

  • ValueError – If holding_cost < 0 or stockout_cost < 0.

Equations Used:

\[ \begin{align}\begin{aligned}S^* = \text{smallest } S \text{ such that } F(S) \ge \frac{p+r-c}{h+p+r-v}\\\pi(S) = (r-c+p)S - p\mu + (v-r-h-p)\bar{n}(S),\end{aligned}\end{align} \]

where \(\mu\) is the lead-time demand mean and \(\bar{n}(\cdot)\) is the Poisson complementary loss function.

Example (Example 4.2 but with Poisson demand):

>>> newsvendor_poisson_explicit(1, 0.3, 0.12, 50)
(56.60395592743389, 33.002394806823354)