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_quantity is 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_quantity in the chosen region. [\(g^*\)]

Notes:

  1. For each region \(j\), computes the EOQ assuming the unit cost \(c_j\): \(Q^*_j = \sqrt{\frac{2K\lambda}{i c_j}}\).

  2. 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\).

  3. 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_quantity in the chosen region. [\(g^*\)]

Notes:

  1. 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.

  2. 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\)).

  3. 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_quantity and stockout_fraction are 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_quantity and stockout_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_quantity is 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 n is 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 n is included in every order_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)