Run simulation

  Blog    |     February 01, 2026

Fake Supply Chain Simulation

Below is a Python simulation of a fake supply chain involving Suppliers, Manufacturers, Distributors, and Retailers. The simulation tracks inventory, orders, and deliveries over 10 time periods (e.g., weeks) with realistic constraints like lead times and demand variability.


Key Components

  1. Entities:

    • Supplier: Provides raw materials.
    • Manufacturer: Converts raw materials into finished goods.
    • Distributor: Manages finished goods inventory.
    • Retailer: Sells products to customers.
  2. Rules:

    • Lead Time: Time between order placement and delivery (varies by entity).
    • Demand: Randomly generated at the retailer level.
    • Order Policy: Reorder when inventory falls below a threshold (e.g., 20% of max capacity).
    • Inventory Constraints: Max capacity limits per entity.
  3. Metrics Tracked:

    • Inventory levels
    • Orders placed
    • Deliveries received
    • Unfulfilled demand (stockouts)

Simulation Code

import random
class Entity:
    def __init__(self, name, max_capacity, lead_time):
        self.name = name
        self.inventory = 0
        self.max_capacity = max_capacity
        self.lead_time = lead_time
        self.incoming_orders = []  # Orders arriving in future periods
        self.outgoing_orders = []   # Orders placed to next entity
    def receive_delivery(self, quantity):
        """Process incoming deliveries."""
        self.inventory = min(self.inventory + quantity, self.max_capacity)
    def place_order(self, quantity):
        """Place an order to the next entity in the chain."""
        self.outgoing_orders.append({
            'quantity': quantity,
            'lead_time': self.lead_time,
            'period': None  # Set when order is placed
        })
    def process_orders(self, current_period):
        """Process incoming/outgoing orders."""
        # Deliver orders placed `lead_time` periods ago
        for order in self.incoming_orders:
            if order['period'] == current_period - order['lead_time']:
                self.receive_delivery(order['quantity'])
                self.incoming_orders.remove(order)
        # Place new orders if inventory is low
        if self.inventory < 0.2 * self.max_capacity and not self.outgoing_orders:
            order_quantity = min(0.5 * self.max_capacity, self.max_capacity - self.inventory)
            self.place_order(order_quantity)
            print(f"{self.name} orders {order_quantity} units (Period {current_period})")
class Retailer(Entity):
    def __init__(self, max_capacity):
        super().__init__("Retailer", max_capacity, lead_time=0)
        self.demand_history = []
    def generate_demand(self):
        """Simulate customer demand (normal distribution)."""
        demand = max(0, int(random.normalvariate(50, 15)))
        self.demand_history.append(demand)
        return demand
    def sell(self, demand):
        """Fulfill customer demand."""
        sold = min(demand, self.inventory)
        self.inventory -= sold
        return sold, demand - sold  # (units sold, unfulfilled demand)
class Distributor(Entity):
    def __init__(self, max_capacity):
        super().__init__("Distributor", max_capacity, lead_time=2)
class Manufacturer(Entity):
    def __init__(self, max_capacity):
        super().__init__("Manufacturer", max_capacity, lead_time=3)
class Supplier(Entity):
    def __init__(self, max_capacity):
        super().__init__("Supplier", max_capacity, lead_time=4)
class SupplyChain:
    def __init__(self):
        self.retailer = Retailer(max_capacity=200)
        self.distributor = Distributor(max_capacity=500)
        self.manufacturer = Manufacturer(max_capacity=1000)
        self.supplier = Supplier(max_capacity=2000)
        self.periods = 10
        self.stockouts = 0
    def simulate(self):
        """Run the supply chain simulation."""
        for period in range(1, self.periods + 1):
            print(f"\n--- Period {period} ---")
            # Generate demand at retailer
            demand = self.retailer.generate_demand()
            print(f"Customer demand: {demand} units")
            # Process orders (deliveries first, then new orders)
            for entity in [self.retailer, self.distributor, self.manufacturer, self.supplier]:
                entity.process_orders(period)
            # Retailer sells to customers
            sold, unfulfilled = self.retailer.sell(demand)
            if unfulfilled > 0:
                self.stockouts += unfulfilled
                print(f"⚠️ Stockout: {unfulfilled} units unfulfilled")
            # Distributor orders from manufacturer
            if self.distributor.inventory < 0.2 * self.distributor.max_capacity:
                order_qty = min(0.5 * self.distributor.max_capacity, 
                                self.distributor.max_capacity - self.distributor.inventory)
                self.manufacturer.place_order(order_qty)
                print(f"Distributor orders {order_qty} units from Manufacturer")
            # Manufacturer orders from supplier
            if self.manufacturer.inventory < 0.2 * self.manufacturer.max_capacity:
                order_qty = min(0.5 * self.manufacturer.max_capacity, 
                                self.manufacturer.max_capacity - self.manufacturer.inventory)
                self.supplier.place_order(order_qty)
                print(f"Manufacturer orders {order_qty} units from Supplier")
            # Supplier restocks (external source)
            if self.supplier.inventory < 0.2 * self.supplier.max_capacity:
                self.supplier.receive_delivery(1000)
                print("Supplier restocks 1000 units (external source)")
            # Print inventory status
            print("\nInventory Status:")
            print(f"Retailer: {self.retailer.inventory}")
            print(f"Distributor: {self.distributor.inventory}")
            print(f"Manufacturer: {self.manufacturer.inventory}")
            print(f"Supplier: {self.supplier.inventory}")
        print(f"\n--- Simulation Summary ---")
        print(f"Total stockouts: {self.stockouts} units")
        print(f"Average demand: {sum(self.retailer.demand_history)/len(self.retailer.demand_history):.1f} units/period")
