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

Quang00 / qnscheduling / 21981894485

13 Feb 2026 09:35AM UTC coverage: 70.494% (-0.6%) from 71.099%
21981894485

push

github

Quang00
change track link waiting to distribute equally the waiting time on blocking links

9 of 16 new or added lines in 2 files covered. (56.25%)

2 existing lines in 1 file now uncovered.

528 of 749 relevant lines covered (70.49%)

2.11 hits per line

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

78.57
/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
from typing import Any, Dict, List, Tuple
3✔
16

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

20
from utils.helper import compute_link_utilization, track_link_waiting
3✔
21

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

25

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

51
        The possible outcomes for a PGA:
52
        - If the PGA starts but cannot generate the required E2E EPR pairs
53
          within its time window, it is marked as "failed".
54
        - If the PGA successfully generates the required E2E EPR pairs within
55
          its time window, it is marked as "completed".
56

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

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

113
    def _simulate_e2e_attempts(self, max_attempts: int) -> np.ndarray:
3✔
114
        """Single end-to-end entanglement for a batch of attempts."""
115
        if self.memory <= 0 or self.p_gen <= 0.0 or max_attempts <= 0:
3✔
116
            return np.zeros(max_attempts, dtype=bool)
3✔
117

118
        n_links = self.n_swap + 1
3✔
119
        t_mem = self.memory
3✔
120
        size = (max_attempts, n_links)
3✔
121

122
        starts = self.rng.geometric(self.p_gen, size=size) - 1
3✔
123
        ends = starts + (t_mem - 1)
3✔
124

125
        candidate = starts.max(axis=1)
3✔
126
        last_valid = ends.min(axis=1)
3✔
127

128
        succ = (candidate < t_mem) & (last_valid >= candidate)
3✔
129

130
        if self.n_swap > 0:
3✔
131
            if self.p_swap < 1.0:
×
132
                p_bsms = self.p_swap**self.n_swap
×
133
                swap_ok = self.rng.random(max_attempts) < p_bsms
×
134
                succ &= swap_ok
×
135

136
        return succ
3✔
137

138
    def _update_resources_and_links(
3✔
139
        self,
140
        completion: float,
141
        attempts_run: int,
142
    ) -> None:
143
        """Mark nodes and links busy through ``completion``."""
144
        for link in self.links:
3✔
145
            self.resources[link] = max(
3✔
146
                self.resources.get(link, 0.0), completion
147
            )
148

149
        if attempts_run > 0 and self.links:
3✔
150
            busy = attempts_run * self.slot_duration
3✔
151
            for link in self.links:
3✔
152
                self.link_busy[link] = self.link_busy.get(link, 0.0) + busy
3✔
153

154
    def run(self) -> Dict[str, Any]:
3✔
155
        attempts_run = 0
3✔
156
        pairs_generated = 0
3✔
157
        wait_until = 0.0
3✔
158
        blocking_links = []
3✔
159

160
        for link in self.links:
3✔
161
            link_busy_time = self.resources.get(link, 0.0)
3✔
162
            wait_until = max(wait_until, link_busy_time)
3✔
163

164
        for link in self.links:
3✔
165
            link_busy_time = self.resources.get(link, 0.0)
3✔
166
            if abs(link_busy_time - wait_until) < EPS:
3✔
167
                blocking_links.append(link)
3✔
168

169
        current_time = self.start
3✔
170
        t_budget = max(0.0, self.end - self.start)
3✔
171
        status = "failed"
3✔
172

173
        if t_budget > EPS and self.policy == "deadline":
