eoq Module¶
Overview¶
The eoq module contains code for solving the economic order quantity
(EOQ) 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¶
- economic_order_quantity(fixed_cost, holding_cost, demand_rate, order_quantity=None)[source]¶
Solve the economic order quantity (EOQ) problem, or (if
order_quantityis supplied) calculate cost of given solution.- 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. [\(\lambda\)]
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) (items). [\(Q^*\)]
cost (float) – Cost per unit time attained by
order_quantity. [\(g^*\)]
Equations Used (equations (3.4) and (3.5)):
\[ \begin{align}\begin{aligned}Q^* = \sqrt{\frac{2K\lambda}{h}}\\g^* = \sqrt{2K\lambda h}\end{aligned}\end{align} \]or
\[g(Q) = \frac{K\lambda}{Q} + \frac{hQ}{2}\]Example (Example 3.1):
>>> economic_order_quantity(8, 0.225, 1300) (304.0467800264368, 68.41052550594829)
- economic_order_quantity_with_all_units_discounts(fixed_cost, holding_cost_rate, demand_rate, breakpoints, unit_costs)[source]¶
Solve the economic order quantity (EOQ) problem with all-units quantity discounts.
In the EOQ problem with all-units quantity discounts, the unit cost of all items in an order is discounted based on the order quantity, according to a piecewise constant function defined by the breakpoints and unit costs.
- Parameters
fixed_cost (float) – Fixed cost per order. [\(K\)]
holding_cost_rate (float) – Holding cost rate per item per unit time as a percentage of the purchase cost. [\(i\)]
demand_rate (float) – Demand (items) per unit time. [\(\lambda\)]
breakpoints (list of float) – Breakpoints for quantity discounts, in increasing order starting with 0. [\([b_0, b_1, \ldots, b_n]\) where \(b_0 = 0\)]
unit_costs (list of float) – Unit cost for each discount region. [\([c_0, c_1, \ldots, c_n]\)]
- Returns
order_quantity (float) – Optimal order quantity (items). [\(Q^*\)]
region (int) – The index of the discount region used (i.e., the index \(j\) such that \(b_j \leq Q^* < b_{j+1}\)). [\(j^*\)]
cost (float) – Cost per unit time attained by
order_quantityin the chosen region. [\(g^*\)]
Notes:
For each region \(j\), computes the EOQ assuming the unit cost \(c_j\): \(Q^*_j = \sqrt{\frac{2K\lambda}{i c_j}}\).
Identifies candidates, which include:
Each \(Q^*_j\) that is realizable (i.e., falls within its region’s bounds: \(b_j \leq Q^*_j < b_{j+1}\) for \(j < n\), or \(Q^*_j \geq b_j\) for \(j = n\)).
Each breakpoint \(b_k\) for \(k \geq 1\), evaluated in region \(k\).
Evaluates the total cost at each candidate and selects the one with the lowest cost.
Equations Used (equations (3.18) and (3.19)):
For each region \(j\), the unconstrained EOQ is:
\[Q^*_j = \sqrt{\frac{2K\lambda}{i c_j}}\]The total cost for an order quantity \(Q\) in region \(j\) is:
\[g_j(Q) = c_j \lambda + \frac{K \lambda}{Q} + \frac{i c_j Q}{2}\]Example:
>>> fixed_cost = 200 >>> holding_cost_rate = 0.20 >>> demand_rate = 1000 >>> breakpoints = [0, 200, 500] >>> unit_costs = [500, 475, 450] >>> economic_order_quantity_with_all_units_discounts(fixed_cost, holding_cost_rate, demand_rate, breakpoints, unit_costs) (500, 2, 472900.0)
- economic_order_quantity_with_incremental_discounts(fixed_cost, holding_cost_rate, demand_rate, breakpoints, unit_costs)[source]¶
Solve the economic order quantity (EOQ) problem with incremental quantity discounts.
In the EOQ problem with incremental quantity discounts, the unit cost decreases incrementally for units above each breakpoint. For an order quantity \(Q\) in region \(j\) (i.e., \(b_j \leq Q < b_{j+1}\)), the purchase cost is the sum of the costs of units up to \(b_j\) plus the cost of additional units at \(c_j\).
- Parameters
fixed_cost (float) – Fixed cost per order. [\(K\)]
holding_cost_rate (float) – Holding cost rate per item per unit time as a percentage of the purchase cost. [\(i\)]
demand_rate (float) – Demand (items) per unit time. [\(\lambda\)]
breakpoints (list of float) – Breakpoints for quantity discounts, in increasing order starting with 0. [\([b_0, b_1, \ldots, b_n]\) where \(b_0 = 0\)]
unit_costs (list of float) – Unit cost for incremental units in each discount region. [\([c_0, c_1, \ldots, c_n]\)]
- Returns
order_quantity (float) – Optimal order quantity (items). [\(Q^*\)]
region (int) – The index of the discount region used (i.e., the index \(j\) such that \(b_j \leq Q^* < b_{j+1}\)). [\(j^*\)]
cost (float) – Cost per unit time attained by
order_quantityin the chosen region. [\(g^*\)]
Notes:
For each region \(j\), computes a modified EOQ based on the incremental cost structure: \(Q^*_j = \sqrt{\frac{2(K + \bar{c}_j)\lambda}{i c_j}}\), where \(\bar{c}_j\) accounts for the fixed cost offset due to incremental discounts.
Checks if each \(Q^*_j\) is realizable (i.e., \(b_j \leq Q^*_j < b_{j+1}\) for \(j < n\), or \(Q^*_j \geq b_j\) for \(j = n\)).
Among realizable \(Q^*_j\), selects the one with the lowest total cost.
Equations Used (equations (3.20), (3.21) and (3.22)):
For \(Q\) in region \(j\), the purchase cost is:
\[C(Q) = \sum_{i=0}^{j-1} c_i (b_{i+1} - b_i) + c_j (Q - b_j) = \bar{c}_j + c_j Q\]where:
\[\bar{c}_j = \sum_{i=0}^{j-1} c_i (b_{i+1} - b_i) - c_j b_j \text{ if } j > 0, \text{ else } 0\]The unconstrained EOQ for region \(j\) is:
\[Q^*_j = \sqrt{\frac{2(K + \bar{c}_j)\lambda}{i c_j}}\]The total cost is:
\[g_j\left(Q_j^*\right) = c_j \lambda + \frac{i \bar{c}_j}{2} + \sqrt{2\left(K+\bar{c}_j\right) \lambda i c_j}\]or
\[g_j(Q) = c_j \lambda + \frac{i \bar{c}_j}{2} + \frac{(K + \bar{c}_j)\lambda}{Q} + \frac{i c_j Q}{2}\]Example:
>>> fixed_cost = 150 >>> holding_cost_rate = 0.25 >>> demand_rate = 2400 >>> breakpoints = [0, 300, 600] >>> unit_costs = [100, 90, 80] >>> economic_order_quantity_with_incremental_discounts(fixed_cost, holding_cost_rate, demand_rate, breakpoints, unit_costs) (1481.8906842274164, 2, 222762.8136845483)
- economic_order_quantity_with_backorders(fixed_cost, holding_cost, stockout_cost, demand_rate, order_quantity=None, stockout_fraction=None)[source]¶
Solve the economic order quantity with backorders (EOQB) problem, or (if
order_quantityandstockout_fractionare supplied) calculate cost of given solution.- 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 per unit time. [\(p\)]
demand_rate (float) – Demand (items) per unit time. [\(\lambda\)]
order_quantity (float, optional) – Order quantity for cost evaluation. If supplied, no optimization will be performed. [\(Q\)]
stockout_fraction (float, optional) – Stockout fraction for cost evaluation. If supplied, no optimization will be performed. [\(x\)]
- Returns
order_quantity (float) – Optimal order quantity (or order quantity supplied) (items). [\(Q^*\)]
stockout_fraction (float) – Optimal stockout fraction (or stockout fraction supplied) (items). [\(x^*\)]
cost (float) – Cost per unit time attained by
order_quantityandstockout_fraction. [\(g^*\)]
Equations Used (equations (3.27)–(3.29)):
\[ \begin{align}\begin{aligned}Q^* = \sqrt{\frac{2K\lambda(h+p)}{hp}}\\x^* = \frac{h}{h+p}\\g^* = \sqrt{\frac{2K\lambda hp}{h+p}}\end{aligned}\end{align} \]or
\[g(Q,x) = \frac{hQ(1-x)^2}{2} + \frac{pQx^2}{2} + \frac{K\lambda}{Q}\]Example (Example 3.8):
>>> economic_order_quantity_with_backorders(8, 0.225, 5, 1300) (310.81255515896464, 0.0430622009569378, 66.92136355097325)
- economic_production_quantity(fixed_cost, holding_cost, demand_rate, production_rate, order_quantity=None)[source]¶
Solve the economic production quantity (EPQ) problem, or (if
order_quantityis supplied) calculate cost of given solution.- 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. [\(\lambda\)]
production_rate (float) – Production quantity (items) per unit time. [\(\mu\)]
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) (items). [\(Q^*\)]
cost (float) – Cost per unit time attained by
order_quantity. [\(g^*\)]
Equations Used (equations (3.31) and (3.32)):
\[ \begin{align}\begin{aligned}Q^* = \sqrt{\frac{2K\lambda}{h(1-\rho)}}\\g^* = \sqrt{2K\lambda h(1-\rho)}\end{aligned}\end{align} \]or
\[g(Q) = \frac{K\lambda}{Q} + \frac{h(1 - \rho)Q}{2}\]where \(\rho = \lambda/\mu\).
Example:
>>> economic_production_quantity(8, 0.225, 1300, 1700) (626.8084945889684, 33.183979125298336)
- joint_replenishment_problem_silver_heuristic(shared_fixed_cost, individual_fixed_costs, holding_costs, demand_rates)[source]¶
Solve the joint replenishment problem (JRP) using Silver’s (1976) heuristic.
- Parameters
shared_fixed_cost (float) – Shared fixed cost per order. [\(K\)]
individual_fixed_costs (list of floats) – Individual fixed cost if product
nis included in order. [\(k_n\)]holding_costs (list of floats) – Holding cost per item per unit time for product
n. [\(h_n\)]demand_rates (list of floats) – Demand (items) per unit time for product
n. [\(\lambda_n\)]
- Returns
order_quantities (list of floats) – Order quantities (items). [\(Q_n\)]
base_cycle_time (float) – Interval between consecutive orders. [\(T\)]
order_multiples (list of ints) – Product
nis included in everyorder_multiples[n]orders. [\(m_n\)]cost (float) – Cost per unit time. [\(g\)]
Equations Used:
\[ \begin{align}\begin{aligned}\hat{n} = n \text{ that minimizes } k_n / h_n\lambda_n\\m_{\hat{n}} = 1\\m_n = \sqrt{\frac{k_n}{h_n\lambda_n} \times \frac{h_{\hat{n}}\lambda_{\hat{n}}}{K+k_{\hat{n}}}} \text{ (rounded)}\\T = \sqrt{\frac{2(K+\sum_{n=1}^N \frac{k_n}{m_n}}{\sum_{n=1}^N h_nm_n\lambda_n}}\\Q_n = Tm_n\lambda_n\\g = \frac{K + \sum_{n=1}^N \frac{k_n}{m_n}}{T} + \frac{T}{2}\sum_{n=1}^N h_nm_n\lambda_n\end{aligned}\end{align} \]Example:
>>> shared_fixed_cost = 600 >>> individual_fixed_costs = [120, 840, 300] >>> holding_costs = [160, 20, 50] >>> demand_rates = [1, 1, 1] >>> joint_replenishment_problem_silver_heuristic(shared_fixed_cost, individual_fixed_costs, holding_costs, demand_rates) ([3.103164454170876, 9.309493362512628, 3.103164454170876], 3.103164454170876, [1, 3, 1], 837.8544026261366)