Many Items, Running Systems, and the Tools

Manuel D. Rossetti, PhD P.E.

Agenda

  • Many items under one ordering capacity and one service figure
  • Why equal service is not efficient, shown on the storeroom with no search
  • Where \(\lambda\), \(\theta\) and \(\sigma_{D(L)}\) come from in a running system
  • The workbook: a ledger, an evaluator, and an algorithm with no loop, read on the transformer and used to price a shorter lead time
  • The software: the window as the unit of work, two searches that must agree, and an item the worksheet cannot reach

Many Items at Once

The items do not couple through the physics. Each has its own demand, lead time and lead time demand, and nothing in the exact model refers to another item. They couple through what they share: one storekeeper, one budget, one service figure the organization reports.

A common backorder cost \(b\) per unit per year. Each reorder point meets its own critical ratio:

\[ G_{j}(r_{j}^{*}) = \frac{b}{b + h_{j}} \]

A common stockout cost \(k\) per unit short. The order quantity enters:

\[ G_{j}(r_{j}^{*}) = \frac{k\lambda_{j}}{k\lambda_{j} + h_{j}Q_{j}} \]

Either way the order quantities are the multi-item EOQs, \(Q_{j} = \sqrt{2A\lambda_{j}/h_{j}}\), with \(A\) the shared ordering cost. Notice that the second form depends on \(Q_{j}\) and the first does not.

Two Constraints, Two Loops

When neither the ordering cost nor the shortage cost can be defended, both become constraints:

\[ \min \; \text{inventory investment} \quad\text{subject to}\quad \overline{\mathit{OF}} \le F, \quad \overline{\mathit{FR}} \ge S \]

Search on the two multipliers in turn: adjust \(A\) until the order frequency binds; then, holding \(A\), adjust the shortage cost until service binds.

The order matters. \(A\) sets the \(Q_{j}\), the \(Q_{j}\) appear in the stockout condition, so the reorder points depend on \(A\). The reverse is not true. Settling order frequency first converges; the other order chases itself.

The constraints are checked with the exact measures even though the parameters come from the approximation: choose with the approximation, quote with the exact model, as before.

Equal Service Is Not Efficient

An average service target does not imply an equal one. In Hopp and Spearman’s example the search gives 99.5% to a cheap, fast part and 74.9% to an expensive, slow one, on the way to a 95% average.

Suppose the storeroom adopts the cutout’s \(b = \$287.50\) for every item. What ready rate does that imply for the transformer?

Item \(h_j = 0.25c_j\) \(b/(b+h_j)\)
Pad-mount transformer $950.00 0.2323
Fuse cutout $28.75 0.9091
Smart meter $23.75 0.9237
Riser conduit $15.50 0.9488
Meter socket $12.00 0.9599
Splice kit $7.00 0.9762
Ground rod $3.50 0.9880
Compression connector $0.85 0.9971
Warning tape $0.53 0.9982

From 23.2% to 99.8% across one storeroom, and nothing but the unit cost produced the spread.

The transformer’s own \(b\) is $8,550, thirty times the cutout’s, and gives \(8550/9500 = 0.90\). The shared figure applies the cutout’s penalty to an item worth thirty-three times as much.

A constraint on the average does not claim every item has the same penalty.

Every Parameter Has Arrived as a Given

In a running system none of \(\lambda\), \(L\), \(k\), \(h\), \(b\) is given, and none stands still. Let \(t_{F}\) represent the basic forecast period and \(\hat{D}_{t}\) the forecast of one period’s demand, revised at the end of period \(t\). Then \(\lambda = \hat{D}_{t}/t_{F}\), the rate as of period \(t\).

Let \(\mathit{MAD}_{t}\) represent the smoothed mean absolute deviation of the one period ahead forecast error. Converting it to a standard deviation takes an assumption:

\[ \begin{aligned} E\big[|X - E[X]|\big] &= \sigma\sqrt{2/\pi} &&\text{for a normally distributed error}\\ \sigma_{D} &= \sqrt{\pi/2}\;\mathit{MAD}_{t} \approx 1.2533\,\mathit{MAD}_{t} &&\text{invert} \end{aligned} \]

The normal enters here, and nowhere else

It converts a scale estimate of the forecast error. It is not a statement about the shape of \(D(L)\), which is still chosen by the variance to mean ratio. Confusing the two is how the normal gets into a lead time demand model without anyone deciding to put it there.

Carrying Them Over a Lead Time

