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

Quang00 / dqsweep / 13953014193

19 Mar 2025 05:33PM UTC coverage: 82.288% (-0.07%) from 82.362%
13953014193

push

github

Quang00
change to reliable tests

7 of 7 new or added lines in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

525 of 638 relevant lines covered (82.29%)

1.65 hits per line

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

59.73
/experiments/utils.py
1
import copy
2✔
2
import itertools
2✔
3
import os
2✔
4
from typing import Callable, Generator, List, Union
2✔
5

6
import matplotlib.pyplot as plt
2✔
7
import numpy as np
2✔
8
import pandas as pd
2✔
9
from netqasm.sdk import Qubit
2✔
10
from netsquid.qubits.dmutil import dm_fidelity
2✔
11

12
from squidasm.run.stack.config import StackNetworkConfig
2✔
13
from squidasm.run.stack.run import run
2✔
14
from squidasm.sim.stack.program import ProgramContext
2✔
15
from squidasm.util import get_qubit_state
2✔
16
from squidasm.util.routines import teleport_recv, teleport_send
2✔
17

18
# =============================================================================
19
# Constants
20
# =============================================================================
21
LOG_SCALE_PARAMS = {
2✔
22
    "length",
23
    "T1",
24
    "T2",
25
    "single_qubit_gate_time",
26
    "two_qubit_gate_time",
27
}
28

29

30
# =============================================================================
31
# Helper Functions
32
# =============================================================================
33
def truncate_param(name: str, char: str = "_", n: int = 4) -> str:
2✔
34
    """Truncates a parameter name to improve readability in plots.
35

36
    Args:
37
        name (str): Parameter name to truncate.
38
        char (str, optional): Character to split the name. Defaults to "_".
39
        n (int, optional): Number of tokens to keep. Defaults to 4.
40

41
    Returns:
42
        str: Truncated parameter name.
43
    """
44
    return " ".join(name.split(char)[:n]).capitalize()
2✔
45

46

47
def create_subdir(
2✔
48
    directory: str, experiment: str, sweep_params: Union[list, str]
49
) -> str:
50
    """Creates a structured subdirectory for storing experiment results.
51

52
    Args:
53
        directory (str): The main results directory (e.g., "results").
54
        experiment (str): The experiment name (e.g., "pingpong").
55
        sweep_params (list | str): List of swept parameters (or a string).
56

57
    Returns:
58
        str: Path to the created experiment-specific directory.
59
    """
60
    if not os.path.exists(directory):
2✔
61
        os.makedirs(directory)
2✔
62

63
    if isinstance(sweep_params, str):
2✔
64
        sweep_params = sweep_params.split(",")
2✔
65

66
    sweep_params = [param.strip() for param in sweep_params]
2✔
67
    param_names = (
2✔
68
        "_".join(truncate_param(param, n=1) for param in sweep_params)
69
        if sweep_params
70
        else "default"
71
    )
72
    experiment_dir = os.path.join(directory, f"{experiment}_{param_names}")
2✔
73
    cpt = 1
2✔
74
    new_subdir = experiment_dir
2✔
75

76
    while os.path.exists(new_subdir):
2✔
77
        new_subdir = f"{experiment_dir}_{cpt}"
2✔
78
        cpt += 1
2✔
79

80
    os.makedirs(new_subdir)
2✔
81

82
    return new_subdir
2✔
83

84

85
def parse_range(range_str: str, param_name: str) -> np.ndarray:
2✔
86
    """Parses a range string and returns a numpy array of values.
87

88
    Uses logarithmic scaling if the parameter is in LOG_SCALE_PARAMS.
89

90
    Args:
91
        range_str (str): Range in format "start,end,points".
92
        param_name (str): The name of the parameter being parsed.
93

94
    Returns:
95
        np.ndarray: Array of evenly spaced values.
96
    """
97
    try:
2✔
98
        start, end, points = map(float, range_str.split(","))
2✔
99
        if param_name in LOG_SCALE_PARAMS:
2✔
100
            return np.logspace(start, end, int(points))
2✔
101
        return np.linspace(start, end, int(points))
2✔
102
    except ValueError:
×
103
        raise ValueError("Invalid format. Use 'start,end,points'.") from None
×
104

105

