SimPy Container Explained: Modelling Levels and Quantities
Not everything is about counting slots. Sometimes you need to model levels—fuel in a tank, items in a buffer, water in a reservoir. That's what Container is for.
Resource vs Container
Resource: Discrete slots. "3 of 5 tills occupied."
Container: Continuous levels. "Tank at 750 of 1000 litres."
import simpy
env = simpy.Environment()
# Resource: counting slots
checkout = simpy.Resource(env, capacity=5)
# Container: measuring levels
fuel_tank = simpy.Container(env, capacity=1000, init=500)
Basic Usage
env = simpy.Environment()
tank = simpy.Container(env, capacity=100, init=50)
print(tank.level) # 50
print(tank.capacity) # 100
Put and Get
Add to or remove from a container:
def producer(env, container):
while True:
yield env.timeout(5)
yield container.put(10) # Add 10 units
print(f"Added 10, level now {container.level}")
def consumer(env, container):
while True:
yield env.timeout(3)
yield container.get(5) # Remove 5 units
print(f"Removed 5, level now {container.level}")
Blocking Behaviour
put() blocks when container is full:
tank = simpy.Container(env, capacity=100, init=95)
yield tank.put(10) # Blocks until there's room for 10
get() blocks when container is empty:
tank = simpy.Container(env, capacity=100, init=3)
yield tank.get(10) # Blocks until there's at least 10
This is the magic—processes automatically wait for the right conditions.
Fuel Station Example
import simpy
import random
def car(env, name, station, tank_size):
"""Car arrives, fills up, leaves."""
fuel_needed = random.uniform(20, 50)
print(f"{name} arrives, needs {fuel_needed:.0f}L at {env.now:.1f}")
yield station.get(fuel_needed) # Pump fuel
print(f"{name} filled up at {env.now:.1f}, station level: {station.level:.0f}")
def tanker(env, station):
"""Tanker periodically refills station."""
while True:
yield env.timeout(50) # Arrive every 50 time units
refill = station.capacity - station.level
yield station.put(refill)
print(f"Tanker delivered {refill:.0f}L at {env.now:.1f}")
def generate_cars(env, station):
"""Generate arriving cars."""
i = 0
while True:
yield env.timeout(random.expovariate(1/5))
env.process(car(env, f"Car{i}", station, 60))
i += 1
env = simpy.Environment()
station = simpy.Container(env, capacity=500, init=300)
env.process(generate_cars(env, station))
env.process(tanker(env, station))
env.run(until=200)
Manufacturing Buffer
def machine(env, name, input_buffer, output_buffer, process_time):
"""Machine takes from input, processes, puts to output."""
while True:
yield input_buffer.get(1) # Take 1 item
yield env.timeout(process_time) # Process
yield output_buffer.put(1) # Output 1 item
print(f"{name} processed at {env.now}")
env = simpy.Environment()
raw_materials = simpy.Container(env, capacity=100, init=50)
finished_goods = simpy.Container(env, capacity=100, init=0)
env.process(machine(env, "Lathe", raw_materials, finished_goods, 5))
env.run(until=100)
Checking Without Blocking
Sometimes you want to check if operation is possible:
def try_get(container, amount):
"""Non-blocking get attempt."""
if container.level >= amount:
yield container.get(amount)
return True
return False
Or use events with timeout:
def consumer(env, container, patience):
get_event = container.get(10)
timeout_event = env.timeout(patience)
result = yield get_event | timeout_event
if get_event in result:
print("Got the items")
else:
get_event.cancel() # Important!
print("Gave up waiting")
Container Properties
container = simpy.Container(env, capacity=100, init=30)
container.level # Current level (30)
container.capacity # Maximum capacity (100)
len(container.put_queue) # Processes waiting to put
len(container.get_queue) # Processes waiting to get
Monitoring Levels
level_log = []
def monitor(env, container, interval):
while True:
level_log.append({
'time': env.now,
'level': container.level
})
yield env.timeout(interval)
env.process(monitor(env, tank, 1))
Visualise after:
import matplotlib.pyplot as plt
import pandas as pd
df = pd.DataFrame(level_log)
plt.plot(df['time'], df['level'])
plt.xlabel('Time')
plt.ylabel('Level')
plt.show()
Partial Operations
Unlike Resources, Containers allow partial operations by design. You're moving quantities, not discrete items.
# Valid: add 5.5 units
yield container.put(5.5)
# Valid: remove 3.7 units
yield container.get(3.7)
Use integers if your domain requires them.
When to Use Container
Use Container for: - Fuel tanks, water reservoirs - Inventory levels - Production buffers - Bank account balances - Any continuous quantity
Use Resource for: - Staff, machines, servers - Discrete slots - Things you "borrow" and "return"
Common Gotchas
Not checking level before get
# This might block forever if level never reaches 10
yield container.get(10)
Consider timeouts or checking level first.
Forgetting initial level
# Default init is 0
tank = simpy.Container(env, capacity=100)
yield tank.get(50) # Blocks! Tank is empty
Put more than capacity
tank = simpy.Container(env, capacity=100, init=50)
yield tank.put(60) # Blocks until level <= 40
Summary
Container:
- Models continuous levels, not discrete slots
- Use put(amount) to add, get(amount) to remove
- Blocks when full (put) or empty (get)
- Tracks .level and .capacity
For tanks, buffers, and inventories—Container is your tool.
Next Steps
Discover the Power of Simulation
Want to become a go-to expert in simulation with Python? The Complete Simulation Bootcamp will show you how simulation can transform your career and your projects.
Explore the Bootcamp