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 orstockout_cost
<= 0.ValueError – If
demand_mean
<= 0 ordemand_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 orstockout_cost
<= 0.ValueError – If
demand_mean
<= 0 ordemand_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 orstockout_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 orstockout_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 (indemand_distrib
) or demand pdf (indemand_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 notNone
. [\(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 orstockout_cost
<= 0.ValueError – If
demand_distrib
anddemand_pdf
are bothNone
.
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 (indemand_distrib
) or demand pmf (indemand_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 notNone
. [\(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 orstockout_cost
<= 0.ValueError – If
demand_distrib
anddemand_pdf
are bothNone
.
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).
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).
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 bymyopic_cost()
. Assumes demand is normally distrbuted.If
left_half
isTrue
, 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)
equalscost
.- 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
orc
<v
.ValueError – If
holding_cost
< 0 orstockout_cost
< 0.ValueError – If
demand_mean
<= 0 ordemand_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
orc
<v
.ValueError – If
holding_cost
< 0 orstockout_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)