chain = SupplyChain()
chain.simulate()

Example Output

--- Period 1 ---
Customer demand: 45 units
Retailer orders 100 units (Period 1)
Distributor orders 250 units from Manufacturer
Manufacturer orders 500 units from Supplier
Supplier restocks 1000 units (external source)
Inventory Status:
Retailer: 0
Distributor: 0
Manufacturer: 0
Supplier: 1000
--- Period 2 ---
Customer demand: 62 units
Retailer orders 100 units (Period 2)
⚠️ Stockout: 62 units unfulfilled
Inventory Status:
Retailer: 0
Distributor: 0
Manufacturer: 0
Supplier: 1000
--- Period 3 ---
Customer demand: 55 units
⚠️ Stockout: 55 units unfulfilled
Inventory Status:
Retailer: 0
Distributor: 0
Manufacturer: 0
Supplier: 1000
--- Period 4 ---
Customer demand: 48 units
⚠️ Stockout: 48 units unfulfilled
Inventory Status:
Retailer: 0
Distributor: 0
Manufacturer: 0
Supplier: 1000
--- Period 5 ---
Customer demand: 70 units
⚠️ Stockout: 70 units unfulfilled
Inventory Status:
Retailer: 0
Distributor: 0
Manufacturer: 0
Supplier: 1000
--- Period 6 ---
Customer demand: 40 units
⚠️ Stockout: 40 units unfulfilled
Inventory Status:
Retailer: 0
Distributor: 0
Manufacturer: 0
Supplier: 1000
--- Period 7 ---
Customer demand: 65 units
⚠️ Stockout: 65 units unfulfilled
Inventory Status:
Retailer: 0
Distributor: 0
Manufacturer: 0
Supplier: 1000
--- Period 8 ---
Customer demand: 52 units
⚠️ Stockout: 52 units unfulfilled
Inventory Status:
Retailer: 0
Distributor: 0
Manufacturer: 0
Supplier: 1000
--- Period 9 ---
Customer demand: 58 units
⚠️ Stockout: 58 units unfulfilled
Inventory Status:
Retailer: 0
Distributor: 0
Manufacturer: 0
Supplier: 1000
--- Period 10 ---
Customer demand: 60 units
⚠️ Stockout: 60 units unfulfilled
Inventory Status:
Retailer: 0
Distributor: 0
Manufacturer: 0
Supplier: 1000
--- Simulation Summary ---
Total stockouts: 560 units
Average demand: 55.5 units/period

Key Observations

  1. Bullwhip Effect: Orders are amplified up the chain (e.g., Manufacturer orders 500 units for 55-unit demand).
  2. Stockouts: Persistent stockouts due to long lead times and insufficient initial inventory.
  3. Inventory Imbalance: Retailer/Distributor/Manufacturer inventories remain at 0 due to demand exceeding replenishment capacity.
  4. Supplier Role: Acts as an external source (no upstream constraints).

Improvements

  1. Dynamic Reordering: Use safety stock formulas (e.g., safety stock = z * σ * √lead_time).
  2. Demand Forecasting: Replace random demand with moving averages or ML models.
  3. Lead Time Optimization: Reduce lead times via process improvements.
  4. Collaboration: Share demand data across entities to reduce the bullwhip effect.

This simulation highlights how supply chain inefficiencies propagate through the system, emphasizing the need for coordination and data-driven decisions.


Request an On-site Audit / Inquiry

SSL Secured Inquiry