106
def compute_fidelity(
2✔
107
    qubit: Qubit, owner: str, dm_state: np.ndarray, full_state: bool = True
108
) -> float:
109
    """Computes the fidelity between the density matrix of a given qubit and
110
    the density matrix constructed from a reference state vector.
111

112
    Args:
113
        qubit (Qubit): The qubit whose state is to be evaluated.
114
        owner (str): The owner of the qubit.
115
        dm_state (np.ndarray): The reference state vector.
116
        full_state (bool): Flag to retrieve the full entangled state.
117

118
    Returns:
119
        float: The fidelity between the two density matrices.
120
    """
121
    dm = get_qubit_state(qubit, owner, full_state=full_state)
2✔
122
    dm_ref = np.outer(dm_state, np.conjugate(dm_state))
2✔
123
    fidelity = dm_fidelity(dm, dm_ref, dm_check=False)
2✔
124

125
    return fidelity
2✔
126

127

128
def metric_correlation(
2✔
129
    df: pd.DataFrame,
130
    sweep_params: list,
131
    metric_cols: list,
132
    output_dir: str,
133
    experiment: str,
134
):
135
    """Computes and generate a txt file for parameter-performance correlation.
136

137
    Args:
138
        df (pd.DataFrame): Dataframe containing parameter values and metrics.
139
        sweep_params (list): List of parameters being swept.
140
        metric_cols (list): List of performance metrics.
141
        output_dir (str): Directory to save the plot.
142
        experiment (str): Experiment name.
143
    """
144
    # Calculate the correlation for the selected columns
145
    corr = df[sweep_params + metric_cols].corr().loc[sweep_params, metric_cols]
×
146
    filename = os.path.join(output_dir, f"{experiment}_corr.txt")
×
147

148
    with open(filename, "w") as f:
×
149
        f.write("Parameter\t" + "\t".join(metric_cols) + "\n")
×
150
        for param in sweep_params:
×
151
            row = [f"{corr.loc[param, metric]:.3f}" for metric in metric_cols]
×
152
            f.write(f"{param}\t" + "\t".join(row) + "\n")
×
153

154
    print(f"Saved correlation values to {filename}")
×
155

156

157
def run_simulation(
2✔
158
    config: StackNetworkConfig,
159
    programs: dict = None,
160
) -> float:
161
    """Runs a simulation with the given configuration and program classes.
162

163
    Args:
164
        config (str): Path to the network configuration YAML file.
165
        programs (dict): A dictionary mapping program names to their classes.
166

167
    Returns:
168
        float: Average fidelity of the simulation.
169
    """
170
    if programs is None or not programs:
2✔
171
        raise ValueError("At least one class must be provided.")
×
172

173
    times = 9
2✔
174
    programs = {
2✔
175
        name: cls(num_epr_rounds=times) for name, cls in programs.items()
176
    }
177

178
    # Run the simulation with the provided configuration.
179
    all_results = run(
2✔
180
        config=config,
181
        programs=programs,
182
        num_times=times,
183
    )
184

185
    results = all_results[len(programs) - 1]
2✔
186

187
    # Compute the average fidelity.
188
    all_fid_results = [res[0] for res in results]
2✔
189
    avg_fidelity = np.mean(all_fid_results)
2✔
190

191
    return avg_fidelity
2✔
192

193

194
def check_sweep_params_input(cfg: StackNetworkConfig, params: str) -> bool:
2✔
195
    """Check that all sweep parameters are found in the configuration.
196

197
    Args:
198
        cfg (StackNetworkConfig): Network configuration.
199
        params (str): Parameters to sweep.
200

201
    Returns:
202
        bool: True if all sweep parameters are found.
203

204
    Raises:
205
        ValueError: If one or more sweep parameters are missing in the config.
206
    """
207
    sweep_set = {param.strip() for param in params.split(",")}
2✔
208
    cfg_set = {token.strip("',:") for token in str(cfg).split()}
2✔
209

210
    missing = sweep_set - cfg_set
2✔
211
    if missing:
2✔
212
        raise ValueError("Please provide parameters that exist in the config.")
2✔
213
    return True
2✔
214

215

216
def update_cfg(cfg: StackNetworkConfig, map_param: dict) -> None:
2✔
217
    """Update the configuration for every link and stack with the
218
    given parameter values.
219

220
    Args:
221
        cfg (StackNetworkConfig): Network configuration.
222
        map_param (dict): Map parameter names to their values.
223
    """
224
    for param, value in map_param.items():
×
225
        for link in cfg.links:
×
226
            if getattr(link, "link_cfg", None) is not None:
×
227
                link.cfg[param] = value
×
228
        for stack in cfg.stacks:
×
229
            if getattr(stack, "qdevice_cfg", None) is not None:
×
230
                stack.qdevice_cfg[param] = value
