File size: 3,547 Bytes
96027bb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83cd70f
96027bb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83cd70f
96027bb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83cd70f
96027bb
 
d373e76
96027bb
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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
    }