Simulating Inventory: Events, Areas, and a Model That Reproduces Chapter 8

Manuel D. Rossetti, PhD P.E.

Agenda

  • A third route to the same numbers, and the rule that makes it trustworthy
  • Where the exact route ends, and what a simulated answer costs
  • An \((r, Q)\) system as two events and one decision
  • Time averages accumulated as areas, credited before the state changes
  • The storeroom’s thirteen weeks, as a simulation computes them
  • The model in code, and the transformer at \((8, 7)\) reproduced

Three Routes to One Number

Ten chapters computed the performance of inventory policies by an exact route where the assumptions held, and an approximate one where they did not. Simulation is a third route, and the only one that reaches every case.

The book has simulated once already: the newsvendor’s expected profit was estimated by sampling 100,000 demands at each of five quantities, and every estimate covered the exact value. That agreement was not a curiosity. It was the evidence that the sampling code computed the newsvendor’s cost and not something near it.

A simulation is trusted only after it reproduces a number the book has already derived. Every model in this chapter first reproduces an earlier result, inside its half-width, and only then is one assumption behind that result removed.

A simulation is a program, and a program can be wrong without saying so. An exact formula is checked once, by derivation; a simulation is checked by running it on a case whose answer is known.

Where the Exact Route Ends

Four boundaries are crossed by nothing in the book except simulation:

Lost sales. Every continuous-review formula assumes an unfilled demand waits. Charging a lost sale as a backorder is the usual remedy, and whether it is close for a given item only a model of the lost-sales system can say.

Orders that overtake one another. A random lead time leaves the \((r, Q)\) formulas intact only if orders arrive in the sequence placed. A simulation delivers each order when its own lead time ends.

Job lots at a batching hub. Chapter 10 set policies for customers taking one, two or four units, and nothing exact covers that case.

How wrong METRIC is for this system. METRIC predicted 0.4055 backorders per storeroom and VARI-METRIC 0.4815, 19% apart. Neither says which is closer here; a simulation does.

What a Simulated Answer Costs

Every answer is an estimate. A simulated cost is a sample average with a half-width, and a cost reported without its half-width reports a number that is not there. The half-width shrinks with the square root of the run, so ten times the precision costs a hundred times the run.

Every answer is for one policy. The exact route searched a cost function it could evaluate anywhere. A simulation evaluates one policy per run, with noise, so finding the best is a search over noisy estimates: a harder problem than the one it replaces.

The run is measured in demands, not years. The transformer sees 45 demands a year, so a thousand simulated years is 45,000 demands: a modest sample for a backorder that occurs a few times a year.

None of these costs applies to the formulas, which is why the formulas come first wherever they exist.

Two Events and One Decision

An event is an instant at which the state changes. Between events nothing changes, so \(I\), \(\mathit{IO}\), \(B\), \(\mathit{IN}\) and \(\mathit{IP}\) are step functions, computed only at events. An \((r, Q)\) system with backorders has two:

Event Changes to the state Decision taken Schedules
Demand \(I \leftarrow I - 1\) if \(I > 0\), else \(B \leftarrow B + 1\); \(\mathit{IP} \leftarrow \mathit{IP} - 1\) If \(\mathit{IP} \le r\), order \(Q\): \(\mathit{IO} \leftarrow \mathit{IO} + Q\), \(\mathit{IP} \leftarrow \mathit{IP} + Q\) The next demand; a receipt at \(t + L\) if an order was placed
Receipt \(\mathit{IO} \leftarrow \mathit{IO} - Q\), \(I \leftarrow I + Q\), then fill \(\min(B, I)\) backorders None Nothing

Notice what is missing: placing an order. It never happens at an instant of its own; it happens inside a demand, when the demand lowers the position to \(r\). An order is a decision taken during an event, and what it does is schedule a receipt on the event calendar, the list of future events in time order.

Averages Over Time, Accumulated at Events

Let \(t_0 = 0 < t_1 < t_2 < \cdots\) represent the event times, and \(A_I(t) = \int_0^t I(u)\,du\) the area under the on-hand curve. \(I\) is constant between events, so the area grows by a rectangle at each:

\[ \begin{aligned} A_I(t_n) &= A_I(t_{n-1}) + I(t_{n-1})\,(t_n - t_{n-1}) &&\text{credit the interval just ended}\\ \bar{I} = \frac{A_I(T)}{T}, \quad \bar{B} &= \frac{A_B(T)}{T}, \quad \overline{\mathit{RR}} = \frac{A_{+}(T)}{T} &&\text{areas divided by time}\\ \overline{\mathit{FR}} &= \frac{N_F(T)}{N_D(T)} &&\text{filled over demanded: a count} \end{aligned} \]

where \(A_B\) is the area under \(B(t)\), \(A_{+}\) the time with \(I > 0\), \(N_D\) the demands and \(N_F\) those filled from the shelf on arrival.

The area is updated before the event changes the state, using the value that held during the interval just ended. Updating it afterward credits the interval with a value the system did not hold.

The Storeroom’s Thirteen Weeks

The transformer at the storeroom of chapter 8: \((r, Q) = (4, 5)\), an eight-week lead time, 6 on hand at the start, nothing on order. Ten demands, in weeks 1, 2, 3, 5, 6, 7, 8, 9, 11 and 13, given rather than sampled; the observation closes at week 13.

Week Event \(I\) \(B\) \(\mathit{IP}\) \(A_I\) \(A_B\) \(A_{+}\) Receipts due
0 start 6 0 6 0 0 0 none
1 demand 5 0 5 6 0 1 none
2 demand, order 5 4 0 9 11 0 2 week 10
3 demand 3 0 8 15 0 3 week 10
5 demand 2 0 7 21 0 5 week 10
6 demand 1 0 6 23 0 6 week 10
7 demand 0 0 5 24 0 7 week 10
8 backordered, order 5 0 1 9 24 0 7 weeks 10, 16
9 backordered 0 2 8 24 1 7 weeks 10, 16
10 receipt of 5 3 0 8 24 3 7 week 16
11 demand 2 0 7 27 3 8 week 16
13 demand 1 0 6 31 3 10 week 16

The Averages, and the Check

With \(T = 13\) weeks:

\[ \begin{aligned} \bar{I} &= 31/13 = 2.3846, \qquad \bar{B} = 3/13 = 0.2308 &&\text{areas over time}\\ \overline{\mathit{RR}} &= 10/13 = 0.7692, \qquad \overline{\mathit{FR}} = 8/10 = 0.8000 &&\text{time with stock; demands filled}\\ A_{+}(13) + 3 &= 10 + 3 = 13 &&\textit{check}\text{: empty from week 7 to 10}\\ A_B(13) &= 3 = 2 + 1 \text{ backorder-weeks} &&\textit{check}\text{: weeks 8 and 9 waited 2 and 1} \end{aligned} \]

The second check is Little’s law: the average wait per demand is \(3/10 = 0.3\) weeks either way. That check is worth performing on every event ledger, because an area credited after the state changed fails it at once.

Averaging the rows instead gives \(27/12 = 2.25\) on hand and \(3/12 = 0.25\) backordered. Both are wrong. And the ready rate and fill rate disagree, 0.7692 against 0.8000, although nothing here is unusual: ten demands is a small sample, and their equality is a statement about the long run.

The Model in Code: the Decision

The model is the event table turned into code. The decision is the shortest piece, and it is the policy:

override fun checkPosition() {
    if (inventoryPosition <= reorderPoint) {
        val batches = (reorderPoint - inventoryPosition) / orderQuantity + 1
        placeOrder(batches * orderQuantity)
    }
}

inventoryPosition is \(\mathit{IP} = I + \mathit{IO} - B\); reorderPoint and orderQuantity are \(r\) and \(Q\). The division is between whole numbers, so batches is \(\lfloor (r - \mathit{IP})/Q \rfloor + 1\), the fewest batches that lift the position above \(r\): always one with single-unit demand, possibly more with demand in lots.

Notice what the stock point does not know: how its orders are filled. It hands each order to a supplier whose only obligation is to deliver eventually. Here the supplier waits a constant two months; later it is a random lead time, then another stock point, and the stock point’s own code changes in neither case.

The Demand and the Receipt

val now = minOf(onHand, amount)
if (now > 0) {
    myOnHand.decrement(now.toDouble())
    receiver?.receive(now)
}
val rest = amount - now
// ...
myBackordered.increment(rest.toDouble())
myOwed.enqueue(Owed(rest, receiver, time))
// ...
checkPosition()
fun receiveReplenishment(amount: Int) {
    myOnOrder.decrement(amount.toDouble())
    myOnHand.increment(amount.toDouble())
    fillOwed()
    checkPosition()
    // ...
}

myOnHand, myBackordered and myOnOrder are \(I\), \(B\) and \(\mathit{IO}\), each a KSL TWResponse, so every change closes a rectangle of the area update. myOwed is the backorder queue, filled first come first served by fillOwed. The decision comes last, after the state has changed.

The Transformer at \((8, 7)\), Simulated

\(\lambda = 45\) a year, \(L = 1/6\) year exactly, \(k = \$220\), \(h = \$950\), \(b = \$8{,}550\). Every measure is known exactly, so the simulation has nothing to discover: its purpose is to reproduce what is known. Thirty replications of 1,000 years after a 20-year warm-up, each starting with \(r + Q = 15\) on hand.

Measure Exact Simulated, 95%
\(\bar{I}\), units 4.6617 4.6570 \(\pm\) 0.0107
\(\bar{B}\), units 0.1617 0.1628 \(\pm\) 0.0020
\(\overline{\mathit{RR}}\) 0.8781 0.8779 \(\pm\) 0.0009
\(\overline{\mathit{FR}}\) 0.8781 0.8776 \(\pm\) 0.0010
\(\overline{\mathit{OF}}\), orders a year 6.4286 6.4333 \(\pm\) 0.0105
Total cost $7,225.15 $7,231.50 \(\pm\) 15.42

The check: every interval covers its exact value, and the ready and fill rates agree to the third decimal, as single-unit Poisson demand requires. With several measures at 95% each, one miss would not be alarming; the intervals are not independent, since all come from the same runs.

What We Did Today

A simulation is trusted only after it reproduces a derived number, and then one assumption at a time is removed.

An \((r, Q)\) system is two events, a demand and a receipt; ordering is a decision taken inside a demand, which schedules a receipt.

Averages are areas, credited before the state changes; the ready rate is time-persistent and the fill rate observation-based.

The thirteen-week ledger gives 2.3846 on hand and 0.2308 owed, checked two ways, where row averaging gives two wrong numbers.

The model reproduces chapter 8 at \((8, 7)\) in every measure.

For Next Time

Read before next session: Output Analysis for a Stock Point and Where the (r, Q) Formulas Stop.

Today’s material was When the Formulas Stop, The Inventory Processes as Events and Building the (r, Q) Model.

Next session:

  • Why a stock point never terminates, where to start the run, and how long to run it
  • Replications, half-widths, and the measure that needs the longest run
  • Orders that overtake one another, job lots under \((s, S)\), and demand that is lost

Today’s run used 30 replications of 1,000 years. Next session says why, and when that is not enough.

⌂ Index