×
231

232

233
def parallelize_comb(comb, cfg, sweep_params, num_experiments, programs):
2✔
234
    """Parallelize a single combination of parameters
235

236
    Args:
237
        comb (tuple): One combination of parameters value.
238
        cfg (StackNetworkConfig): Network configuration.
239
        sweep_params (list): Parameters to sweep.
240
        num_experiments (int): Number of experiments per configuration.
241
        programs (dict): Progam that map a name to class.
242

243
    Returns:
244
        dict: A dictionary with the parameter mapping and computed results.
245
    """
246
    # Map parameter name to value.
247
    map_param = dict(zip(sweep_params, comb, strict=False))
×
248

249
    local_cfg = copy.deepcopy(cfg)
×
250
    update_cfg(local_cfg, map_param)
×
251

252
    res = run(config=local_cfg, programs=programs, num_times=num_experiments)
×
253
    last_program_results = res[len(programs) - 1]
×
254

255
    all_fid_results = [r[0] for r in last_program_results]
×
256
    all_time_results = [r[1] for r in last_program_results]
×
257

258
    avg_fid = np.mean(all_fid_results) * 100
×
259
    avg_time = np.mean(all_time_results)
×
260

261
    return {
×
262
        **map_param,
263
        "Fidelity Results": all_fid_results,
264
        "Simulation Time Results": all_time_results,
265
        "Average Fidelity (%)": avg_fid,
266
        "Average Simulation Time (ms)": avg_time,
267
    }
268

269

270
# =============================================================================
271
# Plotting Functions
272
# =============================================================================
273
def plot_heatmap(
2✔
274
    ax,
275
    df: pd.DataFrame,
276
    p: str,
277
    q: str,
278
    metric: dict,
279
    params: dict,
280
    exp: str,
281
    rounds: int,
282
):
283
    """Plot an individual heatmap on a given axes.
284

285
    Args:
286
        ax: Matplotlib Axes object.
287
        df (pd.DataFrame): Dataframe containing experiment results.
288
        p (str): Name of the parameter for the y-axis.
289
        q (str): Name of the parameter for the x-axis.
290
        metric (dict): Dictionary with keys 'name', 'cmap', and 'file_label'.
291
        params (dict): Dictionary mapping parameters to their ranges.
292
        exp (str): Name of the experiment.
293
        rounds (int): Number of epr rounds.
294
    """
295
    pivot = df.pivot_table(index=p, columns=q, values=metric["name"])
×
296
    metric_matrix = pivot.values
×
297

298
    im = ax.imshow(
×
299
        metric_matrix,
300
        extent=[
301
            params[q][0],
302
            params[q][-1],  # X-axis
303
            params[p][0],
304
            params[p][-1],  # Y-axis
305
        ],
306
        origin="lower",
307
        aspect="auto",
308
        cmap=metric["cmap"],
309
    )
310

311
    cbar = ax.figure.colorbar(im, ax=ax)
×
312
    cbar.set_label(metric["name"], fontsize=14)
×
313

314
    var1 = truncate_param(q)
×
315
    var2 = truncate_param(p)
×
316

317
    ax.set_xlabel(var1, fontsize=14)
×
318
    ax.set_ylabel(var2, fontsize=14)
×
319

320
    title_suffix = f" with {rounds} hops" if exp == "pingpong" else ""
×
321
    ax.set_title(
×
322
        f"{exp.capitalize()}: {var2} vs {var1}{title_suffix}",
323
        fontsize=16,
324
        fontweight="bold",
325
    )
326

327

328
def save_single_heatmap(
2✔
329
    metric: dict,
330
    df: pd.DataFrame,
331
    pairs: list,
332
    params: dict,
333
    exp: str,
334
    rounds: int,
335
    dir: str,
336
) -> None:
337
    """Generate and save a heatmap for a single metric.
338

339
    Args:
340
        metric (dict): Metric details (keys: 'name', 'cmap', 'file_label').
341
        df (pd.DataFrame): DataFrame containing experiment results.
342
        pairs (list): List of parameter pairs generated from sweep_params.
343
        params (dict): Dictionary mapping parameters to their ranges .
344
        exp (str): Name of the experiment.
345
        rounds (int): Number of EPR rounds.
346
        dir (str): Directory to save the generated figures.
347
    """
348
    figsize = (12 * len(pairs), 8)
×
349
    fig, axes = plt.subplots(1, len(pairs), figsize=figsize)
×
350

351
    # Ensure axes is iterable.
352
    if len(pairs) == 1:
×
353
        axes = [axes]
×
354

355
    for ax, (p, q) in zip(axes, pairs, strict=False):
×
356
        plot_heatmap(ax, df, p, q, metric, params, exp, rounds)
×
357

358
    plt.tight_layout()
×
359
    prefix = f"{exp}_heat_{metric['file_label']}"
×
360
    suffix = f"{(rounds + 1) // 2}" if exp == "pingpong" else ""
×
361
    filename = os.path.join(dir, f"{prefix}_{suffix}.png")
×
362
    plt.savefig(filename, dpi=300)
×
363
    plt.close(fig)
×
364
    print(f"Saved {metric['name']} heatmap to {filename}")
×
365

366

367
def save_combined_heatmaps(
2✔
368
    metrics: list,
369
    df: pd.DataFrame,
370
    pairs: list,
371
    params: dict,
372
    exp: str,
373
    rounds: int,
374
    dir: str,
375
) -> None:
376
    """Generate and save a combined figure with heatmaps for all metrics.
377

378
    Args:
379
           metrics (list): Metric details (keys: 'name', 'cmap', 'file_label').
380
           df (pd.DataFrame): DataFrame containing experiment results.
381
           pairs (list): List of parameter pairs generated from sweep_params.
382
           params (dict): Dictionary mapping parameters to their ranges.
383
           exp (str): Name of the experiment.
384
           rounds (int): Number of EPR rounds.
385
           dir (str): Directory to save the generated figures.
386
    """
387
    figsize = (12 * len(pairs), 8 * len(metrics))
×
388
    fig, axes = plt.subplots(len(metrics), len(pairs), figsize=figsize)
×
389

390
    # Ensure axes is 2D even when there is only one pair.
391
    if len(pairs) == 1:
×
392
        axes = np.array([axes]).reshape(len(metrics), 1)
×
393

394
    for i, metric in enumerate(metrics):
×
395
        for j, (p, q) in enumerate(pairs):
×
396
            plot_heatmap(axes[i, j], df, p, q, metric, params, exp, rounds)
×
397

398
    plt.tight_layout()
×
399
    filename = os.path.join(dir, f"{exp}_heatmaps.png")
×
400
    plt.savefig(filename, dpi=300)
×
401
    plt.close(fig)
×
402
    print(f"Saved combined heatmaps to {filename}")
×
403

404

405
def plot_combined_heatmaps(
2✔
406
    df: pd.DataFrame,
407
    sweep_params: list,
408
    params: dict,
409
    dir: str,
410
    exp: str,
411
    rounds: int,
412
    separate_files: bool = False,
413
):
414
    """Generates heatmaps from experiment results.
415

416
    If `separate_files` is True, separate figures will be created for each
417
    metric. Otherwise, a single combined figure is generated.
418

419
    Args:
420
        df (pd.DataFrame): DataFrame containing experiment results.
421
        sweep_params (list): List of swept parameters.
422
        params (dict): Dictionary mapping parameters to their ranges.
423
        dir (str): Directory to save the generated figures.
424
        exp (str): Name of the experiment.
425
        rounds (int): Number of EPR rounds.
426
        separate_files (bool, optional): Whether to generate separate files.
427
    """
428
    pairs = list(itertools.combinations(sweep_params, 2))
×
429
    metrics = [
×
430
        {
431
            "name": "Average Fidelity (%)",
432
            "cmap": "magma",
433
            "file_label": "fidelity"
434
        },
435
        {
436
            "name": "Average Simulation Time (ms)",
437
            "cmap": "viridis",
438
            "file_label": "sim_times",
439
        },
440
    ]
441

442
    if separate_files:
×
443
        for metric in metrics:
×
444
            save_single_heatmap(metric, df, pairs, params, exp, rounds, dir)
×
445
    else:
446
        save_combined_heatmaps(metrics, df, pairs, params, exp, rounds, dir)
×
447

448

449
# =============================================================================
450
# Gates
451
# =============================================================================
452
def toffoli(control1: Qubit, control2: Qubit, target: Qubit) -> None:
2✔
453
    """Performs a Toffoli gate with `control1` and `control2` as control qubits
454
    and `target` as target, using CNOT, Rz and Hadamard gates.
455

456
    See https://en.wikipedia.org/wiki/Toffoli_gate
457

458
    Args:
459
        control1 (Qubit): First control qubit.
460
        control2 (Qubit): Second control qubit.
461
        target (Qubit): Target qubit.
462
    """
463
    target.H()
