Initial commit
This commit is contained in:
193
skills/simpy/scripts/basic_simulation_template.py
Normal file
193
skills/simpy/scripts/basic_simulation_template.py
Normal file
@@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Basic SimPy Simulation Template
|
||||
|
||||
This template provides a starting point for building SimPy simulations.
|
||||
Customize the process functions and parameters for your specific use case.
|
||||
"""
|
||||
|
||||
import simpy
|
||||
import random
|
||||
|
||||
|
||||
class SimulationConfig:
|
||||
"""Configuration parameters for the simulation."""
|
||||
|
||||
def __init__(self):
|
||||
self.random_seed = 42
|
||||
self.num_resources = 2
|
||||
self.num_processes = 10
|
||||
self.sim_time = 100
|
||||
self.arrival_rate = 5.0 # Average time between arrivals
|
||||
self.service_time_mean = 3.0 # Average service time
|
||||
self.service_time_std = 1.0 # Service time standard deviation
|
||||
|
||||
|
||||
class SimulationStats:
|
||||
"""Collect and report simulation statistics."""
|
||||
|
||||
def __init__(self):
|
||||
self.arrival_times = []
|
||||
self.service_start_times = []
|
||||
self.departure_times = []
|
||||
self.wait_times = []
|
||||
self.service_times = []
|
||||
|
||||
def record_arrival(self, time):
|
||||
self.arrival_times.append(time)
|
||||
|
||||
def record_service_start(self, time):
|
||||
self.service_start_times.append(time)
|
||||
|
||||
def record_departure(self, time):
|
||||
self.departure_times.append(time)
|
||||
|
||||
def record_wait_time(self, wait_time):
|
||||
self.wait_times.append(wait_time)
|
||||
|
||||
def record_service_time(self, service_time):
|
||||
self.service_times.append(service_time)
|
||||
|
||||
def report(self):
|
||||
print("\n" + "=" * 50)
|
||||
print("SIMULATION STATISTICS")
|
||||
print("=" * 50)
|
||||
|
||||
if self.wait_times:
|
||||
print(f"Total customers: {len(self.wait_times)}")
|
||||
print(f"Average wait time: {sum(self.wait_times) / len(self.wait_times):.2f}")
|
||||
print(f"Max wait time: {max(self.wait_times):.2f}")
|
||||
print(f"Min wait time: {min(self.wait_times):.2f}")
|
||||
|
||||
if self.service_times:
|
||||
print(f"Average service time: {sum(self.service_times) / len(self.service_times):.2f}")
|
||||
|
||||
if self.arrival_times and self.departure_times:
|
||||
throughput = len(self.departure_times) / max(self.departure_times)
|
||||
print(f"Throughput: {throughput:.2f} customers/time unit")
|
||||
|
||||
print("=" * 50)
|
||||
|
||||
|
||||
def customer_process(env, name, resource, stats, config):
|
||||
"""
|
||||
Simulate a customer process.
|
||||
|
||||
Args:
|
||||
env: SimPy environment
|
||||
name: Customer identifier
|
||||
resource: Shared resource (e.g., server, machine)
|
||||
stats: Statistics collector
|
||||
config: Simulation configuration
|
||||
"""
|
||||
# Record arrival
|
||||
arrival_time = env.now
|
||||
stats.record_arrival(arrival_time)
|
||||
print(f"{name} arrived at {arrival_time:.2f}")
|
||||
|
||||
# Request resource
|
||||
with resource.request() as request:
|
||||
yield request
|
||||
|
||||
# Record service start and calculate wait time
|
||||
service_start = env.now
|
||||
wait_time = service_start - arrival_time
|
||||
stats.record_service_start(service_start)
|
||||
stats.record_wait_time(wait_time)
|
||||
print(f"{name} started service at {service_start:.2f} (waited {wait_time:.2f})")
|
||||
|
||||
# Service time (normally distributed)
|
||||
service_time = max(0.1, random.gauss(
|
||||
config.service_time_mean,
|
||||
config.service_time_std
|
||||
))
|
||||
stats.record_service_time(service_time)
|
||||
|
||||
yield env.timeout(service_time)
|
||||
|
||||
# Record departure
|
||||
departure_time = env.now
|
||||
stats.record_departure(departure_time)
|
||||
print(f"{name} departed at {departure_time:.2f}")
|
||||
|
||||
|
||||
def customer_generator(env, resource, stats, config):
|
||||
"""
|
||||
Generate customers arriving at random intervals.
|
||||
|
||||
Args:
|
||||
env: SimPy environment
|
||||
resource: Shared resource
|
||||
stats: Statistics collector
|
||||
config: Simulation configuration
|
||||
"""
|
||||
customer_count = 0
|
||||
|
||||
while True:
|
||||
# Wait for next customer arrival (exponential distribution)
|
||||
inter_arrival_time = random.expovariate(1.0 / config.arrival_rate)
|
||||
yield env.timeout(inter_arrival_time)
|
||||
|
||||
# Create new customer process
|
||||
customer_count += 1
|
||||
customer_name = f"Customer {customer_count}"
|
||||
env.process(customer_process(env, customer_name, resource, stats, config))
|
||||
|
||||
|
||||
def run_simulation(config):
|
||||
"""
|
||||
Run the simulation with given configuration.
|
||||
|
||||
Args:
|
||||
config: SimulationConfig object with simulation parameters
|
||||
|
||||
Returns:
|
||||
SimulationStats object with collected statistics
|
||||
"""
|
||||
# Set random seed for reproducibility
|
||||
random.seed(config.random_seed)
|
||||
|
||||
# Create environment
|
||||
env = simpy.Environment()
|
||||
|
||||
# Create shared resource
|
||||
resource = simpy.Resource(env, capacity=config.num_resources)
|
||||
|
||||
# Create statistics collector
|
||||
stats = SimulationStats()
|
||||
|
||||
# Start customer generator
|
||||
env.process(customer_generator(env, resource, stats, config))
|
||||
|
||||
# Run simulation
|
||||
print(f"Starting simulation for {config.sim_time} time units...")
|
||||
print(f"Resources: {config.num_resources}")
|
||||
print(f"Average arrival rate: {config.arrival_rate:.2f}")
|
||||
print(f"Average service time: {config.service_time_mean:.2f}")
|
||||
print("-" * 50)
|
||||
|
||||
env.run(until=config.sim_time)
|
||||
|
||||
return stats
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to run the simulation."""
|
||||
# Create configuration
|
||||
config = SimulationConfig()
|
||||
|
||||
# Customize configuration if needed
|
||||
config.num_resources = 2
|
||||
config.sim_time = 50
|
||||
config.arrival_rate = 2.0
|
||||
config.service_time_mean = 3.0
|
||||
|
||||
# Run simulation
|
||||
stats = run_simulation(config)
|
||||
|
||||
# Report statistics
|
||||
stats.report()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
345
skills/simpy/scripts/resource_monitor.py
Normal file
345
skills/simpy/scripts/resource_monitor.py
Normal file
@@ -0,0 +1,345 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SimPy Resource Monitoring Utilities
|
||||
|
||||
This module provides reusable classes and functions for monitoring
|
||||
SimPy resources during simulation. Includes utilities for tracking
|
||||
queue lengths, utilization, wait times, and generating reports.
|
||||
"""
|
||||
|
||||
import simpy
|
||||
from collections import defaultdict
|
||||
from typing import List, Tuple, Dict, Any
|
||||
|
||||
|
||||
class ResourceMonitor:
|
||||
"""
|
||||
Monitor resource usage with detailed statistics tracking.
|
||||
|
||||
Tracks:
|
||||
- Queue lengths over time
|
||||
- Resource utilization
|
||||
- Wait times for requests
|
||||
- Request and release events
|
||||
"""
|
||||
|
||||
def __init__(self, env: simpy.Environment, resource: simpy.Resource, name: str = "Resource"):
|
||||
"""
|
||||
Initialize the resource monitor.
|
||||
|
||||
Args:
|
||||
env: SimPy environment
|
||||
resource: Resource to monitor
|
||||
name: Name for the resource (for reporting)
|
||||
"""
|
||||
self.env = env
|
||||
self.resource = resource
|
||||
self.name = name
|
||||
|
||||
# Data storage
|
||||
self.queue_data: List[Tuple[float, int]] = [(0, 0)]
|
||||
self.utilization_data: List[Tuple[float, float]] = [(0, 0.0)]
|
||||
self.request_times: Dict[Any, float] = {}
|
||||
self.wait_times: List[float] = []
|
||||
self.events: List[Tuple[float, str, Dict]] = []
|
||||
|
||||
# Patch the resource
|
||||
self._patch_resource()
|
||||
|
||||
def _patch_resource(self):
|
||||
"""Patch resource methods to intercept requests and releases."""
|
||||
original_request = self.resource.request
|
||||
original_release = self.resource.release
|
||||
|
||||
def monitored_request(*args, **kwargs):
|
||||
req = original_request(*args, **kwargs)
|
||||
|
||||
# Record request event
|
||||
queue_length = len(self.resource.queue)
|
||||
utilization = self.resource.count / self.resource.capacity
|
||||
|
||||
self.queue_data.append((self.env.now, queue_length))
|
||||
self.utilization_data.append((self.env.now, utilization))
|
||||
self.events.append((self.env.now, 'request', {
|
||||
'queue_length': queue_length,
|
||||
'utilization': utilization
|
||||
}))
|
||||
|
||||
# Store request time for wait time calculation
|
||||
self.request_times[req] = self.env.now
|
||||
|
||||
# Add callback to record when request is granted
|
||||
def on_granted(event):
|
||||
if req in self.request_times:
|
||||
wait_time = self.env.now - self.request_times[req]
|
||||
self.wait_times.append(wait_time)
|
||||
del self.request_times[req]
|
||||
|
||||
req.callbacks.append(on_granted)
|
||||
return req
|
||||
|
||||
def monitored_release(*args, **kwargs):
|
||||
result = original_release(*args, **kwargs)
|
||||
|
||||
# Record release event
|
||||
queue_length = len(self.resource.queue)
|
||||
utilization = self.resource.count / self.resource.capacity
|
||||
|
||||
self.queue_data.append((self.env.now, queue_length))
|
||||
self.utilization_data.append((self.env.now, utilization))
|
||||
self.events.append((self.env.now, 'release', {
|
||||
'queue_length': queue_length,
|
||||
'utilization': utilization
|
||||
}))
|
||||
|
||||
return result
|
||||
|
||||
self.resource.request = monitored_request
|
||||
self.resource.release = monitored_release
|
||||
|
||||
def average_queue_length(self) -> float:
|
||||
"""Calculate time-weighted average queue length."""
|
||||
if len(self.queue_data) < 2:
|
||||
return 0.0
|
||||
|
||||
total_time = 0.0
|
||||
weighted_sum = 0.0
|
||||
|
||||
for i in range(len(self.queue_data) - 1):
|
||||
time1, length1 = self.queue_data[i]
|
||||
time2, length2 = self.queue_data[i + 1]
|
||||
duration = time2 - time1
|
||||
total_time += duration
|
||||
weighted_sum += length1 * duration
|
||||
|
||||
return weighted_sum / total_time if total_time > 0 else 0.0
|
||||
|
||||
def average_utilization(self) -> float:
|
||||
"""Calculate time-weighted average utilization."""
|
||||
if len(self.utilization_data) < 2:
|
||||
return 0.0
|
||||
|
||||
total_time = 0.0
|
||||
weighted_sum = 0.0
|
||||
|
||||
for i in range(len(self.utilization_data) - 1):
|
||||
time1, util1 = self.utilization_data[i]
|
||||
time2, util2 = self.utilization_data[i + 1]
|
||||
duration = time2 - time1
|
||||
total_time += duration
|
||||
weighted_sum += util1 * duration
|
||||
|
||||
return weighted_sum / total_time if total_time > 0 else 0.0
|
||||
|
||||
def average_wait_time(self) -> float:
|
||||
"""Calculate average wait time for requests."""
|
||||
return sum(self.wait_times) / len(self.wait_times) if self.wait_times else 0.0
|
||||
|
||||
def max_queue_length(self) -> int:
|
||||
"""Get maximum queue length observed."""
|
||||
return max(length for _, length in self.queue_data) if self.queue_data else 0
|
||||
|
||||
def report(self):
|
||||
"""Print detailed statistics report."""
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f"RESOURCE MONITOR REPORT: {self.name}")
|
||||
print(f"{'=' * 60}")
|
||||
print(f"Simulation time: 0.00 to {self.env.now:.2f}")
|
||||
print(f"Capacity: {self.resource.capacity}")
|
||||
print(f"\nUtilization:")
|
||||
print(f" Average: {self.average_utilization():.2%}")
|
||||
print(f" Final: {self.resource.count / self.resource.capacity:.2%}")
|
||||
print(f"\nQueue Statistics:")
|
||||
print(f" Average length: {self.average_queue_length():.2f}")
|
||||
print(f" Max length: {self.max_queue_length()}")
|
||||
print(f" Final length: {len(self.resource.queue)}")
|
||||
print(f"\nWait Time Statistics:")
|
||||
print(f" Total requests: {len(self.wait_times)}")
|
||||
if self.wait_times:
|
||||
print(f" Average wait: {self.average_wait_time():.2f}")
|
||||
print(f" Max wait: {max(self.wait_times):.2f}")
|
||||
print(f" Min wait: {min(self.wait_times):.2f}")
|
||||
print(f"\nEvent Summary:")
|
||||
print(f" Total events: {len(self.events)}")
|
||||
request_count = sum(1 for _, event_type, _ in self.events if event_type == 'request')
|
||||
release_count = sum(1 for _, event_type, _ in self.events if event_type == 'release')
|
||||
print(f" Requests: {request_count}")
|
||||
print(f" Releases: {release_count}")
|
||||
print(f"{'=' * 60}")
|
||||
|
||||
def export_csv(self, filename: str):
|
||||
"""
|
||||
Export monitoring data to CSV file.
|
||||
|
||||
Args:
|
||||
filename: Output CSV filename
|
||||
"""
|
||||
import csv
|
||||
|
||||
with open(filename, 'w', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(['Time', 'Event', 'Queue Length', 'Utilization'])
|
||||
|
||||
for time, event_type, data in self.events:
|
||||
writer.writerow([
|
||||
time,
|
||||
event_type,
|
||||
data['queue_length'],
|
||||
data['utilization']
|
||||
])
|
||||
|
||||
print(f"Data exported to {filename}")
|
||||
|
||||
|
||||
class MultiResourceMonitor:
|
||||
"""Monitor multiple resources simultaneously."""
|
||||
|
||||
def __init__(self, env: simpy.Environment):
|
||||
"""
|
||||
Initialize multi-resource monitor.
|
||||
|
||||
Args:
|
||||
env: SimPy environment
|
||||
"""
|
||||
self.env = env
|
||||
self.monitors: Dict[str, ResourceMonitor] = {}
|
||||
|
||||
def add_resource(self, resource: simpy.Resource, name: str):
|
||||
"""
|
||||
Add a resource to monitor.
|
||||
|
||||
Args:
|
||||
resource: SimPy resource to monitor
|
||||
name: Name for the resource
|
||||
"""
|
||||
monitor = ResourceMonitor(self.env, resource, name)
|
||||
self.monitors[name] = monitor
|
||||
return monitor
|
||||
|
||||
def report_all(self):
|
||||
"""Generate reports for all monitored resources."""
|
||||
for name, monitor in self.monitors.items():
|
||||
monitor.report()
|
||||
|
||||
def summary(self):
|
||||
"""Print summary statistics for all resources."""
|
||||
print(f"\n{'=' * 60}")
|
||||
print("MULTI-RESOURCE SUMMARY")
|
||||
print(f"{'=' * 60}")
|
||||
print(f"{'Resource':<20} {'Avg Util':<12} {'Avg Queue':<12} {'Avg Wait':<12}")
|
||||
print(f"{'-' * 20} {'-' * 12} {'-' * 12} {'-' * 12}")
|
||||
|
||||
for name, monitor in self.monitors.items():
|
||||
print(f"{name:<20} {monitor.average_utilization():<12.2%} "
|
||||
f"{monitor.average_queue_length():<12.2f} "
|
||||
f"{monitor.average_wait_time():<12.2f}")
|
||||
|
||||
print(f"{'=' * 60}")
|
||||
|
||||
|
||||
class ContainerMonitor:
|
||||
"""Monitor Container resources (for tracking level changes)."""
|
||||
|
||||
def __init__(self, env: simpy.Environment, container: simpy.Container, name: str = "Container"):
|
||||
"""
|
||||
Initialize container monitor.
|
||||
|
||||
Args:
|
||||
env: SimPy environment
|
||||
container: Container to monitor
|
||||
name: Name for the container
|
||||
"""
|
||||
self.env = env
|
||||
self.container = container
|
||||
self.name = name
|
||||
self.level_data: List[Tuple[float, float]] = [(0, container.level)]
|
||||
|
||||
self._patch_container()
|
||||
|
||||
def _patch_container(self):
|
||||
"""Patch container methods to track level changes."""
|
||||
original_put = self.container.put
|
||||
original_get = self.container.get
|
||||
|
||||
def monitored_put(amount):
|
||||
result = original_put(amount)
|
||||
|
||||
def on_put(event):
|
||||
self.level_data.append((self.env.now, self.container.level))
|
||||
|
||||
result.callbacks.append(on_put)
|
||||
return result
|
||||
|
||||
def monitored_get(amount):
|
||||
result = original_get(amount)
|
||||
|
||||
def on_get(event):
|
||||
self.level_data.append((self.env.now, self.container.level))
|
||||
|
||||
result.callbacks.append(on_get)
|
||||
return result
|
||||
|
||||
self.container.put = monitored_put
|
||||
self.container.get = monitored_get
|
||||
|
||||
def average_level(self) -> float:
|
||||
"""Calculate time-weighted average level."""
|
||||
if len(self.level_data) < 2:
|
||||
return self.level_data[0][1] if self.level_data else 0.0
|
||||
|
||||
total_time = 0.0
|
||||
weighted_sum = 0.0
|
||||
|
||||
for i in range(len(self.level_data) - 1):
|
||||
time1, level1 = self.level_data[i]
|
||||
time2, level2 = self.level_data[i + 1]
|
||||
duration = time2 - time1
|
||||
total_time += duration
|
||||
weighted_sum += level1 * duration
|
||||
|
||||
return weighted_sum / total_time if total_time > 0 else 0.0
|
||||
|
||||
def report(self):
|
||||
"""Print container statistics."""
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f"CONTAINER MONITOR REPORT: {self.name}")
|
||||
print(f"{'=' * 60}")
|
||||
print(f"Capacity: {self.container.capacity}")
|
||||
print(f"Current level: {self.container.level:.2f}")
|
||||
print(f"Average level: {self.average_level():.2f}")
|
||||
print(f"Utilization: {self.average_level() / self.container.capacity:.2%}")
|
||||
|
||||
if self.level_data:
|
||||
levels = [level for _, level in self.level_data]
|
||||
print(f"Max level: {max(levels):.2f}")
|
||||
print(f"Min level: {min(levels):.2f}")
|
||||
|
||||
print(f"{'=' * 60}")
|
||||
|
||||
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
def example_process(env, name, resource, duration):
|
||||
"""Example process using a resource."""
|
||||
with resource.request() as req:
|
||||
yield req
|
||||
print(f"{name} started at {env.now}")
|
||||
yield env.timeout(duration)
|
||||
print(f"{name} finished at {env.now}")
|
||||
|
||||
# Create environment and resource
|
||||
env = simpy.Environment()
|
||||
resource = simpy.Resource(env, capacity=2)
|
||||
|
||||
# Create monitor
|
||||
monitor = ResourceMonitor(env, resource, "Example Resource")
|
||||
|
||||
# Start processes
|
||||
for i in range(5):
|
||||
env.process(example_process(env, f"Process {i}", resource, 3 + i))
|
||||
|
||||
# Run simulation
|
||||
env.run()
|
||||
|
||||
# Generate report
|
||||
monitor.report()
|
||||
Reference in New Issue
Block a user