Let \(\eta\) represent the scaling exponent, and \(L/t_{F}\) the number of forecast periods in a lead time:

\[ \theta = \frac{\hat{D}_{t}}{t_{F}}L, \qquad \sigma_{D(L)} = \sigma_{D}\left(\frac{L}{t_{F}}\right)^{\eta}, \qquad \tfrac{1}{2} \le \eta \le 1 \]

\(\eta = \tfrac{1}{2}\) is the square-root rule, right when one-period errors are independent. Raise \(\eta\) toward 1 as they become positively autocorrelated, which is what a forecast slow to follow a change in level produces.

A check on the transformer. Monthly forecasts, \(\hat{D}_{t} = 3.75\), \(\mathit{MAD}_{t} = 1.55\):

\[ \begin{aligned} \lambda &= \frac{3.75 \text{ units}}{1 \text{ month}} \times \frac{12 \text{ months}}{1 \text{ year}} = 45 \text{ units per year}\\ \sigma_{D(L)} &= 1.2533(1.55)\sqrt{2} = 1.94\sqrt{2} = 2.75 \text{ units} \end{aligned} \]

against 2.74 from the Poisson. At \(\eta = 1\) it is \(1.94(2) = 3.89\), 42% higher, and the safety stock rises with it.

Which Parameters Move, and How Often

The fixed order cost belongs to the ordering process, not the item; it changes when the process does.

The holding cost follows the item’s value, \(h = ic\), so a repriced item has a new \(h\) whether or not anyone recomputed one.

The lead time varies by item, and its variance can contribute more to \(\mathit{Var}[D(L)]\) than the demand does.

The two parameters do not deserve equal attention. \(Q\) is flat near its optimum, so a \(Q\) from a stale \(\lambda\) costs very little. \(r\) sits in the tail of \(D(L)\), where a small shift in \(\theta\) or \(\sigma_{D(L)}\) moves the fill rate visibly. Recompute \(r\) on the forecast’s own cycle, and \(Q\) when \(\lambda\) has moved enough to matter.

The Workbook: a Ledger That Checks Itself

Chapter8Models.xlsx, seven working sheets. Ledger is the twelve-event ledger with nothing collapsed: three typed columns, and the net inventory, the position and a residual as formulas. The residual is \(\mathit{IP} - (I + \mathit{IO} - B)\), so it reads zero on every row.

Notice how it counts orders. An order is placed exactly when the units on order rise, so the flag is =IF(D10>D9,1,0). Searching the event text for “order” returns three orders instead of two, because “backordered” contains it.

The Algorithm Without a Loop

A worksheet cannot loop, and it does not need to. One row per step, each computed from the row above. A latch turns true once the stopping test fires and stays true:

=OR(G61,MIN(E61,F61)>=D61)
Q   =IF(G62,B61,B61+1)
r   =IF(G62,C61,IF(E61<=F61,C61-1,C61))
C*  =IF(G62,D61,D61-(D61-MIN(E61,F61))/B62)

The third is the cost update. Every row after termination repeats the answer, so the last row is always the result.

Reading the Evaluate Sheet

The workbook ships holding the transformer, so its first use is to confirm it says what the chapter says.

The input block. B5 to B9: \(\lambda = 45\), \(k = \$220\), \(h = \$950\), \(b = \$8{,}550\), \(L = 0.1667\) years. B10 is the one derived input, \(\theta = \lambda L = 7.5\), and everything below reads B10.

The policy block. B13 and B14 hold \(r = 8\), \(Q = 7\), the only cells a reader types. B15 to B17 return \(\bar{B} = 0.1617\), \(\overline{\mathit{FR}} = 0.8781\), \(\bar{I} = 4.6617\).

The check, by hand. From the \(\lambda = 7.5\) column of the second order loss table, \(G^{2}(8) = 1.1369\) and \(G^{2}(15) = 0.0052\):

\[ \bar{B} = \frac{G^{2}(8) - G^{2}(15)}{7} = \frac{1.1369 - 0.0052}{7} = \frac{1.1317}{7} = 0.1617 \text{ units} \]

which is B15. Perform it once on any sheet you did not build: it is the whole backorder formula in two table lookups.

What Is a One-Month Lead Time Worth?

Purchasing believes a framework agreement would cut the transformer’s lead time from two months to one. One cell answers it: set B9 to \(1/12 = 0.0833\) years on Optimize. What happens to each cost term?

