• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Quang00 / qnscheduling / 24717474229

21 Apr 2026 10:29AM UTC coverage: 79.227% (-11.5%) from 90.746%
24717474229

Pull #12

github

web-flow
Merge c738d51e8 into b684bd1ff
Pull Request #12: Dynamic

142 of 305 new or added lines in 4 files covered. (46.56%)

3 existing lines in 2 files now uncovered.

923 of 1165 relevant lines covered (79.23%)

2.38 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.46
/scheduling/simulation.py
1
"""
2
Simulation Of PGAs Scheduling
3
-----------------------------
4
This module provides classes and functions to simulate the scheduling of
5
Packet Generation Attempts (PGAs) in a quantum network. Each PGA tries to
6
generate entangled EPR pairs over a specified route within a defined time
7
window, considering resource availability and link busy times. The function,
8
`simulate_static`, simulates a static schedule of PGAs and returns performance
9
metrics, link utilizations, and other relevant data. While the function
10
`simulate_dynamic` implement a dynamic scheduling approach.
11
"""
12

13
import heapq
3✔
14
import re
3✔
15
import time
3✔
16
from typing import Any, Dict, List, Tuple
3✔
17

18
import numpy as np
3✔
19
import pandas as pd
3✔
20

21
from scheduling.routing import (
3✔
22
    compute_path_durations,
23
    dynamic_routing,
24
    rerouting,
25
)
26
from utils.helper import compute_link_utilization, track_link_waiting
3✔
27

28
INIT_PGA_RE = re.compile(r"^([A-Za-z]+)(\d+)$")
3✔
29
EPS = 1e-12
3✔
30

31

32
class PGA:
3✔
33
    def __init__(
3✔
34
        self,
35
        name: str,
36
        arrival: float,
37
        start: float,
38
        end: float,
39
        route: List[str],
40
        resources: Dict[Tuple[str, str], float],
41
        link_busy: Dict[Tuple[str, str], float],
42
        p_gen: float,
43
        epr_pairs: int,
44
        slot_duration: float,
45
        rng: np.random.Generator,
46
        log: List[Dict[str, Any]],
47
        policy: str,
48
        p_swap: float,
49
        memory: int,
50
        deadline: float | None = None,
51
        route_links: List[Tuple[str, str]] | None = None,
52
    ) -> None:
53
        """Packet Generation Attempt (PGA) simulation. A PGA tries to
54
        generate EPR pairs over a specified route within a defined time window,
55
        considering resource availability and link busy times.
56

57
        The possible outcomes for a PGA:
58
        - If the PGA starts but cannot generate the required E2E EPR pairs
59
          within its time window, it is marked as "failed".
60
        - If the PGA successfully generates the required E2E EPR pairs within
61
          its time window, it is marked as "completed".
62

63
        The conditions for EPR generation:
64
        - Each link attempts to generate EPR pairs in discrete time slots.
65
        - The number of trials needed for a successful EPR pair generation on
66
          each link follows a geometric distribution with success probability
67
          `p_gen`.
68
        - The first success across all links must occur within the memory
69
          of the first generated pair to be considered valid.
70
        - If there are swaps involved, each swap must also succeed based on
71
          the swap probability `p_swap` for the end-to-end entanglement to be
72
          successful.
73

74
        Args:
75
            name (str): PGA identifier.
76
            arrival (float): Arrival time of the PGA in the simulation.
77
            start (float): Start time of the PGA in the simulation.
78
            end (float): End time of the PGA in the simulation.
79
            route (List[str]): List of nodes in the PGA's route.
80
            resources (Dict[Tuple[str, str], float]): Dictionary tracking
81
            when undirected links become free.
82
            link_busy (Dict[Tuple[str, str], float]): Dictionary to track busy
83
            time of links.
84
            p_gen (float): Probability of generating an EPR pair in a single
85
            trial.
86
            epr_pairs (int): Number of EPR pairs to generate for this PGA.
87
            slot_duration (float): Duration of a time slot for EPR generation.
88
            rng (np.random.Generator): Random number generator for
89
            probabilistic events.
90
            log (List[Dict[str, Any]]): Log to record PGA performance metrics.
91
            policy (str): Scheduling policy for the PGA, either "best_effort"
92
            or "deadline". If "deadline", the PGA will attempt to complete
93
            within the maximum burst time defined in durations.
94
            p_swap (float): Probability of swapping an EPR pair.
95
            memory (int): Memory: number of independent link-generation trials
96
            per slot.
97
            deadline (float, optional): Deadline time for the PGA. Defaults to
98
            None, which means no deadline.
99
        """