2✔
464
    control2.cnot(target)
2✔
465
    target.rot_Z(angle=-np.pi / 4)
2✔
466
    control1.cnot(target)
2✔
467
    target.rot_Z(angle=np.pi / 4)
2✔
468
    control2.cnot(target)
2✔
469
    target.rot_Z(angle=-np.pi / 4)
2✔
470
    control1.cnot(target)
2✔
471
    control2.rot_Z(angle=np.pi / 4)
2✔
472
    target.rot_Z(angle=np.pi / 4)
2✔
473
    target.H()
2✔
474
    control1.cnot(control2)
2✔
475
    control1.rot_Z(angle=np.pi / 4)
2✔
476
    control2.rot_Z(angle=-np.pi / 4)
2✔
477
    control1.cnot(control2)
2✔
478

479

480
def ccz(control1: Qubit, control2: Qubit, target: Qubit) -> None:
2✔
481
    """Performs a CCZ gate with `control1` and `control2` as control qubits
482
    and `target` as target, using Toffoli and Hadamard gates.
483

484
    Args:
485
        control1 (Qubit): First control qubit.
486
        control2 (Qubit): Second control qubit.
487
        target (Qubit): Target qubit.
488
    """
489
    target.H()
×
490
    toffoli(control1, control2, target)
×
491
    target.H()
×
492

493

494
def n_qubit_controlled_u(
2✔
495
    controls_qubit: List[Qubit],
496
    context: ProgramContext,
497
    controlled_u_gate: Callable,
498
    target: Qubit,
499
) -> None:
500
    """Performs an n-qubit controlled-U gate with `controls_qubit` as controls
501
    and `target` as target, using ancillas qubit, Toffoli gates and a
502
    `controlled_u_gate` as controlled-U gate.
503

504
    The implementation is from "M. A. Nielsen and I. L. Chuang, Quantum Comput-
505
    ation and Quantum Information: 10th Anniversary Edition. Figure 4.10.".
506

507
    Args:
508
        controls_qubit (List[Qubit]): The list of n control qubits.
509
        context (ProgramContext): Context of the current program.
510
        controlled_u_gate (Callable): The controlled-U gate.
511
        target (Qubit): Target qubit.
512
    """
513

514
    n = len(controls_qubit)
×
515
    if n == 0 or n == 1:
×
516
        controlled_u_gate(controls_qubit[0], target)
×
517
        return
×
518

519
    ancillas = [Qubit(context.connection) for _ in range(n - 1)]
×
520

521
    toffoli(controls_qubit[0], controls_qubit[1], ancillas[0])
×
522
    for i in range(2, n):
×
523
        toffoli(controls_qubit[i], ancillas[i - 2], ancillas[i - 1])
×
524

525
    controlled_u_gate(ancillas[-1], target)
×
526

527
    for i in reversed(range(2, n)):
×
528
        toffoli(controls_qubit[i], ancillas[i - 2], ancillas[i - 1])
×
529
    toffoli(controls_qubit[0], controls_qubit[1], ancillas[0])
×
530

531
    for ancilla in ancillas:
×
532
        ancilla.measure()
×
533

534

535
# =============================================================================
536
# Routines
537
# =============================================================================
538
def pingpong_initiator(
2✔
539
    qubit: Qubit, context: ProgramContext, peer_name: str, num_rounds: int = 3
540
):
541
    """Executes the ping‐pong teleportation protocol for the initiator.
542

543
    In even rounds, the provided qubit is sent to the peer.
544
    In odd rounds, the initiator receives the qubit.
545
    The formal return is a generator and requires use of `yield from`
546
    in usage in order to function as intended.
547

548
    Args:
549
        qubit (Qubit): The qubit to be teleported.
550
        context (ProgramContext): Connection, csockets, and epr_sockets.
551
        peer_name (str): Name of the peer.
552
        num_rounds (int): Number of ping‐pong rounds.
553
    """
554
    if num_rounds % 2 == 0:
2✔
UNCOV
555
        raise ValueError("It must be odd for a complete ping-pong exchange.")
×
556

557
    for round_num in range(num_rounds):
2✔
558
        if round_num % 2 == 0:
2✔
559
            # Even round: send the qubit.
560
            yield from teleport_send(qubit, context, peer_name)
2✔
561
        else:
562
            # Odd round: receive a new qubit from the peer.
563
            qubit = yield from teleport_recv(context, peer_name)
2✔
564

565
    yield from context.connection.flush()
2✔
566

567