Lead time 2 months, \((8, 7)\) 1 month, \((4, 6)\) Change
Ordering $1,414.29 $1,650.00 \(+\$235.71\)
Holding $4,428.59 $3,651.67 \(-\$776.92\)
Backorder $1,382.28 $802.49 \(-\$579.79\)
Total $7,225.15 $6,104.16 \(\mathbf{-\$1{,}120.99}\)

The saving is \(1{,}120.99/7{,}225.15 = 15.5\%\) a year. Holding falls because \(\theta\) halves and \(r\) follows it down; backorders fall because the same exposure is shorter. Ordering rises, because the optimal batch drops from seven to six.

A shorter lead time is not simply less of everything. Pricing it by scaling the current cost misses a term that runs the other way.

The Other Sheets, and What No Sheet Can Do

Periodic needs three Poisson means where the others need one: the start, middle and end of the review interval. Nine columns, combined with Simpson’s weights 1, 4, 1 into \(\bar{G}\) and \(\overline{G^{1}}\). The label beside the mean exposure \(\lambda(L + R/2)\) says it is not \(\lambda\tau\).

Lumpy builds the compound Poisson mass function upward by Panjer’s recursion, with \(\nu = \lambda_{e}L\) the expected number of epochs:

\[ \begin{aligned} g(0) &= e^{-\nu}\\ g(x) &= \frac{\nu}{x}\sum_{y \le x} y\,P\{Y = y\}\,g(x-y) \end{aligned} \]

One term per distinct lot size: three for the cutout. The mass sums to one and returns \(\theta\), and neither is forced.

No sheet for the \((s, S)\) policy. Its position is the stationary vector of a Markov chain, found by iterating until nothing moves, and a sheet cannot iterate without a macro. The approximation is sheetable: Evaluate with \(r = s\), \(Q = S - s\), and the corrected order frequency.

No sheet for the multi-item search. Two nested searches over thousands of items is a program.

Two conventions everywhere. No macros: every number comes from the formula in its own cell. Lead time demand is never modeled with a normal.

The Window Is the Unit of Work

The cost of a policy is \(k\lambda/Q\) plus the average of \(Q\) base-stock costs. Written that way it invites a loop, and a loop makes every policy cost \(Q\) evaluations, which is expensive exactly on the items that need a program.

Both sums have closed forms already in the chapter: the on-hand sum is arithmetic and the backorder sum telescopes into \(G^{2}\).

fun windowTotal(r: Int, q: Int): Double {
    requirePolicy(r, q)
    val width = q.toDouble()
    val shortage = leadTimeDemand.lossSecond(r.toDouble()) -
        leadTimeDemand.lossSecond((r + q).toDouble())
    return fixedCostRate +
        holdingCost * width * (expectedPosition(r, q) - theta) +
        (holdingCost + backorderCost) * shortage
}

Notice that q appears in the arithmetic and not in a loop. shortage is \(G^{2}(r) - G^{2}(r+Q)\), expectedPosition is \(r + (Q+1)/2\), and the function returns \(Q \cdot C(r, Q)\). A step of one thousand costs what a step of one costs.

Two Searches, One Answer

optimizeFromUnitBatch is Algorithm Optimize_rq as printed. optimize is the same algorithm warm started, searched in both directions, stepped by doubling, and finished with an exact per-unit search, because a doubling step can carry \(Q\) past the optimum. That is a claim, so it belongs in a test:

for ((name, model) in items()) {
    val exact = RQOptimizer(model).optimizeFromUnitBatch()
    val fast = RQOptimizer(model).optimize()
    assertEquals(exact.orderQuantity, fast.orderQuantity, "$name: Q")
    assertEquals(exact.reorderPoint, fast.reorderPoint, "$name: r")
    assertEquals(exact.cost, fast.cost, 1e-6, "$name: cost")
}
Item \(r^{*}\) \(Q^{*}\) Optimize_rq Warm started
Transformer 8 7 14 19
Mid volume 196 295 590 75
High volume 1,195 2,842 5,684 74

Where the Conventions Live

Two places in this chapter have a discrete form and a continuous one:

  • the position’s mean, \(r + \tfrac{1}{2}(Q+1)\) on the integers and \(r + Q/2\) on the interval;
  • \(G^{2}\), defined with \((X - b - 1)\) for a discrete \(X\) and with a square for a continuous one.

Each belongs in exactly one method, which asks the distribution which kind it is. Everything else, the cost, the measures and both searches, calls those and never restates the rule.

Write a convention out at each place it is used and one of those places will eventually disagree with the others. Half a unit of stock is \(h/2\) per unit time on every policy the software will ever report, and nothing about the answer looks wrong when it is missing.

