Catsy-Simulation / engine /simulation_runner.py
Catsy Deployer
Add reservations and update modal labels
83cd70f
Raw
History Blame Contribute Delete
3.55 kB
import simpy
import simpy.rt
import random
from .cafe_env import CafeEnvironment
import time
def start_simulation(event_queue, config, speed_factor=1.0, sim_state=None):
"""
Runs a standard SimPy Environment and steps it manually, allowing true pausing.
speed_factor = 0.5 means 1 real second = 2 simulation seconds.
"""
env = simpy.Environment()
cafe = CafeEnvironment(env, event_queue, config)
env.process(cafe.run())
env.process(cafe.tick_timer())
if config.get("warmup_time", 0) > 0:
env.process(cafe.warmup_timer())
# 100ms real time tick
tick_real_ms = 100
tick_sim_sec = (tick_real_ms / 1000) / speed_factor
try:
while True:
if sim_state and sim_state.get("paused", False):
time.sleep(0.1)
continue
time.sleep(tick_real_ms / 1000)
env.run(until=env.now + tick_sim_sec)
except Exception as e:
print("Simulation stopped:", e)
def run_batch_simulation(config, replications=50, sim_time_seconds=3600):
"""
Runs headless, fast-forwarded simulations for statistical analysis.
sim_time_seconds defaults to 1 hour (3600s).
"""
global_wait = []
global_cycle = []
global_throughput = []
global_lost = []
global_reservations = []
warmup = config.get("warmup_time", 0)
active_time = sim_time_seconds - warmup
for r in range(replications):
random.seed(r)
# Use standard environment (fast-forward)
env = simpy.Environment()
# event_queue=None means emit() will safely ignore UI events
cafe = CafeEnvironment(env, None, config)
env.process(cafe.run())
if warmup > 0:
env.process(cafe.warmup_timer())
env.run(until=sim_time_seconds)
if cafe.waiting_times:
global_wait.append(sum(cafe.waiting_times) / len(cafe.waiting_times))
if cafe.cycle_times:
global_cycle.append(sum(cafe.cycle_times) / len(cafe.cycle_times))
global_lost.append(cafe.lost_customers)
global_reservations.append(cafe.total_reservations)
if active_time > 0:
throughput_per_sec = cafe.completed_customers / active_time
throughput_per_hour = throughput_per_sec * 3600
global_throughput.append(throughput_per_hour)
# Assuming average ticket size of ₱180.00
avg_ticket_size = 180.0
avg_lost = float(sum(global_lost) / len(global_lost)) if global_lost else 0.0
avg_completed = float(sum(global_throughput) / len(global_throughput)) * (sim_time_seconds / 3600) if global_throughput else 0.0
return {
"avg_wait_time": float(sum(global_wait) / len(global_wait)) if global_wait else 0.0,
"avg_cycle_time": float(sum(global_cycle) / len(global_cycle)) if global_cycle else 0.0,
"throughput_per_hour": float(sum(global_throughput) / len(global_throughput)) if global_throughput else 0.0,
"avg_lost_customers": avg_lost,
"avg_reservations": float(sum(global_reservations) / len(global_reservations)) if global_reservations else 0.0,
"revenue_generated": avg_completed * avg_ticket_size,
"revenue_lost": avg_lost * avg_ticket_size,
"target_arrivals_per_hour": (3600.0 / config.get("avg_arrival_time", 45.0)) if config.get("avg_arrival_time", 45.0) > 0 else 0.0,
"replications": replications
}