100
        self.name = name
3✔
101
        self.arrival = float(arrival)
3✔
102
        self.start = float(start)
3✔
103
        self.end = float(end)
3✔
104
        self.route = route
3✔
105
        self.resources = resources
3✔
106
        self.link_busy = link_busy
3✔
107
        self.p_gen = float(p_gen)
3✔
108
        self.epr_pairs = int(epr_pairs)
3✔
109
        self.slot_duration = float(slot_duration)
3✔
110
        self.rng = rng
3✔
111
        self.log = log
3✔
112
        self.policy = policy
3✔
113
        self.deadline = None if deadline is None else float(deadline)
3✔
114
        self.links = route_links
3✔
115
        self.n_swap = max(0, len(self.route) - 2)
3✔
116
        self.p_swap = float(p_swap)
3✔
117
        self.memory = max(0, int(memory))
3✔
118

119
    def _simulate_e2e_attempts(self, max_attempts: int) -> np.ndarray:
3✔
120
        """Single end-to-end entanglement for a batch of attempts."""
121
        if self.memory <= 0 or self.p_gen <= 0.0 or max_attempts <= 0:
3✔
122
            return np.zeros(max_attempts, dtype=bool)
3✔
123

124
        n_links = self.n_swap + 1
3✔
125
        t_mem = self.memory
3✔
126
        size = (max_attempts, n_links)
3✔
127

128
        starts = self.rng.geometric(self.p_gen, size=size) - 1
3✔
129
        ends = starts + (t_mem - 1)
3✔
130

131
        candidate = starts.max(axis=1)
3✔
132
        last_valid = ends.min(axis=1)
3✔
133

134
        succ = (candidate < t_mem) & (last_valid >= candidate)
3✔
135

136
        if self.n_swap > 0:
3✔
137
            if self.p_swap < 1.0:
×
138
                p_bsms = self.p_swap**self.n_swap
×
139
                swap_ok = self.rng.random(max_attempts) < p_bsms
×
140
                succ &= swap_ok
×
141

142
        return succ
3✔
143

144
    def _update_resources_and_links(
3✔
145
        self,
146
        completion: float,
147
        attempts_run: int,
148
    ) -> None:
149
        """Mark nodes and links busy through ``completion``."""
150
        for link in self.links:
3✔
151
            self.resources[link] = max(
3✔
152
                self.resources.get(link, 0.0), completion
153
            )
154

155
        if attempts_run > 0 and self.links:
3✔
156
            busy = attempts_run * self.slot_duration
3✔
157
            for link in self.links:
3✔
158
                self.link_busy[link] = self.link_busy.get(link, 0.0) + busy
3✔
159

160
    def run(self) -> Dict[str, Any]:
3✔
161
        attempts_run = 0
3✔
162
        pairs_generated = 0
3✔
163
        wait_until = 0.0
3✔
164
        blocking_links = []
3✔
165

166
        for link in self.links:
3✔
167
            lk_b_t = self.resources.get(link, 0.0)
3✔
168
            if lk_b_t > wait_until:
3✔
169
                wait_until = lk_b_t
3✔
170

171
        for link in self.links:
3✔
172
            lk_b_t = self.resources.get(link, 0.0)
3✔
173
            if abs(lk_b_t - wait_until) < EPS:
3✔
174
                blocking_links.append(link)
3✔
175