The Transformer in Code

val transformer = RQModel(
    demandRate = 45.0,
    orderCost = 220.0,
    holdingCost = RQModel.holdingCostFrom(carryingCharge = 0.25, unitCost = 3800.0),
    backorderCost = 8550.0,
    leadTimeDemand = LeadTimeDemand.matched(mean = 7.5, variance = 7.5),
)
println(transformer.bounds())
println(RQOptimizer(transformer).optimize())
bounds: Q in [5, 11], S* = 11 costing 4859.83, C* in [4114.49, 8823.14]
(r = 8, Q = 7) costing 7225.15, found by warm start in 19 window evaluations

Notice the order of the calls. bounds runs before the search and prints what the answer must satisfy: \(Q^{*}\) in \([5, 11]\) and \(C^{*}\) between $4,114.49 and $8,823.14. Both hold. LeadTimeDemand.matched carries out the family choice by the variance to mean ratio, and holdingCostFrom applies \(h = ic\).

The Same Question in Code

The lead-time question of the worksheet is one line in the program: the lead time demand mean halves, from 7.5 to 3.75.

val shorter = transformer.copy(
    leadTimeDemand = LeadTimeDemand.matched(mean = 3.75, variance = 3.75)
)
println(shorter.bounds())
println(RQOptimizer(shorter).optimize())
bounds: Q in [5, 9], S* = 6 costing 3529.19, C* in [4114.49, 6883.92]
(r = 4, Q = 6) costing 6104.16, found by warm start in 16 window evaluations

Notice that the program and the sheet agree: \((4, 6)\) at $6,104.16. copy changes one field of the RQModel and leaves \(\lambda\), \(k\), \(h\) and \(b\) alone, which is the program’s version of typing one cell. The bounds move too: \(S^{*}\) falls from 11 to 6, and the upper cost bound from $8,823.14 to $6,883.92.

An Item the Worksheet Cannot Reach

The ground rod: \(\lambda = 2{,}400\) a year, \(c = \$14\) so \(h = \$3.50\), \(k = \$41.25\), the shared \(b = \$287.50\), two weeks: \(\theta = 2400/26 = 92.31\).

val rod = RQModel(
    demandRate = 2400.0,
    orderCost = 41.25,
    holdingCost = RQModel.holdingCostFrom(carryingCharge = 0.25, unitCost = 14.0),
    backorderCost = 287.50,
    leadTimeDemand = LeadTimeDemand.matched(mean = 2400.0 / 26.0, variance = 2400.0 / 26.0),
)
println(rod.bounds())
println(RQOptimizer(rod).optimize())
println("Algorithm Optimize_rq from a batch of one takes %d steps"
    .format(RQOptimizer(rod).optimizeFromUnitBatch().orderQuantity - 1))
bounds: Q in [239, 265], S* = 115 costing 90.83, C* in [827.44, 881.79]
(r = 94, Q = 244) costing 859.94, found by jump search in 65 window evaluations
Algorithm Optimize_rq from a batch of one takes 243 steps

The Optimize sheet lays down thirty steps and this item needs 243. The program needs 65 window evaluations. The checks: \(94 < 115 \le 338\), and $859.94 lies in $827.44 to $881.79, on an answer no worksheet produced.

What We Did Today, and in This Chapter

Items couple through what they share, and equal service is the expensive way to meet an average. One shared \(b\) ran the transformer at a 23% ready rate.

Parameters come from the forecast, and the normal enters only to convert a MAD. \(r\) deserves attention every cycle; \(Q\) does not.

The workbook and the code compute what the chapter derived. Halving the transformer’s lead time saves 15.5% and raises the ordering cost; the ground rod needs 243 algorithm steps, which only the program reaches.

The chapter in one line. Everything is a functional of lead time demand; every policy is base stock averaged over a uniform quantity; choose with the approximation and quote with the exact model.

For Next Time

Read before next session: the opening of Multi-Echelon Inventory Systems, Where Should the Stock Sit? and Ordering One Unit at a Time.

Today’s material was Many Items at Once, Updating \(r\) and \(Q\) in a Running System, Building These Models in a Worksheet and Designing the Software.

Next session:

  • A second stock point, and the question of where the stock should sit
  • Repairable items, one-for-one replenishment across two levels
  • Why the base-stock model of this chapter is the building block there too

Bring the base-stock model. The next chapter stacks it.

⌂ Index