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

Quang00 / qnscheduling / 21183042606

20 Jan 2026 06:24PM UTC coverage: 32.662% (-0.2%) from 32.873%
21183042606

push

github

Quang00
clean

357 of 1093 relevant lines covered (32.66%)

0.98 hits per line

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

47.18
/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

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

162
        current_time = self.start
3✔
163
        t_budget = max(0.0, self.end - self.start)
3✔
164
        status = "failed"
3✔
165

166
        if t_budget > EPS and self.policy == "deadline":
3✔
167
            max_attempts = int((t_budget + EPS) // self.slot_duration)
3✔
168
            succ = self._simulate_e2e_attempts(max_attempts)
3✔
169
            csum = (
3✔
170
                np.cumsum(succ, dtype=int)
171
                if len(succ)
172
                else np.array([], dtype=int)
173
            )
174
            hit = (
3✔
175
                np.searchsorted(csum, self.epr_pairs, side="left")
176
                if len(csum)
177
                else len(csum)
178
            )
179

180
            if len(csum) and hit < len(csum):
3✔
181
                attempts_run = int(hit + 1)
3✔
182
                pairs_generated = int(csum[attempts_run - 1])
3✔
183
                status = "completed"
3✔
184
            else:
185
                attempts_run = max_attempts
3✔
186
                pairs_generated = int(csum[-1]) if len(csum) else 0
3✔
187

188
            current_time = self.start + attempts_run * self.slot_duration
3✔
189
            completion = min(self.end, current_time)
3✔
190
            self._update_resources_and_links(completion, attempts_run)
3✔
191
        elif self.policy == "best_effort":
×
192
            while pairs_generated < self.epr_pairs:
×
193
                succ = self._simulate_e2e_attempts(1)
×
194
                pairs_generated += int(succ.sum())
×
195
                attempts_run += 1
×
196

197
            completion = self.start + attempts_run * self.slot_duration
×
198
            status = (
×
199
                "completed" if pairs_generated >= self.epr_pairs
200
                else "failed"
201
            )
202
            self._update_resources_and_links(completion, attempts_run)
×
203
        else:
204
            completion = self.start
×
205

206
        burst = completion - self.start
3✔
207
        turnaround = max(0.0, completion - self.arrival)
3✔
208
        waiting = max(0.0, turnaround - burst)
3✔
209

210
        result = {
3✔
211
            "pga": self.name,
212
            "arrival_time": self.arrival,
213
            "start_time": self.start,
214
            "burst_time": burst,
215
            "completion_time": completion,
216
            "turnaround_time": turnaround,
217
            "waiting_time": waiting,
218
            "pairs_generated": pairs_generated,
219
            "status": status,
220
            "deadline": self.deadline if self.policy == "deadline" else None,
221
        }
222
        self.log.append(result)
3✔
223
        return result
3✔
224

225

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

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

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

281
    instances_required = {
3✔
282
        app: max(0, int(app_specs.get(app, {}).get("instances", 0)))
283
        for app in pga_network_paths
284
    }
285
    total_required = sum(instances_required.values())
3✔
286
    completed_instances = {app: 0 for app in instances_required}
3✔
287
    completed_total = 0
3✔
288

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

306
    for pga_name, sched_start, sched_end, sched_deadline in schedule:
3✔
307
        m = INIT_PGA_RE.match(pga_name)
3✔
308
        app, idx = (m.group(1), int(m.group(2))) if m else (pga_name, 0)
3✔
309

310
        required = instances_required.get(app, 0)
3✔
311
        if required > 0 and completed_instances[app] >= required:
3✔
312
            continue
×
313

314
        r0 = float(pga_rel_times.get(app, 0.0))
3✔
315
        T = float(pga_periods.get(app, 0.0))
3✔
316
        arrival = r0 + idx * T
3✔
317
        min_arrival = min(min_arrival, arrival)
3✔
318

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

346
        pga_names.append(pga_name)
3✔
347
        pga_release_times[pga_name] = sched_start
3✔
348
        max_completion = max(max_completion, result["completion_time"])
3✔
349

350
        if result.get("status") == "completed":
3✔
351
            completed_instances[app] += 1
3✔
352
            completed_total += 1
3✔
353
            if completed_total == total_required:
3✔
354
                break
3✔
355

356
    df = pd.DataFrame(log)
3✔
357
    link_utilization = compute_link_utilization(
3✔
358
        link_busy,
359
        min_arrival,
360
        max_completion,
361
    )
362

363
    return df, pga_names, pga_release_times, link_utilization, link_waiting
3✔
364

365

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

388
    The scheduler made the following decisions for each PGA:
389
    - If a PGA cannot start by its deadline due to busy links, it is marked as
390
      "drop".
391
    - If a PGA cannot be completed due to its duration exceeding the deadline,
392
      it is marked as "drop".
393
    - If a PGA does not generate the required E2E EPR pairs within its time
394
      window, it may be retried if there is time before the deadline.
395
    - If a PGA cannot start immediately due to busy links but can still
396
      complete within its deadline, it is marked as "defer" and rescheduled
397
      to start when resources become available.
398
    - If a PGA successfully generates the required E2E EPR pairs within its
399
      time window, it is marked as "completed".
400

401
    Args:
402
        app_specs (Dict[str, Dict[str, Any]]): Application specifications.
403
        durations (Dict[str, float]): Duration of each application.
404
        pga_parameters (Dict[str, Dict[str, float]]): Parameters for each PGA.
405
        pga_rel_times (Dict[str, float]): Release times for each PGA.
406
        pga_network_paths (Dict[str, List[str]]): Network paths for each PGA.
407
        rng (np.random.Generator): Random number generator.
408
        arrival_rate (float | None): Mean rate lambda for Poisson arrivals.
409
        When None, arrivals remain periodic.
410

411
    Returns:
412
        Tuple[
413
            pd.DataFrame,
414
            List[str],
415
            Dict[str, float],
416
            Dict[Tuple[str, str], Dict[str, float]],
417
        ]: Contains:
418
            - DataFrame with PGA performance metrics.
419
            - List of PGA names.
420
            - Dictionary mapping PGA names to their release times.
421
            - Dictionary mapping undirected links to busy time and utilization.
422
            - Dictionary mapping undirected links to waiting metrics.
423
    """
424
    log = []
×
425
    pga_release_times = {}
×
426
    pga_names = []
×
427
    seen_pgas = set()
×
428
    drop_logged = set()
×
429

430
    pga_route_links = {
×
431
        app: [
432
            tuple(sorted((u, v)))
433
            for u, v in zip(path[:-1], path[1:], strict=False)
434
        ]
435
        for app, path in pga_network_paths.items()
436
    }
437
    all_links = {link for links in pga_route_links.values() for link in links}
×
438
    resources = {link: 0.0 for link in all_links}
×
439
    link_busy = dict.fromkeys(all_links, 0.0)
×
440
    link_waiting = {
×
441
        link: {"total_waiting_time": 0.0, "pga_waited": 0}
442
        for link in all_links
443
    }
444
    min_arrival = float("inf")
×
445
    max_completion = 0.0
×
446

447
    periods = {app: app_specs[app].get("period") for app in app_specs}
×
448
    inst_req = {app: app_specs[app].get("instances") for app in app_specs}
×
449
    base_release = {app: pga_rel_times.get(app, 0.0) for app in app_specs}
×
450
    completed_instances = {app: 0 for app in app_specs}
×
451
    release_indices = {app: 0 for app in app_specs}
×
452
    poisson_enabled = arrival_rate is not None and arrival_rate > 0.0
×
453
    poisson = (1.0 / arrival_rate) if poisson_enabled else None
×
454
    poisson_next_release = (
×
455
        {
456
            app: base_release.get(app, 0.0) + rng.exponential(poisson)
457
            for app in app_specs
458
        }
459
        if poisson_enabled
460
        else {}
461
    )
462

463
    events_queue = []
×
464
    ready_queue = []
×
465

466
    def enqueue_release(app: str) -> None:
×
467
        if inst_req[app] <= completed_instances[app]:
×
468
            return
×
469
        idx = release_indices[app]
×
470
        period = periods[app]
×
471

472
        if poisson_enabled:
×
473
            release = poisson_next_release.get(app, base_release[app])
×
474
            poisson_next_release[app] = release + rng.exponential(poisson)
×
475
        else:
476
            release = base_release[app] + period * idx
×
477

478
        deadline = release + period
×
479
        heapq.heappush(
×
480
            events_queue, (release, deadline, release, app, idx, release)
481
        )
482
        release_indices[app] += 1
×
483

484
    for app in app_specs:
×
485
        enqueue_release(app)
×
486

487
    t = 0.0
×
488

489
    while events_queue or ready_queue:
×
490
        if not ready_queue:
×
491
            t = events_queue[0][0]
×
492

493
        while events_queue and events_queue[0][0] <= t + EPS:
×
494
            event_time, deadline, arrival_time, app, i, ready_time = (
×
495
                heapq.heappop(events_queue)
496
            )
497
            heapq.heappush(
×
498
                ready_queue,
499
                (deadline, ready_time, arrival_time, app, i, event_time),
500
            )
501

502
        if not ready_queue:
×
503
            continue
×
504

505
        while ready_queue:
×
506
            deadline, rdy_t, arrival_time, app, i, _event_time = heapq.heappop(
×
507
                ready_queue
508
            )
509
            min_arrival = min(min_arrival, float(arrival_time))
×
510

511
            pga_name = f"{app}{i}"
×
512
            if pga_name not in seen_pgas:
×
513
                seen_pgas.add(pga_name)
×
514
                pga_names.append(pga_name)
×
515
                pga_release_times[pga_name] = arrival_time
×
516

517
            route_links = pga_route_links.get(app, [])
×
518
            duration = durations.get(app, 0.0)
×
519

520
            last_available = 0.0
×
521
            for link in route_links:
×
522
                last_available = max(last_available, resources.get(link, 0.0))
×
523

524
            if last_available > t + EPS:
×
525
                if last_available + duration > deadline + EPS:
×
526
                    if (app, i) not in drop_logged:
×
527
                        drop_logged.add((app, i))
×
528

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

547
                        track_link_waiting(route_links, wait, link_waiting)
×
548

549
                    if inst_req[app] > completed_instances[app]:
×
550
                        enqueue_release(app)
×
551
                else:
552
                    turnaround = max(0.0, t - arrival_time)
×
553
                    burst = 0.0
×
554
                    wait = max(0.0, t - rdy_t)
×
555
                    result = {
×
556
                        "pga": pga_name,
557
                        "arrival_time": arrival_time,
558
                        "ready_time": rdy_t,
559
                        "start_time": np.nan,
560
                        "burst_time": burst,
561
                        "completion_time": t,
562
                        "turnaround_time": turnaround,
563
                        "waiting_time": wait,
564
                        "pairs_generated": 0,
565
                        "status": "defer",
566
                        "deadline": deadline,
567
                    }
568
                    log.append(result)
×
569
                    heapq.heappush(
×
570
                        events_queue,
571
                        (
572
                            last_available,
573
                            deadline,
574
                            arrival_time,
575
                            app,
576
                            i,
577
                            rdy_t,
578
                        )
579
                    )
580
                continue
×
581

582
            start_time = t
×
583
            period = periods[app]
×
584
            completion = start_time + duration
×
585

586
            if completion > deadline + EPS or duration > period + EPS:
×
587
                turnaround = max(0.0, start_time - arrival_time)
×
588
                wait = max(0.0, start_time - rdy_t)
×
589
                result = {
×
590
                    "pga": pga_name,
591
                    "arrival_time": arrival_time,
592
                    "ready_time": float(rdy_t),
593
                    "start_time": start_time,
594
                    "burst_time": 0.0,
595
                    "completion_time": start_time,
596
                    "turnaround_time": turnaround,
597
                    "waiting_time": wait,
598
                    "pairs_generated": 0,
599
                    "status": "drop",
600
                    "deadline": deadline,
601
                }
602
                log.append(result)
×
603
                track_link_waiting(
×
604
                    route_links, result["waiting_time"], link_waiting
605
                )
606

607
                if duration > period + EPS:
×
608
                    completed_instances[app] += 1
×
609

610
                if inst_req[app] > completed_instances[app]:
×
611
                    enqueue_release(app)
×
612
                continue
×
613

614
            pga = PGA(
×
615
                name=pga_name,
616
                arrival=arrival_time,
617
                start=start_time,
618
                end=completion,
619
                route=pga_network_paths[app],
620
                resources=resources,
621
                link_busy=link_busy,
622
                p_gen=pga_parameters[app]["p_gen"],
623
                epr_pairs=int(pga_parameters[app]["epr_pairs"]),
624
                slot_duration=pga_parameters[app]["slot_duration"],
625
                rng=rng,
626
                log=log,
627
                policy=app_specs[app].get("policy"),
628
                p_swap=pga_parameters[app]["p_swap"],
629
                memory=pga_parameters[app]["memory"],
630
                deadline=deadline,
631
                route_links=route_links,
632
            )
633
            result = pga.run()
×
634
            result["ready_time"] = float(rdy_t)
×
635
            result["waiting_time"] = max(0.0, start_time - rdy_t)
×
636

637
            track_link_waiting(
×
638
                route_links, result.get("waiting_time", 0.0), link_waiting
639
            )
640

641
            max_completion = max(max_completion, result["completion_time"])
×
642

643
            status = result.get("status", "")
×
644
            if status == "completed":
×
645
                completed_instances[app] += 1
×
646
                if inst_req[app] > completed_instances[app]:
×
647
                    enqueue_release(app)
×
648
                continue
×
649

650
            next_ready_time = result["completion_time"] + EPS
×
651
            if next_ready_time + duration <= deadline + EPS:
×
652
                if status == "failed":
×
653
                    result["status"] = "retry"
×
654
                heapq.heappush(
×
655
                    events_queue,
656
                    (
657
                        next_ready_time,
658
                        deadline,
659
                        arrival_time,
660
                        app,
661
                        i,
662
                        next_ready_time,
663
                    )
664
                )
665
            else:
666
                if status == "failed":
×
667
                    result["status"] = "drop"
×
668
                if inst_req[app] > completed_instances[app]:
×
669
                    enqueue_release(app)
×
670

671
    df = pd.DataFrame(log)
×
672
    link_utilization = compute_link_utilization(
×
673
        link_busy, min_arrival, max_completion
674
    )
675

676
    for link in all_links:
×
677
        link_waiting.setdefault(
×
678
            link, {"total_waiting_time": 0.0, "pga_waited": 0}
679
        )
680

681
    return df, pga_names, pga_release_times, link_utilization, link_waiting
×
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