568
def pingpong_responder(
2✔
569
    context: ProgramContext, peer_name: str, num_rounds: int = 3
570
) -> Generator[None, None, Qubit]:
571
    """Executes the complementary ping‐pong teleportation protocol
572
    for the responder.
573

574
    The responder starts without a qubit and in the first (even) round
575
    receives one. In odd rounds he sends the qubit. After completing
576
    the rounds, Bob returns the final qubit he holds.
577
    The formal return is a generator and requires use of `yield from`
578
    in usage in order to function as intended.
579

580
    Args:
581
        context (ProgramContext): Connection, csockets, and epr_sockets.
582
        peer_name (str): Name of the peer.
583
        num_rounds (int): Number of ping‐pong rounds.
584

585
    Returns:
586
        Generator[None, None, Qubit]: The final teleported qubit.
587
    """
588
    if num_rounds % 2 == 0:
2✔
589
        raise ValueError("It must be odd for a complete ping-pong exchange.")
×
590

591
    qubit = None
2✔
592

593
    for round_num in range(num_rounds):
2✔
594
        if round_num % 2 == 0:
2✔
595
            # Even round: receive a qubit from the peer.
596
            qubit = yield from teleport_recv(context, peer_name)
2✔
597
        else:
598
            # Odd round: send the qubit to the peer.
599
            yield from teleport_send(qubit, context, peer_name)
2✔
600

601
    yield from context.connection.flush()
2✔
602

603
    return qubit
2✔
604

605

606
def distributed_n_qubit_controlled_u_control(
2✔
607
    context: ProgramContext, peer_name: str, ctrl_qubit: Qubit
608
) -> Generator[None, None, None]:
609
    """Performs the n-qubit controlled-U gate, but with one control qubit
610
    located on this node, the target on a remote node. The formal return is a
611
    generator and requires use of `yield from` in usage in order to function
612
    as intended.
613

614
    Args:
615
        context (ProgramContext): Context of the current program.
616
        peer_name (str): Name of the peer.
617
        ctrl_qubit (Qubit): The control qubit.
618
    """
619
    csocket = context.csockets[peer_name]
2✔
620
    epr_socket = context.epr_sockets[peer_name]
2✔
621
    connection = context.connection
2✔
622

623
    epr = epr_socket.create_keep()[0]
2✔
624
    ctrl_qubit.cnot(epr)
2✔
625
    epr_meas = epr.measure()
2✔
626
    yield from connection.flush()
2✔
627

628
    csocket.send(str(epr_meas))
2✔
629
    target_meas = yield from csocket.recv()
2✔
630
    if target_meas == "1":
2✔
631
        ctrl_qubit.Z()
2✔
632

633
    yield from connection.flush()
2✔
634

635

636
def distributed_n_qubit_controlled_u_target(
2✔
637
    context: ProgramContext,
638
    peer_names: List[str],
639
    target_qubit: Qubit,
640
    controlled_u: Callable[..., None],
641
):
642
    """Performs the n-qubit controlled-U gate, but with the target qubit
643
    located on this node, the controls on remote nodes. The formal return is
644
    a generator and requires use of `yield from` in usage in order to function
645
    as intended.
646

647
    Args:
648
        context (ProgramContext): Context of the current program.
649
        peer_names (List[str]): Name of the peers engaging.
650
        target_qubit (Qubit): The target qubit.
651
        controlled_u (Callable[..., None]): The n-qubits gate U with this
652
        signature `U(control_qubit_1, control_qubit_2, ..., target_qubit)`.
653
    """
654
    connection = context.connection
2✔
655

656
    epr_dict = {}
2✔
657
    for peer_name in peer_names:
2✔
658
        epr_dict[peer_name] = context.epr_sockets[peer_name].recv_keep()[0]
2✔
659
    yield from connection.flush()
2✔
660

661
    for peer_name, epr in epr_dict.items():
2✔
662
        m = yield from context.csockets[peer_name].recv()
2✔
663
        if m == "1":
2✔
664
            epr.X()
2✔
665

666
    epr_list = [epr_dict[peer_name] for peer_name in peer_names]
2✔
667
    controlled_u(*epr_list, target_qubit)
2✔
668

669
    epr_meas = {}
2✔
670
    for peer_name, epr in epr_dict.items():
2✔
671
        epr.H()
2✔
672
        epr_meas[peer_name] = epr.measure()
2✔
673
    yield from connection.flush()
2✔
674

675
    for peer_name in peer_names:
2✔
676
        context.csockets[peer_name].send(str(epr_meas[peer_name]))
2✔
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