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 }