3✔
174
            max_attempts = int((t_budget + EPS) // self.slot_duration)
3✔
175
            succ = self._simulate_e2e_attempts(max_attempts)
3✔
176
            csum = (
3✔
177
                np.cumsum(succ, dtype=int)
178
                if len(succ)
179
                else np.array([], dtype=int)
180
            )
181
            hit = (
3✔
182
                np.searchsorted(csum, self.epr_pairs, side="left")
183
                if len(csum)
184
                else len(csum)
185
            )
186

187
            if len(csum) and hit < len(csum):
3✔
188
                attempts_run = int(hit + 1)
3✔
189
                pairs_generated = int(csum[attempts_run - 1])
3✔
190
                status = "completed"
3✔
191
            else:
192
                attempts_run = max_attempts
3✔
193
                pairs_generated = int(csum[-1]) if len(csum) else 0
3✔
194

195
            current_time = self.start + attempts_run * self.slot_duration
3✔
196
            completion = min(self.end, current_time)
3✔
197
            self._update_resources_and_links(completion, attempts_run)
3✔
198
        elif self.policy == "best_effort":
×
199
            while pairs_generated < self.epr_pairs:
×
200
                succ = self._simulate_e2e_attempts(1)
×
201
                pairs_generated += int(succ.sum())
×
202
                attempts_run += 1
×
203

204
            completion = self.start + attempts_run * self.slot_duration
×
205
            status = (
×
206
                "completed" if pairs_generated >= self.epr_pairs
207
                else "failed"
208
            )
209
            self._update_resources_and_links(completion, attempts_run)
×
210
        else:
211
            completion = self.start
×
212

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

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

233

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

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

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

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

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

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

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

322
        r0 = float(pga_rel_times.get(app, 0.0))
3✔
323
        T = float(pga_periods.get(app, 0.0))
3✔
324
        arrival = r0 + idx * T
3✔
325
        min_arrival = min(min_arrival, arrival)
3✔
326

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

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

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

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

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

373

374
def simulate_dynamic(
3✔
375
    app_specs: Dict[str, Dict[str, Any]],
376
    durations: Dict[str, float],
377
    pga_parameters: Dict[str, Dict[str, float]],
378
    pga_rel_times: Dict[str, float],
379
    pga_network_paths: Dict[str, List[str]],
380
    rng: np.random.Generator,
381
    arrival_rate: float | None = None,
382
) -> Tuple[
383
    pd.DataFrame,
384
    List[str],
385
    Dict[str, float],
386
    Dict[Tuple[str, str], Dict[str, float]],
387
    Dict[Tuple[str, str], Dict[str, float | int]],
388
]:
389
    """Simulate online dynamic PGA scheduling. PGAs arrive either periodically
390
    or according to a Poisson process, attempting to generate EPR pairs over a
391
    specified route within a defined time window, considering resource
392
    availability and link busy times. The dynamic scheduler processes PGAs
393
    as they arrive, making real-time decisions based on current network
394
    conditions.
395

396
        The scheduler made the following decisions for each PGA:
397

398
        - If a PGA cannot start by its deadline due to busy links, it
399
            is marked as "drop".
400
        - If a PGA cannot be completed due to its duration exceeding
401
            the deadline, it is marked as "drop".
402
        - If a PGA does not generate the required E2E EPR pairs within its time
403
            window, it may be retried if there is time before the deadline.
404
        - If a PGA cannot start immediately due to busy links but can still
405
            complete within its deadline, it is marked as "defer" and
406
            rescheduled to start when resources become available.
407
        - If a PGA successfully generates the required E2E EPR pairs within its
408
            time window, it is marked as "completed".
409

410
    Args:
411
        app_specs (Dict[str, Dict[str, Any]]): Application specifications.
412
        durations (Dict[str, float]): Duration of each application.
413
        pga_parameters (Dict[str, Dict[str, float]]): Parameters for each PGA.
414
        pga_rel_times (Dict[str, float]): Release times for each PGA.
415
        pga_network_paths (Dict[str, List[str]]): Network paths for each PGA.
416
        rng (np.random.Generator): Random number generator.
417
        arrival_rate (float | None): Mean rate lambda for Poisson arrivals.
418
            When None, arrivals remain periodic.
419

420
    Returns:
421
        Tuple[
422
            pd.DataFrame,
423
            List[str],
424
            Dict[str, float],
425
            Dict[Tuple[str, str], Dict[str, float]],
426
        ]: Contains:
427
            - DataFrame with PGA performance metrics.
428
            - List of PGA names.
429
            - Dictionary mapping PGA names to their release times.
430
            - Dictionary mapping undirected links to busy time and utilization.
431
            - Dictionary mapping undirected links to waiting metrics.
432
    """
433
    log = []
3✔
434
    pga_release_times = {}
3✔
435
    pga_names = []
3✔
436
    seen_pgas = set()
3✔
437
    drop_logged = set()
3✔
438

439
    pga_route_links = {
3✔
440
        app: [
441
            tuple(sorted((u, v)))
442
            for u, v in zip(path[:-1], path[1:], strict=False)
443
        ]
444
        for app, path in pga_network_paths.items()
445
    }
446
    all_links = {link for links in pga_route_links.values() for link in links}
3✔
447
    resources = {link: 0.0 for link in all_links}
3✔
448
    link_busy = dict.fromkeys(all_links, 0.0)
3✔
449
    link_waiting = {
3✔
450
        link: {"total_waiting_time": 0.0, "pga_waited": 0}
451
        for link in all_links
452
    }
453
    min_arrival = float("inf")
3✔
454
    max_completion = 0.0
3✔
455

456
    periods = {app: app_specs[app].get("period") for app in app_specs}
3✔
457
    inst_req = {app: app_specs[app].get("instances") for app in app_specs}
3✔
458
    base_release = {app: pga_rel_times.get(app, 0.0) for app in app_specs}
3✔
459
    completed_instances = {app: 0 for app in app_specs}
3✔
460
    release_indices = {app: 0 for app in app_specs}
3✔
461
    poisson_enabled = arrival_rate is not None and arrival_rate > 0.0
3✔
462
    poisson = (1.0 / arrival_rate) if poisson_enabled else None
3✔
463
    poisson_next_release = (
3✔
464
        {app: float(base_release.get(app, 0.0)) for app in app_specs}
465
        if poisson_enabled
466
        else {}
467
    )
468

469
    events_queue = []
3✔
470
    ready_queue = []
3✔
471

472
    def enqueue_release(app: str) -> None:
3✔
473
        if inst_req[app] <= completed_instances[app]:
3✔
474
            return
×
475
        idx = release_indices[app]
3✔
476
        period = periods[app]
3✔
477

478
        if poisson_enabled:
3✔
479
            release = poisson_next_release.get(app, base_release[app])
×
480
            poisson_next_release[app] = release + rng.exponential(poisson)
×
481
        else:
482
            release = base_release[app] + period * idx
3✔
483

484
        deadline = release + period
3✔
485
        heapq.heappush(
3✔
486
            events_queue, (release, deadline, release, app, idx, release)
487
        )
488
        release_indices[app] += 1
3✔
489

490
    for app in app_specs:
3✔
491
        enqueue_release(app)
3✔
492

493
    t = 0.0
3✔
494

495
    while events_queue or ready_queue:
3✔
496
        if not ready_queue:
3✔
497
            t = events_queue[0][0]
3✔
498

499
        while events_queue and events_queue[0][0] <= t + EPS:
3✔
500
            event_time, deadline, arrival_time, app, i, ready_time = (
3✔
501
                heapq.heappop(events_queue)
502
            )
503
            heapq.heappush(
3✔
504
                ready_queue,
505
                (deadline, ready_time, arrival_time, app, i, event_time),
506
            )
507

508
        if not ready_queue:
3✔
509
            continue
×
510

511
        while ready_queue:
3✔
512
            deadline, rdy_t, arrival_time, app, i, _event_time = heapq.heappop(
3✔
513
                ready_queue
514
            )
515
            min_arrival = min(min_arrival, float(arrival_time))
3✔
516

517
            pga_name = f"{app}{i}"
3✔
518
            if pga_name not in seen_pgas:
3✔
519
                seen_pgas.add(pga_name)
3✔
520
                pga_names.append(pga_name)
3✔
521
                pga_release_times[pga_name] = arrival_time
3✔
522

523
            route_links = pga_route_links.get(app, [])
3✔
524
            duration = durations.get(app, 0.0)
3✔
525

526
            last_available = 0.0
3✔
527
            for link in route_links:
3✔
528
                last_available = max(last_available, resources.get(link, 0.0))
3✔
529

530
            if last_available > t + EPS:
3✔
531
                if last_available + duration > deadline + EPS:
×
532
                    if (app, i) not in drop_logged:
×
533
                        drop_logged.add((app, i))
×
534

535
                        turnaround = max(0.0, t - arrival_time)
×
536
                        burst = 0.0
×
537
                        wait = max(0.0, t - rdy_t)
×
538
                        result = {
×
539
                            "pga": pga_name,
540
                            "arrival_time": arrival_time,
541
                            "ready_time": rdy_t,
542
                            "start_time": np.nan,
543
                            "burst_time": burst,
544
                            "completion_time": t,
545
                            "turnaround_time": turnaround,
546
                            "waiting_time": wait,
547
                            "pairs_generated": 0,
548
                            "status": "drop",
549
                            "deadline": deadline,
550
                        }
551
                        log.append(result)
×
552

NEW
553
                        track_link_waiting(
×
554
                            wait,
555
                            link_waiting,
556
                            blocking_links=None
557
                        )
558

559
                    if inst_req[app] > completed_instances[app]:
×
560
                        enqueue_release(app)
×
561
                else:
562
                    turnaround = max(0.0, t - arrival_time)
×
563
                    burst = 0.0
×
564
                    wait = max(0.0, t - rdy_t)
×
565
                    result = {
×
566
                        "pga": pga_name,
567
                        "arrival_time": arrival_time,
568
                        "ready_time": rdy_t,
569
                        "start_time": np.nan,
570
                        "burst_time": burst,
571
                        "completion_time": t,
572
                        "turnaround_time": turnaround,
573
                        "waiting_time": wait,
574
                        "pairs_generated": 0,
575
                        "status": "defer",
576
                        "deadline": deadline,
577
                    }
578
                    log.append(result)
×
579
                    heapq.heappush(
×
580
                        events_queue,
581
                        (
582
                            last_available,
583
                            deadline,
584
                            arrival_time,
585
                            app,
586
                            i,
587
                            rdy_t,
588
                        )
589
                    )
590
                continue
×
591

592
            start_time = t
3✔
593
            period = periods[app]
3✔
594
            completion = start_time + duration
3✔
595

596
            if completion > deadline + EPS or duration > period + EPS:
3✔
597
                turnaround = max(0.0, start_time - arrival_time)
×
598
                wait = max(0.0, start_time - rdy_t)
×
599
                result = {
×
600
                    "pga": pga_name,
601
                    "arrival_time": arrival_time,
602
                    "ready_time": float(rdy_t),
603
                    "start_time": start_time,
604
                    "burst_time": 0.0,
605
                    "completion_time": start_time,
606
                    "turnaround_time": turnaround,
607
                    "waiting_time": wait,
608
                    "pairs_generated": 0,
609
                    "status": "drop",
610
                    "deadline": deadline,
611
                }
612
                log.append(result)
×
613
                track_link_waiting(
×
614
                    result["waiting_time"], link_waiting, blocking_links=None
615
                )
616

617
                if duration > period + EPS:
×
618
                    completed_instances[app] += 1
×
619

620
                if inst_req[app] > completed_instances[app]:
×
621
                    enqueue_release(app)
×
622
                continue
×
623

624
            pga = PGA(
3✔
625
                name=pga_name,
626
                arrival=arrival_time,
627
                start=start_time,
628
                end=completion,
629
                route=pga_network_paths[app],
630
                resources=resources,
631
                link_busy=link_busy,
632
                p_gen=pga_parameters[app]["p_gen"],
633
                epr_pairs=int(pga_parameters[app]["epr_pairs"]),
634
                slot_duration=pga_parameters[app]["slot_duration"],
635
                rng=rng,
636
                log=log,
637
                policy=app_specs[app].get("policy"),
638
                p_swap=pga_parameters[app]["p_swap"],
639
                memory=pga_parameters[app]["memory"],
640
                deadline=deadline,
641
                route_links=route_links,
642
            )
643
            result = pga.run()
3✔
644
            result["ready_time"] = float(rdy_t)
3✔
645
            result["waiting_time"] = max(0.0, start_time - rdy_t)
3✔
646

647
            track_link_waiting(
3✔
648
                result.get("waiting_time", 0.0),
649
                link_waiting,
650
                blocking_links=result.get("blocking_links"),
651
            )
652

653
            max_completion = max(max_completion, result["completion_time"])
3✔
654

655
            status = result.get("status", "")
3✔
656
            if status == "completed":
3✔
657
                completed_instances[app] += 1
3✔
658
                if inst_req[app] > completed_instances[app]:
3✔
659
                    enqueue_release(app)
×
660
                continue
3✔
661

662
            next_ready_time = result["completion_time"] + EPS
×
663
            if next_ready_time + duration <= deadline + EPS:
×
664
                if status == "failed":
×
665
                    result["status"] = "retry"
×
666
                heapq.heappush(
×
667
                    events_queue,
668
                    (
669
                        next_ready_time,
670
                        deadline,
671
                        arrival_time,
672
                        app,
673
                        i,
674
                        next_ready_time,
675
                    )
676
                )
677
            else:
678
                if inst_req[app] > completed_instances[app]:
×
679
                    enqueue_release(app)
×
680

681
    df = pd.DataFrame(log)
3✔
682
    link_utilization = compute_link_utilization(
3✔
683
        link_busy, min_arrival, max_completion
684
    )
685

686
    for link in all_links:
3✔
687
        link_waiting.setdefault(
3✔
688
            link, {"total_waiting_time": 0.0, "pga_waited": 0}
689
        )
690

691
    return df, pga_names, pga_release_times, link_utilization, link_waiting
3✔
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