# Define cost per unit for storage and backorder penalties
STORAGE_COST_PER_UNIT = 0.5 # Cost incurred for storing each unit of product
BACKORDER_PENALTY_COST_PER_UNIT = 1 # Cost penalty for each unit in backorder (unfulfilled orders)
# Define the number of weeks for the simulation
WEEKS_TO_PLAY = 41 # Total simulation period in weeks
# Set delay in supply chain (in weeks)
QUEUE_DELAY_WEEKS = 2 # Delay time in weeks between order and delivery in supply chain
# Initial values for various stock-related metrics
INITIAL_STOCK = 12 # Starting stock level for each actor
INITIAL_COST = 0 # Initial cost at the beginning of the simulation
INITIAL_CURRENT_ORDERS = 0 # Initial orders at the start of the simulation
# Customer ordering pattern settings
CUSTOMER_INITIAL_ORDERS = 5 # Initial order quantity for customers in early weeks
CUSTOMER_SUBSEQUENT_ORDERS = 9 # Increased order quantity for customers in subsequent weeks
# Target stock level for supply chain actors
TARGET_STOCK = 12 # Desired stock level to balance demand and minimize costs
# Define the Customer class to simulate customer behavior
class Customer:
def __init__(self):
# Initialize the total beer received by the customer
self.totalBeerReceived = 0
return
def RecieveFromRetailer(self, amountReceived):
# Update the total amount of beer received based on supply from the retailer
self.totalBeerReceived += amountReceived
return
def CalculateOrder(self, weekNum):
# Calculate the customer order based on the week number
# Order amount changes after the initial weeks to reflect demand increase
if weekNum <= 5:
result = CUSTOMER_INITIAL_ORDERS
else:
result = CUSTOMER_SUBSEQUENT_ORDERS
return result
def GetBeerReceived(self):
# Retrieve the total amount of beer received by the customer
return self.totalBeerReceived
class SupplyChainQueue:
def __init__(self, queueLength):
# Initialize the supply chain queue with a specified length
# 'queueLength' represents the maximum number of orders that can be held in the queue
self.queueLength = queueLength
self.data = [] # List to store orders in the queue
return
def PushEnvelope(self, numberOfCasesToOrder):
# Attempt to place a new order in the queue
# 'numberOfCasesToOrder' represents the quantity of product ordered
orderSuccessfullyPlaced = False # Track if order was successfully added
if len(self.data) < self.queueLength: # Check if queue has space for the order
self.data.append(numberOfCasesToOrder) # Add order to the queue
orderSuccessfullyPlaced = True # Mark order as successfully placed
return orderSuccessfullyPlaced # Return whether the order was successfully added
def AdvanceQueue(self):
# Move the queue forward by removing the oldest order (FIFO - First In, First Out)
# This simulates the delay in processing orders over time
self.data.pop(0) # Remove the first order in the queue
return
def PopEnvelope(self):
# Retrieve the next order to be delivered from the front of the queue
if len(self.data) >= 1: # Check if there is at least one order in the queue
quantityDelivered = self.data[0] # Get the first order in the queue
self.AdvanceQueue() # Remove it from the queue after delivery
else:
quantityDelivered = 0 # No orders to deliver if the queue is empty
return quantityDelivered # Return the quantity to be delivered
def PrettyPrint(self):
# Print the current state of the queue, mainly for debugging purposes
print(self.data)
return
class SupplyChainActor:
def __init__(self, incomingOrdersQueue, outgoingOrdersQueue, incomingDeliveriesQueue, outgoingDeliveriesQueue):
# Initialize stock, orders, and cost attributes
self.currentStock = INITIAL_STOCK # Initial stock level for the actor
self.currentOrders = INITIAL_CURRENT_ORDERS # Initial orders to be fulfilled
self.costsIncurred = INITIAL_COST # Initial cost at the start of the simulation
# Set up queues for managing orders and deliveries
self.incomingOrdersQueue = incomingOrdersQueue # Queue for orders from downstream actors
self.outgoingOrdersQueue = outgoingOrdersQueue # Queue for orders to upstream actors
self.incomingDeliveriesQueue = incomingDeliveriesQueue # Queue for deliveries from upstream
self.outgoingDeliveriesQueue = outgoingDeliveriesQueue # Queue for deliveries to downstream
self.lastOrderQuantity = 0 # Store the quantity ordered in the last round for tracking purposes
return
def PlaceOutgoingDelivery(self, amountToDeliver):
# Place the calculated delivery quantity in the outgoing deliveries queue
self.outgoingDeliveriesQueue.PushEnvelope(amountToDeliver)
return
def PlaceOutgoingOrder(self, weekNum):
# Place an order based on the week number, using an "anchor and maintain" strategy after an initial period
if weekNum <= 4:
amountToOrder = 4 # Initial equilibrium order amount for the first few weeks
else:
# After initial weeks, determine order based on current orders and stock levels
amountToOrder = 0.5 * self.currentOrders # Order amount scales with outstanding orders
# Adjust order to reach target stock level
if (TARGET_STOCK - self.currentStock) > 0:
amountToOrder += TARGET_STOCK - self.currentStock
# Add the order to the outgoing orders queue
self.outgoingOrdersQueue.PushEnvelope(amountToOrder)
self.lastOrderQuantity = amountToOrder # Track the last order quantity for reference
return
def ReceiveIncomingDelivery(self):
# Receive a delivery from upstream by popping the first item in the incoming deliveries queue
quantityReceived = self.incomingDeliveriesQueue.PopEnvelope()
# Add the received quantity to the current stock
if quantityReceived > 0:
self.currentStock += quantityReceived
return
def ReceiveIncomingOrders(self):
# Receive an order from downstream by popping the first item in the incoming orders queue
thisOrder = self.incomingOrdersQueue.PopEnvelope()
# Add the incoming order to the current orders to be fulfilled
if thisOrder > 0:
self.currentOrders += thisOrder
return
def CalcBeerToDeliver(self):
# Calculate the quantity of beer to deliver based on current stock and orders
deliveryQuantity = 0
# If current stock can fulfill all current orders, deliver the full amount
if self.currentStock >= self.currentOrders:
deliveryQuantity = self.currentOrders
self.currentStock -= deliveryQuantity # Reduce stock by delivered quantity
self.currentOrders -= deliveryQuantity # Reduce outstanding orders accordingly
# If stock is insufficient, deliver as much as possible and backorder the rest
elif self.currentStock > 0 and self.currentStock < self.currentOrders:
deliveryQuantity = self.currentStock # Deliver all available stock
self.currentStock = 0 # Stock becomes zero after delivery
self.currentOrders -= deliveryQuantity # Reduce outstanding orders by delivered amount
return deliveryQuantity
def CalcCostForTurn(self):
# Calculate the costs for the current turn, including storage and backorder penalties
inventoryStorageCost = self.currentStock * STORAGE_COST_PER_UNIT # Cost for holding inventory
backorderPenaltyCost = self.currentOrders * BACKORDER_PENALTY_COST_PER_UNIT # Cost for unfulfilled orders
costsThisTurn = inventoryStorageCost + backorderPenaltyCost # Total cost for this turn
return costsThisTurn
def GetCostIncurred(self):
# Return the total costs incurred by this actor so far
return self.costsIncurred
def GetLastOrderQuantity(self):
# Return the quantity of the last order placed by this actor
return self.lastOrderQuantity
def CalcEffectiveInventory(self):
# Calculate effective inventory as the difference between stock and outstanding orders
# This helps determine if the actor is in surplus or backorder
return (self.currentStock - self.currentOrders)
class Retailer(SupplyChainActor):
def __init__(self, incomingOrdersQueue, outgoingOrdersQueue, incomingDeliveriesQueue, outgoingDeliveriesQueue, theCustomer):
# Initialize the retailer with supply chain queues and a customer instance
# Inherit attributes and methods from the SupplyChainActor superclass
super().__init__(incomingOrdersQueue, outgoingOrdersQueue, incomingDeliveriesQueue, outgoingDeliveriesQueue)
self.customer = theCustomer # Customer instance associated with the retailer
return
def ReceiveIncomingOrderFromCustomer(self, weekNum):
# Add the calculated customer order for the current week to the retailer's orders
# CalculateOrder method from Customer class is used to get the order amount
self.currentOrders += self.customer.CalculateOrder(weekNum)
return
def ShipOutgoingDeliveryToCustomer(self):
# Ship the calculated amount of beer to the customer by calling CalcBeerToDeliver
# RecieveFromRetailer method from Customer class is used to receive the quantity delivered
self.customer.RecieveFromRetailer(self.CalcBeerToDeliver())
return
def TakeTurn(self, weekNum):
# Define the series of actions the retailer performs each turn (weekly):
# 1. Receive new delivery from the wholesaler
# This step also advances the delivery queue by removing the first order in line
self.ReceiveIncomingDelivery()
# 2. Receive new order from the customer
self.ReceiveIncomingOrderFromCustomer(weekNum)
# 3. Calculate and ship the required amount to the customer
# Directly call RecieveFromRetailer with a fixed delivery amount for the initial weeks
if weekNum <= 4:
self.customer.RecieveFromRetailer(4) # Fixed delivery amount in the first weeks
else:
self.customer.RecieveFromRetailer(self.CalcBeerToDeliver()) # Dynamic delivery based on stock
# 4. Place an outgoing order to the wholesaler
self.PlaceOutgoingOrder(weekNum)
# 5. Update the retailer's costs for the current turn
self.costsIncurred += self.CalcCostForTurn()
return
class Wholesaler(SupplyChainActor):
def __init__(self, incomingOrdersQueue, outgoingOrdersQueue, incomingDeliveriesQueue, outgoingDeliveriesQueue):
# Initialize the wholesaler with supply chain queues by inheriting from SupplyChainActor
super().__init__(incomingOrdersQueue, outgoingOrdersQueue, incomingDeliveriesQueue, outgoingDeliveriesQueue)
return
def TakeTurn(self, weekNum):
# Define the steps the wholesaler performs each turn (weekly):
# 1. Receive new delivery from the distributor
# This step also advances the delivery queue by removing the first order in line
self.ReceiveIncomingDelivery()
# 2. Receive new order from the retailer
# This step also advances the orders queue
self.ReceiveIncomingOrders()
# 3. Prepare and place the outgoing delivery to the retailer
# Initially, for the first few weeks, send a fixed amount of 4 units
if weekNum <= 4:
self.PlaceOutgoingDelivery(4) # Fixed delivery amount in the first weeks
else:
# After the initial weeks, calculate the delivery based on current stock and orders
self.PlaceOutgoingDelivery(self.CalcBeerToDeliver())
# 4. Place an order to the upstream actor (such as a distributor)
self.PlaceOutgoingOrder(weekNum)
# 5. Update the wholesaler's costs for the current turn
self.costsIncurred += self.CalcCostForTurn()
return
class Distributor(SupplyChainActor):
def __init__(self, incomingOrdersQueue, outgoingOrdersQueue, incomingDeliveriesQueue, outgoingDeliveriesQueue):
# Initialize the distributor with supply chain queues by inheriting from SupplyChainActor
super().__init__(incomingOrdersQueue, outgoingOrdersQueue, incomingDeliveriesQueue, outgoingDeliveriesQueue)
return
def TakeTurn(self, weekNum):
# Define the steps the distributor performs each turn (weekly):
# 1. Receive new delivery from the factory
# This also advances the delivery queue by removing the first item in line
self.ReceiveIncomingDelivery()
# 2. Receive new order from the wholesaler
# This also advances the orders queue by removing the first item in line
self.ReceiveIncomingOrders()
# 3. Prepare and place the outgoing delivery to the wholesaler
# Initially, for the first few weeks, send a fixed amount of 4 units
if weekNum <= 4:
self.PlaceOutgoingDelivery(4) # Fixed delivery amount in the first weeks
else:
# After the initial weeks, calculate delivery based on current stock and orders
self.PlaceOutgoingDelivery(self.CalcBeerToDeliver())
# 4. Place an order to the upstream actor (e.g., factory) to replenish stock
self.PlaceOutgoingOrder(weekNum)
# 5. Update the distributor’s costs for the current turn
self.costsIncurred += self.CalcCostForTurn()
return
class Factory(SupplyChainActor):
def __init__(self, incomingOrdersQueue, outgoingOrdersQueue, incomingDeliveriesQueue, outgoingDeliveriesQueue, productionDelayWeeks):
# Initialize the factory with supply chain queues and a production delay queue
super().__init__(incomingOrdersQueue, outgoingOrdersQueue, incomingDeliveriesQueue, outgoingDeliveriesQueue)
# Initialize a queue to handle production delays (simulating brewing/production time)
self.BeerProductionDelayQueue = SupplyChainQueue(productionDelayWeeks)
# Assume the factory has initial production runs in progress for stability
# These initial production orders help prevent stockouts at the beginning
self.BeerProductionDelayQueue.PushEnvelope(CUSTOMER_INITIAL_ORDERS)
self.BeerProductionDelayQueue.PushEnvelope(CUSTOMER_INITIAL_ORDERS)
return
def ProduceBeer(self, weekNum):
# Calculate the amount of beer to produce based on the week number
if weekNum <= 4:
amountToOrder = 4 # Fixed initial production amount for the first few weeks
else:
# After initial weeks, production amount scales with current orders and target stock
amountToOrder = 0.5 * self.currentOrders # Produces enough to cover current orders
# Adjust production to maintain target stock level if below target
if (TARGET_STOCK - self.currentStock) > 0:
amountToOrder += TARGET_STOCK - self.currentStock
# Add the production order to the production delay queue (simulates delayed production)
self.BeerProductionDelayQueue.PushEnvelope(amountToOrder)
self.lastOrderQuantity = amountToOrder # Track the last production order quantity
return
def FinishProduction(self):
# Complete a production run and add the produced beer to the current stock
amountProduced = self.BeerProductionDelayQueue.PopEnvelope()
# Add completed production amount to stock if production is finished
if amountProduced > 0:
self.currentStock += amountProduced
return
def TakeTurn(self, weekNum):
# Define the steps the factory performs each turn (weekly):
# 1. Complete previous production runs (if any) and add to stock
self.FinishProduction()
# 2. Receive new orders from the distributor
# Advances the incoming orders queue by removing the first order
self.ReceiveIncomingOrders()
# 3. Prepare and place the outgoing delivery to the distributor
# Initially, send a fixed amount of 4 units in the first few weeks
if weekNum <= 4:
self.PlaceOutgoingDelivery(4)
else:
# After initial weeks, calculate delivery based on current stock and orders
self.PlaceOutgoingDelivery(self.CalcBeerToDeliver())
# 4. Initiate production to fulfill future demand
self.ProduceBeer(weekNum)
# 5. Calculate and update the factory’s costs for the current turn
self.costsIncurred += self.CalcCostForTurn()
return
import plotly.express as px
import plotly.graph_objects as go
class SupplyChainStatistics:
def __init__(self):
# Initialize lists to store time-series data for each actor in the supply chain
# Each list will track metrics over each turn (e.g., week)
# Costs over time for each supply chain actor
self.retailerCostsOverTime = []
self.wholesalerCostsOverTime = []
self.distributorCostsOverTime = []
self.factoryCostsOverTime = []
# Orders over time for each actor
self.retailerOrdersOverTime = []
self.wholesalerOrdersOverTime = []
self.distributorOrdersOverTime = []
self.factoryOrdersOverTime = []
# Effective inventory (current stock - current orders) over time for each actor
self.retailerEffectiveInventoryOverTime = []
self.wholesalerEffectiveInventoryOverTime = []
self.distributorEffectiveInventoryOverTime = []
self.factoryEffectiveInventoryOverTime = []
return
# Methods to record orders each week for each actor
def RecordRetailerOrders(self, retailerOrdersThisWeek):
self.retailerOrdersOverTime.append(retailerOrdersThisWeek)
print('Retailer Order:', self.retailerOrdersOverTime[-1])
return
def RecordWholesalerOrders(self, wholesalerOrdersThisWeek):
self.wholesalerOrdersOverTime.append(wholesalerOrdersThisWeek)
print('Wholesaler Order:', self.wholesalerOrdersOverTime[-1])
return
def RecordDistributorOrders(self, distributorOrdersThisWeek):
self.distributorOrdersOverTime.append(distributorOrdersThisWeek)
print('Distributor Order:', self.distributorOrdersOverTime[-1])
return
def RecordFactoryOrders(self, factoryOrdersThisWeek):
self.factoryOrdersOverTime.append(factoryOrdersThisWeek)
print('Factory Order:', self.factoryOrdersOverTime[-1])
return
# Methods to record costs incurred each week for each actor
def RecordRetailerCost(self, retailerCostsThisWeek):
self.retailerCostsOverTime.append(retailerCostsThisWeek)
print('Retailer Cost:', self.retailerCostsOverTime[-1])
return
def RecordWholesalerCost(self, wholesalerCostsThisWeek):
self.wholesalerCostsOverTime.append(wholesalerCostsThisWeek)
print('Wholesaler Cost:', self.wholesalerCostsOverTime[-1])
return
def RecordDistributorCost(self, distributorCostsThisWeek):
self.distributorCostsOverTime.append(distributorCostsThisWeek)
print('Distributor Cost:', self.distributorCostsOverTime[-1])
return
def RecordFactoryCost(self, factoryCostsThisWeek):
self.factoryCostsOverTime.append(factoryCostsThisWeek)
print('Factory Cost:', self.factoryCostsOverTime[-1])
return
# Methods to record effective inventory each week for each actor
def RecordRetailerEffectiveInventory(self, retailerEffectiveInventoryThisWeek):
self.retailerEffectiveInventoryOverTime.append(retailerEffectiveInventoryThisWeek)
print('Retailer Effective Inventory:', self.retailerEffectiveInventoryOverTime[-1])
return
def RecordWholesalerEffectiveInventory(self, wholesalerEffectiveInventoryThisWeek):
self.wholesalerEffectiveInventoryOverTime.append(wholesalerEffectiveInventoryThisWeek)
print('Wholesaler Effective Inventory:', self.wholesalerEffectiveInventoryOverTime[-1])
return
def RecordDistributorEffectiveInventory(self, distributorEffectiveInventoryThisWeek):
self.distributorEffectiveInventoryOverTime.append(distributorEffectiveInventoryThisWeek)
print('Distributor Effective Inventory:', self.distributorEffectiveInventoryOverTime[-1])
return
def RecordFactoryEffectiveInventory(self, factoryEffectiveInventoryThisWeek):
self.factoryEffectiveInventoryOverTime.append(factoryEffectiveInventoryThisWeek)
print('Factory Effective Inventory:', self.factoryEffectiveInventoryOverTime[-1])
return
# Plotting methods to visualize orders, inventory, and costs over time for each actor
def PlotOrders(self):
# Create a plot to visualize orders placed over time by each actor
fig = go.Figure()
weeks = list(range(0, WEEKS_TO_PLAY + 2))
fig.add_trace(go.Scatter(x=weeks, y=self.retailerOrdersOverTime, mode='lines+markers',
name='Retailer Orders', marker=dict(size=5), marker_color='rgb(215,48,39)'))
fig.add_trace(go.Scatter(x=weeks, y=self.wholesalerOrdersOverTime, mode='lines+markers',
name='Wholesaler Orders', marker=dict(size=5), marker_color='rgb(255,186,0)'))
fig.add_trace(go.Scatter(x=weeks, y=self.distributorOrdersOverTime, mode='lines+markers',
name='Distributor Orders', marker=dict(size=5), marker_color='rgb(126,2,114)'))
fig.add_trace(go.Scatter(x=weeks, y=self.factoryOrdersOverTime, mode='lines+markers',
name='Factory Orders', marker=dict(size=5), marker_color='rgb(69,117,180)'))
fig.update_layout(title_text='*Orders Placed Over Time*', xaxis_title='Weeks', yaxis_title='Orders',
paper_bgcolor='rgba(0,0,0,0)', height=580)
fig.update_xaxes(range=[0, WEEKS_TO_PLAY])
fig.show()
return
def PlotEffectiveInventory(self):
# Create a plot to visualize effective inventory over time for each actor
fig = go.Figure()
weeks = list(range(0, WEEKS_TO_PLAY + 2))
fig.add_trace(go.Scatter(x=weeks, y=self.retailerEffectiveInventoryOverTime, mode='lines+markers',
name='Retailer Inventory', marker=dict(size=5), marker_color='rgb(215,48,39)'))
fig.add_trace(go.Scatter(x=weeks, y=self.wholesalerEffectiveInventoryOverTime, mode='lines+markers',
name='Wholesaler Inventory', marker=dict(size=5), marker_color='rgb(255,186,0)'))
fig.add_trace(go.Scatter(x=weeks, y=self.distributorEffectiveInventoryOverTime, mode='lines+markers',
name='Distributor Inventory', marker=dict(size=5), marker_color='rgb(126,2,114)'))
fig.add_trace(go.Scatter(x=weeks, y=self.factoryEffectiveInventoryOverTime, mode='lines+markers',
name='Factory Inventory', marker=dict(size=5), marker_color='rgb(69,117,180)'))
fig.update_layout(title_text='*Effective Inventory Over Time*', xaxis_title='Weeks', yaxis_title='Effective Inventory',
paper_bgcolor='rgba(0,0,0,0)', height=580)
fig.update_xaxes(range=[0, WEEKS_TO_PLAY])
fig.show()
return
def PlotCosts(self):
# Create a plot to visualize total costs incurred over time by each actor
fig = go.Figure()
weeks = list(range(0, WEEKS_TO_PLAY + 2))
fig.add_trace(go.Scatter(x=weeks, y=self.retailerCostsOverTime, mode='lines+markers',
name='Retailer Total Cost', marker=dict(size=5), marker_color='rgb(215,48,39)'))
fig.add_trace(go.Scatter(x=weeks, y=self.wholesalerCostsOverTime, mode='lines+markers',
name='Wholesaler Total Cost', marker=dict(size=5), marker_color='rgb(255,186,0)'))
fig.add_trace(go.Scatter(x=weeks, y=self.distributorCostsOverTime, mode='lines+markers',
name='Distributor Total Cost', marker=dict(size=5), marker_color='rgb(126,2,114)'))
fig.add_trace(go.Scatter(x=weeks, y=self.factoryCostsOverTime, mode='lines+markers',
name='Factory Total Cost', marker=dict(size=5), marker_color='rgb(69,117,180)'))
fig.update_layout(title_text='*Cost Incurred Over Time*', xaxis_title='Weeks', yaxis_title='Cost ($)',
paper_bgcolor='rgba(0,0,0,0)', height=580)
fig.update_xaxes(range=[0, WEEKS_TO_PLAY])
fig.show()
return
# Initialize queues between each actor in the supply chain
# Top and bottom queues simulate order (top) and delivery (bottom) flows between actors
wholesalerRetailerTopQueue = SupplyChainQueue(QUEUE_DELAY_WEEKS)
wholesalerRetailerBottomQueue = SupplyChainQueue(QUEUE_DELAY_WEEKS)
distributorWholesalerTopQueue = SupplyChainQueue(QUEUE_DELAY_WEEKS)
distributorWholesalerBottomQueue = SupplyChainQueue(QUEUE_DELAY_WEEKS)
factoryDistributorTopQueue = SupplyChainQueue(QUEUE_DELAY_WEEKS)
factoryDistributorBottomQueue = SupplyChainQueue(QUEUE_DELAY_WEEKS)
# Populate queues with initial orders to stabilize early weeks of the game
# Each actor's order and delivery queues are initialized with CUSTOMER_INITIAL_ORDERS
for i in range(0, 2):
wholesalerRetailerTopQueue.PushEnvelope(CUSTOMER_INITIAL_ORDERS)
wholesalerRetailerBottomQueue.PushEnvelope(CUSTOMER_INITIAL_ORDERS)
distributorWholesalerTopQueue.PushEnvelope(CUSTOMER_INITIAL_ORDERS)
distributorWholesalerBottomQueue.PushEnvelope(CUSTOMER_INITIAL_ORDERS)
factoryDistributorTopQueue.PushEnvelope(CUSTOMER_INITIAL_ORDERS)
factoryDistributorBottomQueue.PushEnvelope(CUSTOMER_INITIAL_ORDERS)
# Instantiate the customer
theCustomer = Customer()
# Create Retailer, connected to the customer and wholesaler via queues
myRetailer = Retailer(None, wholesalerRetailerTopQueue, wholesalerRetailerBottomQueue, None, theCustomer)
# Create Wholesaler, connected to the retailer and distributor via queues
myWholesaler = Wholesaler(wholesalerRetailerTopQueue, distributorWholesalerTopQueue,
distributorWholesalerBottomQueue, wholesalerRetailerBottomQueue)
# Create Distributor, connected to the wholesaler and factory via queues
myDistributor = Distributor(distributorWholesalerTopQueue, factoryDistributorTopQueue,
factoryDistributorBottomQueue, distributorWholesalerBottomQueue)
# Create Factory, connected to the distributor via queues, with a production delay
myFactory = Factory(factoryDistributorTopQueue, None, None, factoryDistributorBottomQueue, QUEUE_DELAY_WEEKS)
# Initialize an object to track and record statistics across the simulation
myStats = SupplyChainStatistics()
# Simulation loop: Iterate over each week
for thisWeek in range(0, WEEKS_TO_PLAY):
print("\n", "-" * 49)
print(f" Week {thisWeek}") # Print current week number
print("-" * 49)
# Retailer's turn: process orders, calculate costs, and update inventory and statistics
myRetailer.TakeTurn(thisWeek)
myStats.RecordRetailerCost(myRetailer.GetCostIncurred())
myStats.RecordRetailerOrders(myRetailer.GetLastOrderQuantity())
myStats.RecordRetailerEffectiveInventory(myRetailer.CalcEffectiveInventory())
# Wholesaler's turn: process orders, calculate costs, and update inventory and statistics
myWholesaler.TakeTurn(thisWeek)
myStats.RecordWholesalerCost(myWholesaler.GetCostIncurred())
myStats.RecordWholesalerOrders(myWholesaler.GetLastOrderQuantity())
myStats.RecordWholesalerEffectiveInventory(myWholesaler.CalcEffectiveInventory())
# Distributor's turn: process orders, calculate costs, and update inventory and statistics
myDistributor.TakeTurn(thisWeek)
myStats.RecordDistributorCost(myDistributor.GetCostIncurred())
myStats.RecordDistributorOrders(myDistributor.GetLastOrderQuantity())
myStats.RecordDistributorEffectiveInventory(myDistributor.CalcEffectiveInventory())
# Factory's turn: process orders, produce beer, calculate costs, and update statistics
myFactory.TakeTurn(thisWeek)
myStats.RecordFactoryCost(myFactory.GetCostIncurred())
myStats.RecordFactoryOrders(myFactory.GetLastOrderQuantity())
myStats.RecordFactoryEffectiveInventory(myFactory.CalcEffectiveInventory())
# Output final results after simulation
print("\n--- Final Statistics ----")
print("Beer received by customer: {0}".format(theCustomer.GetBeerReceived()))
# Plot time-series data for orders, inventory, and costs over the weeks of simulation
myStats.PlotOrders()
myStats.PlotEffectiveInventory()
myStats.PlotCosts()
------------------------------------------------- Week 0 ------------------------------------------------- Retailer Cost: 13.5 Retailer Order: 4 Retailer Effective Inventory: 12 Wholesaler Cost: 13.5 Wholesaler Order: 4 Wholesaler Effective Inventory: 12 Distributor Cost: 13.5 Distributor Order: 4 Distributor Effective Inventory: 12 Factory Cost: 13.5 Factory Order: 4 Factory Effective Inventory: 12 ------------------------------------------------- Week 1 ------------------------------------------------- Retailer Cost: 34.5 Retailer Order: 4 Retailer Effective Inventory: 12 Wholesaler Cost: 34.5 Wholesaler Order: 4 Wholesaler Effective Inventory: 12 Distributor Cost: 34.5 Distributor Order: 4 Distributor Effective Inventory: 12 Factory Cost: 34.5 Factory Order: 4 Factory Effective Inventory: 12 ------------------------------------------------- Week 2 ------------------------------------------------- Retailer Cost: 62.5 Retailer Order: 4 Retailer Effective Inventory: 11 Wholesaler Cost: 61.5 Wholesaler Order: 4 Wholesaler Effective Inventory: 12 Distributor Cost: 61.5 Distributor Order: 4 Distributor Effective Inventory: 12 Factory Cost: 61.5 Factory Order: 4 Factory Effective Inventory: 12 ------------------------------------------------- Week 3 ------------------------------------------------- Retailer Cost: 97.5 Retailer Order: 4 Retailer Effective Inventory: 10 Wholesaler Cost: 94.5 Wholesaler Order: 4 Wholesaler Effective Inventory: 12 Distributor Cost: 94.5 Distributor Order: 4 Distributor Effective Inventory: 12 Factory Cost: 94.5 Factory Order: 4 Factory Effective Inventory: 12 ------------------------------------------------- Week 4 ------------------------------------------------- Retailer Cost: 139.5 Retailer Order: 4 Retailer Effective Inventory: 9 Wholesaler Cost: 133.5 Wholesaler Order: 4 Wholesaler Effective Inventory: 12 Distributor Cost: 133.5 Distributor Order: 4 Distributor Effective Inventory: 12 Factory Cost: 133.5 Factory Order: 4 Factory Effective Inventory: 12 ------------------------------------------------- Week 5 ------------------------------------------------- Retailer Cost: 143.5 Retailer Order: 4.0 Retailer Effective Inventory: 8 Wholesaler Cost: 139.5 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 12 Distributor Cost: 139.5 Distributor Order: 0.0 Distributor Effective Inventory: 12 Factory Cost: 139.5 Factory Order: 0.0 Factory Effective Inventory: 12 ------------------------------------------------- Week 6 ------------------------------------------------- Retailer Cost: 145.0 Retailer Order: 9.0 Retailer Effective Inventory: 3 Wholesaler Cost: 145.5 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 12.0 Distributor Cost: 147.5 Distributor Order: 0.0 Distributor Effective Inventory: 16 Factory Cost: 147.5 Factory Order: 0.0 Factory Effective Inventory: 16 ------------------------------------------------- Week 7 ------------------------------------------------- Retailer Cost: 155.0 Retailer Order: 0.0 Retailer Effective Inventory: 20 Wholesaler Cost: 160.0 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 29.0 Distributor Cost: 168.5 Distributor Order: 0.0 Distributor Effective Inventory: 42 Factory Cost: 155.5 Factory Order: 0.0 Factory Effective Inventory: 16 ------------------------------------------------- Week 8 ------------------------------------------------- Retailer Cost: 162.5 Retailer Order: 0.0 Retailer Effective Inventory: 15.0 Wholesaler Cost: 174.5 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 29.0 Distributor Cost: 189.5 Distributor Order: 0.0 Distributor Effective Inventory: 42 Factory Cost: 163.5 Factory Order: 0.0 Factory Effective Inventory: 16 ------------------------------------------------- Week 9 ------------------------------------------------- Retailer Cost: 170.0 Retailer Order: 0.0 Retailer Effective Inventory: 15.0 Wholesaler Cost: 189.0 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 29.0 Distributor Cost: 210.5 Distributor Order: 0.0 Distributor Effective Inventory: 42 Factory Cost: 171.5 Factory Order: 0.0 Factory Effective Inventory: 16 ------------------------------------------------- Week 10 ------------------------------------------------- Retailer Cost: 173.0 Retailer Order: 6.0 Retailer Effective Inventory: 6.0 Wholesaler Cost: 203.5 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 29.0 Distributor Cost: 231.5 Distributor Order: 0.0 Distributor Effective Inventory: 42 Factory Cost: 179.5 Factory Order: 0.0 Factory Effective Inventory: 16 ------------------------------------------------- Week 11 ------------------------------------------------- Retailer Cost: 176.0 Retailer Order: 13.5 Retailer Effective Inventory: -3.0 Wholesaler Cost: 215.0 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 23.0 Distributor Cost: 252.5 Distributor Order: 0.0 Distributor Effective Inventory: 42 Factory Cost: 187.5 Factory Order: 0.0 Factory Effective Inventory: 16 ------------------------------------------------- Week 12 ------------------------------------------------- Retailer Cost: 188.0 Retailer Order: 18.0 Retailer Effective Inventory: -12.0 Wholesaler Cost: 219.75 Wholesaler Order: 2.5 Wholesaler Effective Inventory: 9.5 Distributor Cost: 273.5 Distributor Order: 0.0 Distributor Effective Inventory: 42 Factory Cost: 195.5 Factory Order: 0.0 Factory Effective Inventory: 16 ------------------------------------------------- Week 13 ------------------------------------------------- Retailer Cost: 203.0 Retailer Order: 19.5 Retailer Effective Inventory: -15.0 Wholesaler Cost: 228.25 Wholesaler Order: 16.25 Wholesaler Effective Inventory: -8.5 Distributor Cost: 293.25 Distributor Order: 0.0 Distributor Effective Inventory: 39.5 Factory Cost: 203.5 Factory Order: 0.0 Factory Effective Inventory: 16 ------------------------------------------------- Week 14 ------------------------------------------------- Retailer Cost: 213.5 Retailer Order: 17.25 Retailer Effective Inventory: -10.5 Wholesaler Cost: 256.25 Wholesaler Order: 26.0 Wholesaler Effective Inventory: -28.0 Distributor Cost: 304.875 Distributor Order: 0.0 Distributor Effective Inventory: 23.25 Factory Cost: 211.5 Factory Order: 0.0 Factory Effective Inventory: 16 ------------------------------------------------- Week 15 ------------------------------------------------- Retailer Cost: 223.5 Retailer Order: 17.0 Retailer Effective Inventory: -10.0 Wholesaler Cost: 299.0 Wholesaler Order: 33.375 Wholesaler Effective Inventory: -42.75 Distributor Cost: 307.625 Distributor Order: 13.375 Distributor Effective Inventory: -2.75 Factory Cost: 219.5 Factory Order: 0.0 Factory Effective Inventory: 16 ------------------------------------------------- Week 16 ------------------------------------------------- Retailer Cost: 242.5 Retailer Order: 21.5 Retailer Effective Inventory: -19.0 Wholesaler Cost: 342.5 Wholesaler Order: 33.75 Wholesaler Effective Inventory: -43.5 Distributor Cost: 343.75 Distributor Order: 30.0625 Distributor Effective Inventory: -36.125 Factory Cost: 220.8125 Factory Order: 9.375 Factory Effective Inventory: 2.625 ------------------------------------------------- Week 17 ------------------------------------------------- Retailer Cost: 268.0 Retailer Order: 24.75 Retailer Effective Inventory: -25.5 Wholesaler Cost: 384.25 Wholesaler Order: 32.875 Wholesaler Effective Inventory: -41.75 Distributor Cost: 413.625 Distributor Order: 46.9375 Distributor Effective Inventory: -69.875 Factory Cost: 248.25 Factory Order: 25.71875 Factory Effective Inventory: -27.4375 ------------------------------------------------- Week 18 ------------------------------------------------- Retailer Cost: 286.25 Retailer Order: 21.125 Retailer Effective Inventory: -18.25 Wholesaler Cost: 450.75 Wholesaler Order: 45.25 Wholesaler Effective Inventory: -66.5 Distributor Cost: 503.0 Distributor Order: 56.6875 Distributor Effective Inventory: -89.375 Factory Cost: 313.25 Factory Order: 44.5 Factory Effective Inventory: -65.0 ------------------------------------------------- Week 19 ------------------------------------------------- Retailer Cost: 290.25 Retailer Order: 14.0 Retailer Effective Inventory: -4.0 Wholesaler Cost: 538.375 Wholesaler Order: 55.8125 Wholesaler Effective Inventory: -87.625 Distributor Cost: 635.0 Distributor Order: 78.0 Distributor Effective Inventory: -132.0 Factory Cost: 409.21875 Factory Order: 59.984375 Factory Effective Inventory: -95.96875 ------------------------------------------------- Week 20 ------------------------------------------------- Retailer Cost: 303.25 Retailer Order: 18.5 Retailer Effective Inventory: -13.0 Wholesaler Cost: 626.625 Wholesaler Order: 56.125 Wholesaler Effective Inventory: -88.25 Distributor Cost: 813.4375 Distributor Order: 101.21875 Distributor Effective Inventory: -178.4375 Factory Cost: 538.6875 Factory Order: 76.734375 Factory Effective Inventory: -129.46875 ------------------------------------------------- Week 21 ------------------------------------------------- Retailer Cost: 325.25 Retailer Order: 23.0 Retailer Effective Inventory: -22.0 Wholesaler Cost: 730.75 Wholesaler Order: 64.0625 Wholesaler Effective Inventory: -104.125 Distributor Cost: 1022.28125 Distributor Order: 116.421875 Distributor Effective Inventory: -208.84375 Factory Cost: 709.390625 Factory Order: 97.3515625 Factory Effective Inventory: -170.703125 ------------------------------------------------- Week 22 ------------------------------------------------- Retailer Cost: 342.875 Retailer Order: 20.8125 Retailer Effective Inventory: -17.625 Wholesaler Cost: 848.5 Wholesaler Order: 70.875 Wholesaler Effective Inventory: -117.75 Distributor Cost: 1250.6875 Distributor Order: 126.203125 Distributor Effective Inventory: -228.40625 Factory Cost: 919.78125 Factory Order: 117.1953125 Factory Effective Inventory: -210.390625 ------------------------------------------------- Week 23 ------------------------------------------------- Retailer Cost: 366.875 Retailer Order: 24.0 Retailer Effective Inventory: -24.0 Wholesaler Cost: 961.34375 Wholesaler Order: 68.421875 Wholesaler Effective Inventory: -112.84375 Distributor Cost: 1489.984375 Distributor Order: 131.6484375 Distributor Effective Inventory: -239.296875 Factory Cost: 1159.0234375 Factory Order: 131.62109375 Factory Effective Inventory: -239.2421875 ------------------------------------------------- Week 24 ------------------------------------------------- Retailer Cost: 390.5 Retailer Order: 23.8125 Retailer Effective Inventory: -23.625 Wholesaler Cost: 1053.6875 Wholesaler Order: 58.171875 Wholesaler Effective Inventory: -92.34375 Distributor Cost: 1720.96875 Distributor Order: 127.4921875 Distributor Effective Inventory: -230.984375 Factory Cost: 1412.71875 Factory Order: 138.84765625 Factory Effective Inventory: -253.6953125 ------------------------------------------------- Week 25 ------------------------------------------------- Retailer Cost: 397.40625 Retailer Order: 15.453125 Retailer Effective Inventory: -6.90625 Wholesaler Cost: 1109.859375 Wholesaler Order: 40.0859375 Wholesaler Effective Inventory: -56.171875 Distributor Cost: 1912.7734375 Distributor Order: 107.90234375 Distributor Effective Inventory: -191.8046875 Factory Cost: 1662.28515625 Factory Order: 136.783203125 Factory Effective Inventory: -249.56640625 ------------------------------------------------- Week 26 ------------------------------------------------- Retailer Cost: 411.703125 Retailer Order: 0.0 Retailer Effective Inventory: 28.59375 Wholesaler Cost: 1112.4140625 Wholesaler Order: 6.890625 Wholesaler Effective Inventory: 5.109375 Distributor Cost: 2027.46875 Distributor Order: 69.34765625 Distributor Effective Inventory: -114.6953125 Factory Cost: 1880.90625 Factory Order: 121.310546875 Factory Effective Inventory: -218.62109375 ------------------------------------------------- Week 27 ------------------------------------------------- Retailer Cost: 451.4921875 Retailer Order: 0.0 Retailer Effective Inventory: 79.578125 Wholesaler Cost: 1163.64453125 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 102.4609375 Distributor Cost: 2032.486328125 Distributor Order: 1.96484375 Distributor Effective Inventory: 10.03515625 Factory Cost: 2032.091796875 Factory Order: 87.5927734375 Factory Effective Inventory: -151.185546875 ------------------------------------------------- Week 28 ------------------------------------------------- Retailer Cost: 522.59375 Retailer Order: 0.0 Retailer Effective Inventory: 142.203125 Wholesaler Cost: 1273.47265625 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 219.65625 Distributor Cost: 2106.927734375 Distributor Order: 0.0 Distributor Effective Inventory: 148.8828125 Factory Cost: 2063.931640625 Factory Order: 27.919921875 Factory Effective Inventory: -31.83984375 ------------------------------------------------- Week 29 ------------------------------------------------- Retailer Cost: 589.1953125 Retailer Order: 0.0 Retailer Effective Inventory: 133.203125 Wholesaler Cost: 1444.09375 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 341.2421875 Distributor Cost: 2249.7607421875 Distributor Order: 0.0 Distributor Effective Inventory: 285.666015625 Factory Cost: 2091.80810546875 Factory Order: 0.0 Factory Effective Inventory: 55.7529296875 ------------------------------------------------- Week 30 ------------------------------------------------- Retailer Cost: 651.296875 Retailer Order: 0.0 Retailer Effective Inventory: 124.203125 Wholesaler Cost: 1614.71484375 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 341.2421875 Distributor Cost: 2453.2490234375 Distributor Order: 0.0 Distributor Effective Inventory: 406.9765625 Factory Cost: 2133.64453125 Factory Order: 0.0 Factory Effective Inventory: 83.6728515625 ------------------------------------------------- Week 31 ------------------------------------------------- Retailer Cost: 708.8984375 Retailer Order: 0.0 Retailer Effective Inventory: 115.203125 Wholesaler Cost: 1785.3359375 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 341.2421875 Distributor Cost: 2672.6572265625 Distributor Order: 0.0 Distributor Effective Inventory: 438.81640625 Factory Cost: 2175.48095703125 Factory Order: 0.0 Factory Effective Inventory: 83.6728515625 ------------------------------------------------- Week 32 ------------------------------------------------- Retailer Cost: 762.0 Retailer Order: 0.0 Retailer Effective Inventory: 106.203125 Wholesaler Cost: 1955.95703125 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 341.2421875 Distributor Cost: 2892.0654296875 Distributor Order: 0.0 Distributor Effective Inventory: 438.81640625 Factory Cost: 2217.3173828125 Factory Order: 0.0 Factory Effective Inventory: 83.6728515625 ------------------------------------------------- Week 33 ------------------------------------------------- Retailer Cost: 810.6015625 Retailer Order: 0.0 Retailer Effective Inventory: 97.203125 Wholesaler Cost: 2126.578125 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 341.2421875 Distributor Cost: 3111.4736328125 Distributor Order: 0.0 Distributor Effective Inventory: 438.81640625 Factory Cost: 2259.15380859375 Factory Order: 0.0 Factory Effective Inventory: 83.6728515625 ------------------------------------------------- Week 34 ------------------------------------------------- Retailer Cost: 854.703125 Retailer Order: 0.0 Retailer Effective Inventory: 88.203125 Wholesaler Cost: 2297.19921875 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 341.2421875 Distributor Cost: 3330.8818359375 Distributor Order: 0.0 Distributor Effective Inventory: 438.81640625 Factory Cost: 2300.990234375 Factory Order: 0.0 Factory Effective Inventory: 83.6728515625 ------------------------------------------------- Week 35 ------------------------------------------------- Retailer Cost: 894.3046875 Retailer Order: 0.0 Retailer Effective Inventory: 79.203125 Wholesaler Cost: 2467.8203125 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 341.2421875 Distributor Cost: 3550.2900390625 Distributor Order: 0.0 Distributor Effective Inventory: 438.81640625 Factory Cost: 2342.82666015625 Factory Order: 0.0 Factory Effective Inventory: 83.6728515625 ------------------------------------------------- Week 36 ------------------------------------------------- Retailer Cost: 929.40625 Retailer Order: 0.0 Retailer Effective Inventory: 70.203125 Wholesaler Cost: 2638.44140625 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 341.2421875 Distributor Cost: 3769.6982421875 Distributor Order: 0.0 Distributor Effective Inventory: 438.81640625 Factory Cost: 2384.6630859375 Factory Order: 0.0 Factory Effective Inventory: 83.6728515625 ------------------------------------------------- Week 37 ------------------------------------------------- Retailer Cost: 960.0078125 Retailer Order: 0.0 Retailer Effective Inventory: 61.203125 Wholesaler Cost: 2809.0625 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 341.2421875 Distributor Cost: 3989.1064453125 Distributor Order: 0.0 Distributor Effective Inventory: 438.81640625 Factory Cost: 2426.49951171875 Factory Order: 0.0 Factory Effective Inventory: 83.6728515625 ------------------------------------------------- Week 38 ------------------------------------------------- Retailer Cost: 986.109375 Retailer Order: 0.0 Retailer Effective Inventory: 52.203125 Wholesaler Cost: 2979.68359375 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 341.2421875 Distributor Cost: 4208.5146484375 Distributor Order: 0.0 Distributor Effective Inventory: 438.81640625 Factory Cost: 2468.3359375 Factory Order: 0.0 Factory Effective Inventory: 83.6728515625 ------------------------------------------------- Week 39 ------------------------------------------------- Retailer Cost: 1007.7109375 Retailer Order: 0.0 Retailer Effective Inventory: 43.203125 Wholesaler Cost: 3150.3046875 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 341.2421875 Distributor Cost: 4427.9228515625 Distributor Order: 0.0 Distributor Effective Inventory: 438.81640625 Factory Cost: 2510.17236328125 Factory Order: 0.0 Factory Effective Inventory: 83.6728515625 ------------------------------------------------- Week 40 ------------------------------------------------- Retailer Cost: 1024.8125 Retailer Order: 0.0 Retailer Effective Inventory: 34.203125 Wholesaler Cost: 3320.92578125 Wholesaler Order: 0.0 Wholesaler Effective Inventory: 341.2421875 Distributor Cost: 4647.3310546875 Distributor Order: 0.0 Distributor Effective Inventory: 438.81640625 Factory Cost: 2552.0087890625 Factory Order: 0.0 Factory Effective Inventory: 83.6728515625 --- Final Statistics ---- Beer received by customer: 365.0