Simpy Smart
Powerful skill for process, based, discrete, event. Includes structured workflows, validation checks, and reusable patterns for scientific.
Simpy Smart
Build and run discrete-event simulations using SimPy, a process-based simulation framework for Python. This skill covers process modeling, resource management, event scheduling, statistics collection, and simulation of real-world systems like manufacturing lines, service queues, and logistics networks.
When to Use This Skill
Choose Simpy Smart when you need to:
- Simulate queuing systems (call centers, hospital ERs, checkout lines)
- Model manufacturing and production line throughput with resource constraints
- Analyze logistics and supply chain scenarios with stochastic demand
- Evaluate system capacity and bottleneck identification
Consider alternatives when:
- You need continuous-time simulation (use scipy.integrate or custom ODE solvers)
- You need agent-based modeling (use Mesa or NetLogo)
- You need Monte Carlo risk analysis without events (use numpy random sampling)
Quick Start
pip install simpy numpy
import simpy import numpy as np def customer(env, name, server, service_time_mean): """A customer arrives, waits for the server, gets served.""" arrival_time = env.now print(f'{name} arrives at {arrival_time:.1f}') with server.request() as req: yield req wait_time = env.now - arrival_time print(f'{name} starts service at {env.now:.1f} (waited {wait_time:.1f})') service_time = np.random.exponential(service_time_mean) yield env.timeout(service_time) print(f'{name} leaves at {env.now:.1f}') def customer_generator(env, server, arrival_rate, service_time_mean): """Generate customers at random intervals.""" i = 0 while True: yield env.timeout(np.random.exponential(1.0 / arrival_rate)) i += 1 env.process(customer(env, f'Customer-{i}', server, service_time_mean)) # Setup and run np.random.seed(42) env = simpy.Environment() server = simpy.Resource(env, capacity=2) # 2 service counters env.process(customer_generator(env, server, arrival_rate=1.5, service_time_mean=1.0)) env.run(until=20)
Core Concepts
SimPy Components
| Component | Class | Purpose |
|---|---|---|
| Environment | simpy.Environment() | Simulation clock and event scheduler |
| Process | env.process(generator) | Active entity in the simulation |
| Resource | simpy.Resource(capacity=N) | Shared resource with queue |
| PriorityResource | simpy.PriorityResource() | Resource with priority queue |
| PreemptiveResource | simpy.PreemptiveResource() | Resource allowing preemption |
| Container | simpy.Container(capacity=N) | Bulk resource (tanks, bins) |
| Store | simpy.Store(capacity=N) | Queue for discrete items |
| FilterStore | simpy.FilterStore() | Store with item selection |
| Event | env.event() | Custom synchronization events |
| Timeout | env.timeout(delay) | Wait for duration |
Manufacturing Line Simulation
import simpy import numpy as np from dataclasses import dataclass, field from typing import List @dataclass class SimStats: """Collect simulation statistics.""" throughput: int = 0 wait_times: List[float] = field(default_factory=list) cycle_times: List[float] = field(default_factory=list) utilization: dict = field(default_factory=dict) def manufacturing_line(env, stats): """Simulate a 3-stage manufacturing line.""" # Define workstations cutting = simpy.Resource(env, capacity=2) assembly = simpy.Resource(env, capacity=3) inspection = simpy.Resource(env, capacity=1) def part(name): start = env.now # Stage 1: Cutting with cutting.request() as req: yield req yield env.timeout(np.random.normal(4.0, 0.5)) # Stage 2: Assembly with assembly.request() as req: t_wait = env.now yield req stats.wait_times.append(env.now - t_wait) yield env.timeout(np.random.normal(6.0, 1.0)) # Stage 3: Inspection (10% fail rate) with inspection.request() as req: yield req yield env.timeout(np.random.normal(2.0, 0.3)) if np.random.random() > 0.10: # 90% pass stats.throughput += 1 stats.cycle_times.append(env.now - start) def part_generator(): i = 0 while True: yield env.timeout(np.random.exponential(2.0)) i += 1 env.process(part(f'Part-{i}')) env.process(part_generator()) # Run simulation np.random.seed(42) env = simpy.Environment() stats = SimStats() manufacturing_line(env, stats) env.run(until=480) # 8-hour shift (480 minutes) print(f"Parts completed: {stats.throughput}") print(f"Avg cycle time: {np.mean(stats.cycle_times):.1f} min") print(f"Avg assembly wait: {np.mean(stats.wait_times):.1f} min")
Configuration
| Parameter | Description | Default |
|---|---|---|
sim_duration | Total simulation time | Problem-specific |
resource_capacity | Number of units in a resource | 1 |
random_seed | Seed for reproducibility | 42 |
warmup_period | Time to discard initial transients | 0 |
num_replications | Number of independent simulation runs | 10 |
arrival_distribution | Inter-arrival time distribution | Exponential |
service_distribution | Service time distribution | Exponential |
priority_levels | Number of priority classes | 1 |
Best Practices
-
Run multiple replications with different seeds — A single simulation run produces a single sample path. Run 10-30 replications with different random seeds and report mean ± confidence interval. This accounts for stochastic variation and gives reliable performance estimates.
-
Include a warmup period to eliminate initialization bias — Simulations start empty, which isn't representative of steady-state. Discard statistics from the first 10-20% of simulation time. Calculate warmup length by plotting a metric over time and identifying when it stabilizes.
-
Validate against analytical solutions for simple cases — Before simulating complex systems, validate your SimPy code against M/M/1 or M/M/c queueing theory results. If the simple case matches theory, you can trust the framework for complex scenarios where analytical solutions don't exist.
-
Use generators for processes, not functions — SimPy processes must be Python generators using
yield. Eachyieldhands control back to the simulation clock. Forgettingyieldbeforeenv.timeout()orresource.request()causes the simulation to skip events entirely without errors. -
Collect statistics in a separate data structure — Don't print or log every event — it slows simulation and creates enormous output. Use a statistics collector (dataclass or dict) and record only the metrics you need: wait times, queue lengths at intervals, and throughput counters.
Common Issues
Simulation finishes instantly with no output — The process generator wasn't started with env.process(). Just defining the generator function doesn't register it. Call env.process(my_process(env, ...)) to schedule it. Also ensure the process contains yield statements — a function without yields runs to completion immediately.
Resource deadlock — processes wait forever — This happens when processes hold one resource while requesting another in circular dependency. Ensure resources are requested and released in a consistent order, or use simpy.PreemptiveResource if priority-based access is needed. Add timeout-based fallbacks to detect deadlocks.
Statistics are biased by startup transients — The first customers experience no queue because the system starts empty. This inflates throughput and deflates wait times. Implement a warmup by collecting statistics only after a warmup period: if env.now > warmup_time: stats.wait_times.append(wait).
Reviews
No reviews yet. Be the first to review this template!
Similar Templates
Full-Stack Code Reviewer
Comprehensive code review skill that checks for security vulnerabilities, performance issues, accessibility, and best practices across frontend and backend code.
Test Suite Generator
Generates comprehensive test suites with unit tests, integration tests, and edge cases. Supports Jest, Vitest, Pytest, and Go testing.
Pro Architecture Workspace
Battle-tested skill for architectural, decision, making, framework. Includes structured workflows, validation checks, and reusable patterns for development.