176
        current_time = self.start
3✔
177
        diff = self.end - self.start
3✔
178
        t_budget = diff if diff > 0.0 else 0.0
3✔
179
        status = "failed"
3✔
180

181
        if t_budget > EPS and self.policy == "deadline":
3✔
182
            max_attempts = int((t_budget + EPS) // self.slot_duration)
3✔
183
            succ = self._simulate_e2e_attempts(max_attempts)
3✔
184
            csum = (
3✔
185
                np.cumsum(succ, dtype=int)
186
                if len(succ)
187
                else np.array([], dtype=int)
188
            )
189
            hit = (
3✔
190
                np.searchsorted(csum, self.epr_pairs, side="left")
191
                if len(csum)
192
                else len(csum)
193
            )
194

195
            if len(csum) and hit < len(csum):
3✔
196
                attempts_run = int(hit + 1)
3✔
197
                pairs_generated = int(csum[attempts_run - 1])
3✔
198
                status = "completed"
3✔
199
            else:
200
                attempts_run = max_attempts
3✔
201
                pairs_generated = int(csum[-1]) if len(csum) else 0
3✔
202

203
            current_time = self.start + attempts_run * self.slot_duration
3✔
204
            completion = (
3✔
205
                current_time if current_time < self.end else self.end
206
            )
207
            self._update_resources_and_links(completion, attempts_run)
3✔
208
        else:
209
            completion = self.start
×
210

211
        burst = completion - self.start
3✔
212
        turnaround = max(0.0, completion - self.arrival)
3✔
213
        waiting = max(0.0, turnaround - burst)
3✔
214

215
        result = {
3✔
216
            "pga": self.name,
217
            "arrival_time": self.arrival,
218
            "start_time": self.start,
219
            "burst_time": burst,
220
            "completion_time": completion,
221
            "turnaround_time": turnaround,
222
            "waiting_time": waiting,
223
            "pairs_generated": pairs_generated,
224
            "status": status,
225
            "deadline": self.deadline if self.policy == "deadline" else None,
226
            "blocking_links": blocking_links,
227
        }
228
        self.log.append(result)
3✔
229
        return result
3✔
230

231

232
def simulate_static(
3✔
233
    schedule: List[Tuple[str, float, float, float]],
234
    app_specs: Dict[str, Dict[str, Any]],
235
    pga_parameters: Dict[str, Dict[str, float]],
236
    pga_rel_times: Dict[str, float],
237
    pga_periods: Dict[str, float],
238
    pga_network_paths: Dict[str, List[str]],
239
    policies: Dict[str, str],
240
    rng: np.random.Generator,
241
) -> Tuple[
242
    pd.DataFrame,
243
    List[str],
244
    Dict[str, float],
245
    Dict[Tuple[str, str], Dict[str, float]],
246
    Dict[Tuple[str, str], Dict[str, float | int]],
247
]:
248
    """Simulate periodic PGA scheduling. The scheduler computes a static
249
    schedule of PGAs that attempt to generate EPR pairs over a specified route
250
    within a defined time window, considering resource availability and link
251
    busy times.
252

253
    Args:
254
        schedule (List[Tuple[str, float, float, float]]): List of tuples where
255
        each contains the PGA name, start time, end time, and deadline of the
256
        scheduled PGA.
257
        pga_parameters (Dict[str, Dict[str, float]]): Parameters for each PGA,
258
        including the probability of generating an EPR pair, number of
259
        required successes, and slot duration.
260
        pga_rel_times (Dict[str, float]): Relative release times for each PGA.
261
        pga_periods (Dict[str, float]): Periods for each PGA, indicating the
262
        time interval between successive releases of the PGA.
263
        pga_network_paths (Dict[str, list[str]]): List of nodes for each PGA's
264
        path in the network.
265
        policies (Dict[str, str]): Scheduling policy for each PGA. This can be
266
        "best_effort" or "deadline".
267
        rng (np.random.Generator): Random number generator for probabilistic
268
        events.
269

270
    Returns:
271
        Tuple[
272
            pd.DataFrame,
273
            List[str],
274
            Dict[str, float],
275
            Dict[Tuple[str, str], Dict[str, float]],
276
        ]: Contains:
277
            - DataFrame with PGA performance metrics.
278
            - List of PGA names.
279
            - Dictionary mapping PGA names to their release times.
280
            - Dictionary mapping undirected links to busy time and utilization.
281
            - Dictionary mapping undirected links to waiting metrics.
282
    """
283
    log = []
3✔
284
    pga_release_times = {}
3✔
285
    pga_names = []
3✔
286

287
    instances_required = {
3✔
288
        app: max(0, int(app_specs.get(app, {}).get("instances", 0)))
289
        for app in pga_network_paths
290
    }
291
    total_required = sum(instances_required.values())
3✔
292
    completed_instances = {app: 0 for app in instances_required}
3✔
293
    completed_total = 0
3✔
294

295
    pga_route_links = {
3✔
296
        app: [
297
            tuple(sorted((u, v)))
298
            for u, v in zip(path[:-1], path[1:], strict=False)
299
        ]
300
        for app, path in pga_network_paths.items()
301
    }
302
    all_links = {link for links in pga_route_links.values() for link in links}
3✔
303
    resources = {link: 0.0 for link in all_links}
3✔
304
    link_busy = dict.fromkeys(all_links, 0.0)
3✔
305
    link_waiting = {
3✔
306
        link: {"total_waiting_time": 0.0, "pga_waited": 0}
307
        for link in all_links
308
    }
309
    min_arrival = float("inf")
3✔
310
    max_completion = 0.0
3✔
311

312
    for pga_name, sched_start, sched_end, sched_deadline in schedule:
3✔
313
        m = INIT_PGA_RE.match(pga_name)
3✔
314
        app, idx = (m.group(1), int(m.group(2))) if m else (pga_name, 0)
3✔
315

316
        required = instances_required.get(app, 0)
3✔
317
        if required > 0 and completed_instances[app] >= required:
3✔
318
            continue
×
319

320
        r0 = float(pga_rel_times.get(app, 0.0))
3✔
321
        T = float(pga_periods.get(app, 0.0))
3✔
322
        arrival = r0 + idx * T
3✔
323
        if arrival < min_arrival:
3✔
324
            min_arrival = arrival
3✔
325

326
        pga = PGA(
3✔
327
            name=pga_name,
328
            arrival=arrival,
329
            start=sched_start,
330
            end=sched_end,
331
            route=pga_network_paths[app],
332
            resources=resources,
333
            link_busy=link_busy,
334
            p_gen=pga_parameters[app]["p_gen"],
335
            epr_pairs=int(pga_parameters[app]["epr_pairs"]),
336
            slot_duration=pga_parameters[app]["slot_duration"],
337
            rng=rng,
338
            log=log,
339
            policy=policies[app],
340
            p_swap=pga_parameters[app]["p_swap"],
341
            memory=pga_parameters[app]["memory"],
342
            deadline=sched_deadline,
343
            route_links=pga_route_links.get(app),
344
        )
345
        result = pga.run()
3✔
346
        waiting_time = max(0.0, float(result.get("waiting_time", 0.0)))
3✔
347
        track_link_waiting(
3✔
348
            waiting_time,
349
            link_waiting,
350
            blocking_links=result.get("blocking_links"),
351
        )
352

353
        pga_names.append(pga_name)
3✔
354
        pga_release_times[pga_name] = sched_start
3✔
355
        max_completion = max(max_completion, result["completion_time"])
3✔
356

357
        if result.get("status") == "completed":
3✔
358
            completed_instances[app] += 1
3✔
359
            completed_total += 1
3✔
360
            if completed_total == total_required:
3✔
361
                break
3✔
362

363
    df = pd.DataFrame(log)
3✔
364
    link_utilization = compute_link_utilization(
3✔
365
        link_busy,
366
        min_arrival,
367
        max_completion,
368
    )
369

370
    return df, pga_names, pga_release_times, link_utilization, link_waiting
3✔
371

372

373
def simulate_dynamic(
3✔
374
    app_specs: Dict[str, Dict[str, Any]],
375
    durations: Dict[str, float],
376
    pga_parameters: Dict[str, Dict[str, float]],
377
    pga_rel_times: Dict[str, float],
378
    pga_network_paths: Dict[str, List[List[str]]],
379
    rng: np.random.Generator,
380
    full_dynamic: bool = True,
381
    rerouting_mode: bool = False,
382
    all_links: List[Tuple[str, str]] | None = None,
383
    simple_paths: Dict[str, List[List[str]]] | None = None,
384
    static_routing_mode: bool = False,
385
    horizon_time: float | None = None,
386
    warmup_time: float = 0.0,
387
    rng_routing: np.random.Generator | None = None,
388
    rng_arrivals: Dict[str, np.random.Generator] | None = None,
389
    instance_arrival_rate: float = 10.0,
390
):
391
    log = []
3✔
392
    defer_counts = {}
3✔
393
    pga_release_times = {}
3✔
394
    pga_names = []
3✔
395
    seen_pgas = set()
3✔
396

397
    pga_route_links = {
3✔
398
        app: [
399
            tuple(sorted((u, v)))
400
            for u, v in zip(paths[0][:-1], paths[0][1:], strict=False)
401
        ]
402
        for app, paths in pga_network_paths.items()
403
    }
404
    resources = {link: 0.0 for link in all_links}
3✔
405
    link_busy = dict.fromkeys(all_links, 0.0)
3✔
406
    link_busy_record = dict.fromkeys(all_links, 0.0)
3✔
407
    link_waiting_routing = (
3✔
408
        {
409
            link: {"total_waiting_time": 0.0, "pga_waited": 0}
410
            for link in all_links
411
        }
412
        if full_dynamic and simple_paths is not None
413
        else None
414
    )
415
    link_waiting = {
3✔
416
        link: {"total_waiting_time": 0.0, "pga_waited": 0}
417
        for link in all_links
418
    }
419
    min_arrival = float("inf")
3✔
420
    max_completion = 0.0
3✔
421
    routing_decision_cpt = 0
3✔
422
    routing_decision_runtime = 0.0
3✔
423

424
    periods = {app: app_specs[app].get("period") for app in app_specs}
3✔
425
    base_release = {app: pga_rel_times.get(app, 0.0) for app in app_specs}
3✔
426
    max_instances = {
3✔
427
        app: max(0, int(app_specs[app].get("instances", 0)))
428
        for app in app_specs
429
    }
430
    release_indices = {app: 0 for app in app_specs}
3✔
431
    instance_poisson_enabled = instance_arrival_rate > 0.0
3✔
432
    poisson_next_release = (
3✔
433
        {app: float(base_release.get(app, 0.0)) for app in app_specs}
434
        if instance_poisson_enabled
435
        else {}
436
    )
437

438
    events_queue = []
3✔
439
    ready_queue = []
3✔
440

441
    routing_metadata = {}
3✔
442
    pga_best = {}
3✔
443
    rerouting_candidates = {}
3✔
444
    if full_dynamic and simple_paths is not None:
3✔
NEW
445
        for app in app_specs:
×
NEW
446
            _t0 = time.perf_counter()
×
NEW
447
            routing_metadata[app] = compute_path_durations(
×
448
                pga_parameters[app],
449
                simple_paths=simple_paths,
450
                src=app_specs[app]["src"],
451
                dst=app_specs[app]["dst"],
452
            )
NEW
453
            routing_decision_runtime += time.perf_counter() - _t0
×
NEW
454
            min_fid = app_specs[app].get("min_fidelity", 0.0)
×
NEW
455
            feasible_durs = [
×
456
                dur
457
                for fid, _, _, dur in routing_metadata[app]
458
                if fid >= min_fid
459
            ]
NEW
460
            pga_best[app] = (
×
461
                min(feasible_durs) if feasible_durs else float("nan")
462
            )
463
    elif rerouting_mode:
3✔
NEW
464
        for app, app_paths in pga_network_paths.items():
×
NEW
465
            _t0 = time.perf_counter()
×
NEW
466
            rerouting_candidates[app] = compute_path_durations(
×
467
                pga_parameters[app], provisioned_paths=app_paths
468
            )
NEW
469
            routing_decision_runtime += time.perf_counter() - _t0
×
470

471
    def enqueue_release(app: str) -> None:
3✔
472
        idx = release_indices[app]
3✔
473
        period = periods[app]
3✔
474

475
        if max_instances[app] > 0 and idx >= max_instances[app]:
3✔
476
            return
3✔
477

478
        if instance_poisson_enabled:
3✔
479
            release = poisson_next_release.get(app, base_release[app])
3✔
480
            _rng_arr = (
3✔
481
                rng_arrivals.get(app) if rng_arrivals is not None else None
482
            ) or rng
483
            poisson_next_release[app] = release + _rng_arr.exponential(
3✔
484
                1.0 / instance_arrival_rate
485
            )
486
        else:
UNCOV
487
            release = base_release[app] + period * idx
×
488

489
        if release >= horizon_time:
3✔
NEW
490
            return
×
491

492
        deadline = release + period
3✔
493
        heapq.heappush(
3✔
494
            events_queue,
495
            (release, deadline, release, app, idx, release, "release"),
496
        )
497
        release_indices[app] += 1
3✔
498

499
    for app in app_specs:
3✔
500
        enqueue_release(app)
3✔
501

502
    cur_t = 0.0
3✔
503

504
    while events_queue or ready_queue:
3✔
505
        if not ready_queue:
3✔
506
            cur_t = events_queue[0][0]
3✔
507

508
        if cur_t >= horizon_time:
3✔
NEW
509
            break
×
510

511
        while events_queue and events_queue[0][0] <= cur_t + EPS:
3✔
512
            (
3✔
513
                event_time,
514
                deadline,
515
                arrival_time,
516
                app,
517
                i,
518
                ready_time,
519
                event_type,
520
            ) = heapq.heappop(events_queue)
521
            if event_type == "release":
3✔
522
                enqueue_release(app)
3✔
523

524
            if event_time >= horizon_time:
3✔
NEW
525
                continue
×
526

527
            heapq.heappush(
3✔
528
                ready_queue,
529
                (deadline, ready_time, arrival_time, app, i, event_time),
530
            )
531

532
        if not ready_queue:
3✔
533
            continue
×
534

535
        while ready_queue:
3✔
536
            deadline, rdy_t, arrival_time, app, i, _ = heapq.heappop(
3✔
537
                ready_queue
538
            )
539
            at = float(arrival_time)
3✔
540
            if at < min_arrival:
3✔
541
                min_arrival = at
3✔
542

543
            pga_name = f"{app}{i}"
3✔
544
            if pga_name not in seen_pgas:
3✔
545
                seen_pgas.add(pga_name)
3✔
546
                pga_names.append(pga_name)
3✔
547
                pga_release_times[pga_name] = arrival_time
3✔
548

549
            routed_fid = np.nan
3✔
550
            if static_routing_mode:
3✔
NEW
551
                route_links = pga_route_links.get(app, [])
×
NEW
552
                selected_path = pga_network_paths[app][0]
×
NEW
553
                duration = durations.get(app, 0.0)
×
UNCOV
554
                last_available = max(
×
555
                    (resources.get(lk, 0.0) for lk in route_links),
556
                    default=0.0,
557
                )
558
            elif full_dynamic and simple_paths is not None:
3✔
NEW
559
                routing_decision_cpt += 1
×
NEW
560
                _t0 = time.perf_counter()
×
NEW
561
                routed, next_avail = dynamic_routing(
×
562
                    routing_metadata[app],
563
                    app_specs[app]["min_fidelity"],
564
                    deadline,
565
                    cur_t,
566
                    resources,
567
                    rng_routing if rng_routing is not None else rng,
568
                )
NEW
569
                routing_decision_runtime += time.perf_counter() - _t0
×
NEW
570
                if routed is None:
×
NEW
571
                    if next_avail is not None:
×
NEW
572
                        defer_counts[pga_name] = (
×
573
                            defer_counts.get(pga_name, 0) + 1
574
                        )
NEW
575
                        heapq.heappush(
×
576
                            events_queue,
577
                            (
578
                                next_avail,
579
                                deadline,
580
                                arrival_time,
581
                                app,
582
                                i,
583
                                rdy_t,
584
                                "resume"
585
                            ),
586
                        )
587
                    else:
NEW
588
                        log.append({
×
589
                            "pga": pga_name,
590
                            "arrival_time": arrival_time,
591
                            "ready_time": rdy_t,
592
                            "start_time": np.nan,
593
                            "burst_time": 0.0,
594
                            "completion_time": cur_t,
595
                            "turnaround_time": max(0.0, cur_t - arrival_time),
596
                            "waiting_time": max(0.0, cur_t - rdy_t),
597
                            "pairs_generated": 0,
598
                            "status": "drop",
599
                            "deadline": deadline,
600
                        })
NEW
601
                    continue
×
NEW
602
                (
×
603
                    selected_path,
604
                    route_links,
605
                    last_available,
606
                    duration,
607
                    routed_fid,
608
                ) = routed
609
            else:
610
                route_links = pga_route_links.get(app, [])
3✔
611
                selected_path = pga_network_paths[app][0]
3✔
612
                duration = durations.get(app, 0.0)
3✔
613

614
                last_available = 0.0
3✔
615
                for link in route_links:
3✔
616
                    last_available = max(
3✔
617
                        last_available, resources.get(link, 0.0)
618
                    )
619

620
                would_drop = last_available + duration > deadline + EPS
3✔
621
                if (
3✔
622
                    last_available > cur_t + EPS
623
                    and rerouting_mode
624
                    and would_drop
625
                ):
NEW
626
                    routing_decision_cpt += 1
×
NEW
627
                    _t0 = time.perf_counter()
×
NEW
628
                    alt_path = rerouting(
×
629
                        rerouting_candidates,
630
                        deadline,
631
                        cur_t,
632
                        app,
633
                        resources,
634
                    )
NEW
635
                    routing_decision_runtime += time.perf_counter() - _t0
×
NEW
636
                    if alt_path is not None:
×
NEW
637
                        (
×
638
                            selected_path,
639
                            route_links,
640
                            last_available,
641
                            duration,
642
                            routed_fid,
643
                        ) = alt_path
644

645
            _stamp = (
3✔
646
                {
647
                    "e2e_fidelity": routed_fid,
648
                    "pga_duration": duration,
649
                    "hops": len(selected_path) - 1,
650
                }
651
                if (full_dynamic and simple_paths is not None)
652
                or rerouting_mode
653
                else {}
654
            )
655
            if full_dynamic and simple_paths is not None:
3✔
NEW
656
                best = pga_best.get(app, float("nan"))
×
NEW
657
                _stamp["routing_efficiency"] = (
×
658
                    best / duration
659
                    if duration > 0 and not np.isnan(best)
660
                    else float("nan")
661
                )
662

663
            if last_available > cur_t + EPS:
3✔
664
                if last_available + duration <= deadline + EPS:
3✔
665
                    defer_counts[pga_name] = (
3✔
666
                        defer_counts.get(pga_name, 0) + 1
667
                    )
668
                    heapq.heappush(
3✔
669
                        events_queue,
670
                        (
671
                            last_available,
672
                            deadline,
673
                            arrival_time,
674
                            app,
675
                            i,
676
                            rdy_t,
677
                            "resume",
678
                        ),
679
                    )
680
                else:
681
                    log.append({
3✔
682
                        "pga": pga_name,
683
                        "arrival_time": arrival_time,
684
                        "ready_time": rdy_t,
685
                        "start_time": np.nan,
686
                        "burst_time": 0.0,
687
                        "completion_time": cur_t,
688
                        "turnaround_time": max(0.0, cur_t - arrival_time),
689
                        "waiting_time": max(0.0, cur_t - rdy_t),
690
                        "pairs_generated": 0,
691
                        "status": "drop",
692
                        "deadline": deadline,
693
                        **_stamp,
694
                    })
695
                continue
3✔
696

697
            start_time = cur_t
3✔
698
            completion = start_time + duration
3✔
699

700
            if (
3✔
701
                app_specs[app].get("policy") == "deadline"
702
                and deadline is not None
703
                and start_time + duration > deadline + EPS
704
            ):
705
                log.append({
3✔
706
                    "pga": pga_name,
707
                    "arrival_time": arrival_time,
708
                    "ready_time": rdy_t,
709
                    "start_time": np.nan,
710
                    "burst_time": 0.0,
711
                    "completion_time": cur_t,
712
                    "turnaround_time": max(0.0, cur_t - arrival_time),
713
                    "waiting_time": max(0.0, cur_t - rdy_t),
714
                    "pairs_generated": 0,
715
                    "status": "drop",
716
                    "deadline": deadline,
717
                    **_stamp,
718
                })
719
                continue
3✔
720

721
            recording = start_time >= warmup_time
3✔
722
            pga = PGA(
3✔
723
                name=pga_name,
724
                arrival=arrival_time,
725
                start=start_time,
726
                end=completion,
727
                route=selected_path,
728
                resources=resources,
729
                link_busy=link_busy_record if recording else link_busy,
730
                p_gen=pga_parameters[app]["p_gen"],
731
                epr_pairs=int(pga_parameters[app]["epr_pairs"]),
732
                slot_duration=pga_parameters[app]["slot_duration"],
733
                rng=rng,
734
                log=log,
735
                policy=app_specs[app].get("policy"),
736
                p_swap=pga_parameters[app]["p_swap"],
737
                memory=pga_parameters[app]["memory"],
738
                deadline=deadline,
739
                route_links=route_links,
740
            )
741
            result = pga.run()
3✔
742
            result["ready_time"] = float(rdy_t)
3✔
743
            result["waiting_time"] = max(0.0, start_time - rdy_t)
3✔
744
            result.update(_stamp)
3✔
745

746
            if link_waiting_routing is not None:
3✔
NEW
747
                track_link_waiting(
×
748
                    result.get("waiting_time", 0.0),
749
                    link_waiting_routing,
750
                    blocking_links=result.get("blocking_links"),
751
                )
752

753
            if recording:
3✔
754
                track_link_waiting(
3✔
755
                    result.get("waiting_time", 0.0),
756
                    link_waiting,
757
                    blocking_links=result.get("blocking_links"),
758
                )
759
                if result["completion_time"] > horizon_time:
3✔
NEW
760
                    excess = result["completion_time"] - horizon_time
×
NEW
761
                    for lk in route_links:
×
NEW
762
                        if lk in link_busy_record:
×
NEW
763
                            link_busy_record[lk] = max(
×
764
                                0.0, link_busy_record[lk] - excess
765
                            )
766

767
            max_completion = max(max_completion, result["completion_time"])
3✔
768

769
            status = result.get("status", "")
3✔
770
            if status == "completed":
3✔
771
                continue
3✔
772

773
    df = pd.DataFrame(log)
3✔
774
    del log
3✔
775
    link_utilization = compute_link_utilization(
3✔
776
        link_busy_record, warmup_time, horizon_time,
777
    )
778

779
    return (
3✔
780
        df,
781
        pga_names,
782
        pga_release_times,
783
        link_utilization,
784
        link_waiting,
785
        routing_decision_cpt,
786
        routing_decision_runtime,
787
        defer_counts,
788
    )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc