Inventory Oscillation

Bullwhip-style swings from naive reordering with shipping delay.

Level:Beginner

inventorydelaybullwhip

  • Stocks:inventory
  • Flows:demand, shipments
  • Feedback Loops:delayed reorder overshoot
  • Probes:inventory, pipeline
simulation.py

Inventory oscillation from delayed orders

A store targets a fixed inventory level. When stock falls below that it orders enough to fill the gap. Orders take a few days to arrive and the manager ignores what is already on the way, so shipments pile up and then run out, creating a bullwhip-like cycle.


from tys import probe, progress


def simulate(cfg: dict):

    import simpy
    env = simpy.Environment()

    inventory = cfg["initial_inventory"]
    target = cfg["target_inventory"]
    daily_demand = cfg["daily_demand"]
    lead_time = cfg["lead_time"]
    review_period = cfg["review_period"]
    sim_time = cfg["sim_time"]

    pipeline = []  # list of (arrival_time, qty)

    done = env.event()

    def run():
        nonlocal inventory, pipeline
        for day in range(sim_time):

demand depletes stock

            inventory = max(inventory - daily_demand, 0)

receive any orders that have arrived

            arrivals = [qty for (t, qty) in pipeline if t == day]
            for qty in arrivals:
                inventory += qty
            pipeline = [(t, q) for (t, q) in pipeline if t != day]

periodic review and naive reordering

            if day % review_period == 0:
                order_qty = max(target - inventory, 0)
                if order_qty > 0:
                    pipeline.append((day + lead_time, order_qty))

            probe("inventory", env.now, inventory)
            probe("pipeline", env.now, sum(q for _, q in pipeline))

            progress(int(100 * (day + 1) / sim_time))
            yield env.timeout(1)

        done.succeed({"final_inventory": inventory})

    env.process(run())
    env.run(until=done)
    return done.value


def requirements():
    return {
        "builtin": ["micropip", "pyyaml"],
        "external": ["simpy==4.1.1"],
    }
Default.yaml
initial_inventory: 100
target_inventory: 100
daily_demand: 10
lead_time: 3
review_period: 1
sim_time: 60
Charts (Default)

inventory

inventory chartCSV
Samples60 @ 0.00–59.00
Valuesmin 70.00, mean 95.67, median 95.00, max 130.00, σ 20.03

pipeline

pipeline chartCSV
Samples60 @ 0.00–59.00
Valuesmin 0.00, mean 31.83, median 20.00, max 80.00, σ 31.12
Final Results (Default)
MetricValue
final_inventory80.00