cirq-core 1.6.0.dev20250502191313__py3-none-any.whl → 1.6.0.dev20250505215959__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of cirq-core might be problematic. Click here for more details.

cirq/_version.py CHANGED
@@ -28,4 +28,4 @@ if sys.version_info < (3, 10, 0): # pragma: no cover
28
28
  'of cirq (e.g. "python -m pip install cirq==1.1.*")'
29
29
  )
30
30
 
31
- __version__ = "1.6.0.dev20250502191313"
31
+ __version__ = "1.6.0.dev20250505215959"
cirq/_version_test.py CHANGED
@@ -3,4 +3,4 @@ import cirq
3
3
 
4
4
 
5
5
  def test_version():
6
- assert cirq.__version__ == "1.6.0.dev20250502191313"
6
+ assert cirq.__version__ == "1.6.0.dev20250505215959"
@@ -0,0 +1,17 @@
1
+ # Copyright 2025 The Cirq Developers
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Tools for branchmarking NISQ circuits."""
16
+
17
+ from cirq.experiments.benchmarking.parallel_xeb import parallel_two_qubit_xeb
@@ -0,0 +1,679 @@
1
+ # Copyright 2025 The Cirq Developers
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """A module for performing and analysing parallel XEB."""
16
+
17
+ from __future__ import annotations
18
+
19
+ from concurrent import futures
20
+ from typing import Dict, Optional, overload, Sequence, TYPE_CHECKING, Union
21
+
22
+ import attrs
23
+ import networkx as nx
24
+ import numpy as np
25
+ import pandas as pd
26
+
27
+ import cirq.experiments.random_quantum_circuit_generation as rqcg
28
+ import cirq.experiments.two_qubit_xeb as tqxeb
29
+ import cirq.experiments.xeb_fitting as xeb_fitting
30
+ from cirq import circuits, devices, ops, protocols, sim, value
31
+
32
+ if TYPE_CHECKING:
33
+ import cirq
34
+
35
+ _TARGET_T = Union['cirq.Gate', 'cirq.Operation', 'cirq.AbstractCircuit']
36
+ _QUBIT_PAIR_T = tuple['cirq.GridQubit', 'cirq.GridQubit']
37
+ _CANONICAL_TARGET_T = Union['cirq.Operation', Dict[_QUBIT_PAIR_T, 'cirq.Operation']]
38
+ _PROBABILITIES_DICT_T = dict[_QUBIT_PAIR_T, list[list[np.ndarray]]]
39
+
40
+
41
+ def _canonize_pair(pair: _QUBIT_PAIR_T) -> _QUBIT_PAIR_T:
42
+ return min(pair), max(pair)
43
+
44
+
45
+ @attrs.frozen
46
+ class XEBParameters:
47
+ """A frozen dataclass that holds the parameter of an XEB experiment.
48
+
49
+ Attributes:
50
+ n_repetitions: The number of repetitions to use.
51
+ n_combinations: The number of combinations to generate.
52
+ n_circuits: The number of circuits to generate.
53
+ cycle_depths: The cycle depths to use.
54
+ """
55
+
56
+ n_repetitions: int = 10**4
57
+ n_combinations: int = 10
58
+ n_circuits: int = 20
59
+ cycle_depths: tuple[int, ...] = attrs.field(default=(5, 25, 50, 100, 200, 300), converter=tuple)
60
+
61
+
62
+ @attrs.frozen
63
+ class XEBWideCircuitInfo:
64
+ """Represents an XEB circuit expanded to the given cycle depth.
65
+
66
+ Attributes:
67
+ wide_circuit: The expanded circuit.
68
+ pairs: A list of the pairs benchmarked by the given circuit.
69
+ narrow_template_indices: Integer indices of the circuits in the narrow circuit library
70
+ used to build the given wide circuit.
71
+ cycle_depth: Optional, the depth of the cycle forming the wide circuit.
72
+ """
73
+
74
+ wide_circuit: circuits.Circuit
75
+ pairs: Sequence[_QUBIT_PAIR_T] = attrs.field(
76
+ converter=lambda seq: [_canonize_pair(pair) for pair in seq]
77
+ )
78
+ narrow_template_indices: tuple[int, ...] = attrs.field(converter=tuple)
79
+ cycle_depth: Optional[int] = None
80
+
81
+ @staticmethod
82
+ def from_narrow_circuits(
83
+ circuit_templates: Sequence[cirq.Circuit],
84
+ permutation: np.ndarray,
85
+ pairs: Sequence[_QUBIT_PAIR_T],
86
+ target: _CANONICAL_TARGET_T,
87
+ ) -> XEBWideCircuitInfo:
88
+ """A static method that merges a sequence of narrow circuits into a wide circuit.
89
+
90
+ Args:
91
+ circuit_templates: A sequence of 2Q (i.e. narrow) circuits.
92
+ permutation: A permutation that maps a qubit-pair to a narrow circuit.
93
+ pairs: The list of qubit-pairs to benchmark.
94
+ target: The target 2Q operation to benchmark.
95
+
96
+ Returns:
97
+ An XEBWideCircuitInfo instance representing the glued circuits.
98
+ """
99
+ transformed_circuits = []
100
+ has_circuit_operations = False
101
+ for i, pair in zip(permutation, pairs, strict=True):
102
+ circuit = circuit_templates[i].transform_qubits(lambda q: pair[q.x])
103
+ if isinstance(target, ops.Operation):
104
+ xeb_op = target.with_qubits(*pair)
105
+ else:
106
+ if pair not in target:
107
+ continue
108
+ xeb_op = target[pair]
109
+ xeb_op = xeb_op.with_qubits(*pair)
110
+
111
+ if isinstance(xeb_op, circuits.CircuitOperation):
112
+ xeb_op = xeb_op.mapped_op()
113
+ has_circuit_operations = True
114
+
115
+ def _map_operation(op):
116
+ num_qubits = protocols.num_qubits(op)
117
+ if num_qubits <= 1:
118
+ return op
119
+ assert num_qubits == 2
120
+ return xeb_op
121
+
122
+ circuit = circuit.map_operations(_map_operation)
123
+ transformed_circuits.append(circuit)
124
+
125
+ zipped_circuit = circuits.Circuit.zip(*transformed_circuits)
126
+ if has_circuit_operations and len(circuit_templates) > 1:
127
+ # Each moment must have at most one circuit operation.
128
+ new_moments = []
129
+ for moment in zipped_circuit:
130
+ if any(isinstance(op, circuits.CircuitOperation) for op in moment):
131
+ new_moments.append(
132
+ _transform_moment_with_circuit_ops_to_moment_with_single_op(moment)
133
+ )
134
+ else:
135
+ new_moments.append(moment)
136
+ zipped_circuit = circuits.Circuit.from_moments(*new_moments)
137
+ return XEBWideCircuitInfo(zipped_circuit, pairs, narrow_template_indices=permutation)
138
+
139
+ def sliced_circuits(self, cycle_depths: Sequence[int]) -> Sequence[XEBWideCircuitInfo]:
140
+ """Slices the wide circuit into the given cycle depths and appends necessary measurements.
141
+
142
+ Args:
143
+ cycle_depths: the cycle depths to cut the wide circuit into.
144
+
145
+ Returns:
146
+ A sequence of XEBWideCircuitInfo representing the sliced circuits.
147
+ """
148
+ xeb_circuits = []
149
+ for cycle_depth in cycle_depths:
150
+ circuit_depth = 2 * cycle_depth + 1
151
+ xeb_circuit = self.wide_circuit[:circuit_depth]
152
+ xeb_circuit.append(
153
+ circuits.Moment(ops.measure(pair, key=str(pair)) for pair in self.pairs)
154
+ )
155
+ xeb_circuits.append(
156
+ attrs.evolve(self, wide_circuit=xeb_circuit, cycle_depth=cycle_depth)
157
+ )
158
+ return xeb_circuits
159
+
160
+
161
+ def _target_to_operation(target: _TARGET_T) -> cirq.Operation:
162
+ if isinstance(target, ops.Gate):
163
+ return target(*devices.LineQid.for_gate(target))
164
+ elif isinstance(target, circuits.AbstractCircuit):
165
+ return circuits.CircuitOperation(target.freeze())
166
+ return target
167
+
168
+
169
+ def _canonize_target(
170
+ target: Union[_TARGET_T, Dict[_QUBIT_PAIR_T, _TARGET_T]],
171
+ ) -> _CANONICAL_TARGET_T:
172
+ if isinstance(target, (ops.Gate, ops.Operation, circuits.AbstractCircuit)):
173
+ return _target_to_operation(target)
174
+ return {k: _target_to_operation(v) for k, v in target.items()}
175
+
176
+
177
+ def _transform_moment_with_circuit_ops_to_moment_with_single_op(
178
+ moment: circuits.Moment,
179
+ ) -> circuits.Moment:
180
+ """Merges all circuit operations in a moment into a single circuit operation.
181
+
182
+ Args:
183
+ moment: A cirq moment composed of single and two qubit operations.
184
+
185
+ Returns:
186
+ A Moment with at most one CircuitOperation.
187
+ """
188
+ circuit_ops = [
189
+ op.mapped_circuit() for op in moment if isinstance(op, circuits.CircuitOperation)
190
+ ]
191
+ not_circuit_ops = [op for op in moment if not isinstance(op, circuits.CircuitOperation)]
192
+ all_subcircuits = circuit_ops
193
+ if not_circuit_ops:
194
+ all_subcircuits.append(circuits.Circuit(circuits.Moment(not_circuit_ops)))
195
+ return circuits.Moment(circuits.CircuitOperation(circuits.FrozenCircuit.zip(*all_subcircuits)))
196
+
197
+
198
+ def create_combination_circuits(
199
+ circuit_templates: Sequence[cirq.Circuit],
200
+ combinations_by_layer: Sequence[rqcg.CircuitLibraryCombination],
201
+ target: _CANONICAL_TARGET_T,
202
+ ) -> Sequence[XEBWideCircuitInfo]:
203
+ """Zips two-qubit circuits into a single wide circuit for each of the given combinations.
204
+
205
+ Args:
206
+ circuit_templates: A sequence of narrow circuits.
207
+ combinations_by_layer: A sequence of combinations.
208
+ target: The target 2Q operation.
209
+
210
+ Returns:
211
+ A sequence of XEBWideCircuitInfo representing the wide circuits.
212
+ """
213
+ wide_circuits_info = []
214
+ for layer_comb in combinations_by_layer:
215
+ pairs = layer_comb.pairs
216
+ if isinstance(target, dict):
217
+ pairs = [pair for pair in pairs if pair in target]
218
+ assert pairs
219
+ for comb in layer_comb.combinations:
220
+ wide_circuits_info.append(
221
+ XEBWideCircuitInfo.from_narrow_circuits(
222
+ circuit_templates,
223
+ permutation=comb,
224
+ pairs=layer_comb.pairs, # type: ignore[arg-type]
225
+ target=target,
226
+ )
227
+ )
228
+ return wide_circuits_info
229
+
230
+
231
+ def simulate_circuit(
232
+ simulator: cirq.Simulator, circuit: cirq.Circuit, cycle_depths: Sequence[int]
233
+ ) -> Sequence[np.ndarray]:
234
+ """Simulates the given circuit and returns the state probabilities for each cycle depth.
235
+
236
+ Args:
237
+ simulator: A cirq simulator.
238
+ circuit: The circuit to simulate.
239
+ cycle_depths: A sequence of integers representing the depths for which we need the
240
+ state probabilities.
241
+
242
+ Returns:
243
+ - The cuircuit_id, same as given in input.
244
+ - The state probabilities for each cycle depth.
245
+ """
246
+ cycle_depths_set = frozenset(cycle_depths)
247
+ result = []
248
+ for moment_i, step_result in enumerate(simulator.simulate_moment_steps(circuit=circuit)):
249
+ # Translate from moment_i to cycle_depth:
250
+ # We know circuit_depth = cycle_depth * 2 + 1, and step_result is the result *after*
251
+ # moment_i, so circuit_depth = moment_i + 1 and moment_i = cycle_depth * 2.
252
+ if moment_i % 2 == 1:
253
+ continue
254
+ cycle_depth = moment_i // 2
255
+ if cycle_depth not in cycle_depths_set:
256
+ continue
257
+
258
+ psi = step_result.state_vector()
259
+ pure_probs = value.state_vector_to_probabilities(psi)
260
+
261
+ result.append(pure_probs)
262
+ return result
263
+
264
+
265
+ @overload
266
+ def simulate_circuit_library(
267
+ circuit_templates: Sequence[cirq.Circuit],
268
+ target_or_dict: ops.Operation,
269
+ cycle_depths: Sequence[int],
270
+ pool: Optional[futures.Executor] = None,
271
+ ) -> Sequence[Sequence[np.ndarray]]: ...
272
+
273
+
274
+ @overload
275
+ def simulate_circuit_library(
276
+ circuit_templates: Sequence[cirq.Circuit],
277
+ target_or_dict: dict[_QUBIT_PAIR_T, ops.Operation],
278
+ cycle_depths: Sequence[int],
279
+ pool: Optional[futures.Executor] = None,
280
+ ) -> dict[_QUBIT_PAIR_T, Sequence[Sequence[np.ndarray]]]: ...
281
+
282
+
283
+ def simulate_circuit_library(
284
+ circuit_templates: Sequence[cirq.Circuit],
285
+ target_or_dict: _CANONICAL_TARGET_T,
286
+ cycle_depths: Sequence[int],
287
+ pool: Optional[futures.Executor] = None,
288
+ ) -> Union[Sequence[Sequence[np.ndarray]], dict[_QUBIT_PAIR_T, Sequence[Sequence[np.ndarray]]]]:
289
+ """Simulate the given sequence of circuits.
290
+
291
+ Args:
292
+ circuit_templates: A sequence of circuits to simulate.
293
+ target_or_dict: The target operation or dictionary mapping qubit-pairs to operations.
294
+ cycle_depths: A list of integers giving the cycle depths to use in benchmarking.
295
+ pool: An optional concurrent.futures.Executor pool (e.g. ThreadPoolExecutor).
296
+ If given, the simulations are performed asynchronously.
297
+
298
+ Returns:
299
+ If target_or_dict is an operation:
300
+ A sequence of the result of simulate_circuit for each circuit_templates.
301
+ Else:
302
+ A dictionary mapping the keys of the map to a sequence of the result of
303
+ simulate_circuit for each circuit_templates.
304
+ """
305
+ two_qubit_ops = []
306
+ keys = None
307
+ if isinstance(target_or_dict, dict):
308
+ keys = tuple(target_or_dict.keys())
309
+ two_qubit_ops = list(target_or_dict[k] for k in keys)
310
+ else:
311
+ two_qubit_ops = [target_or_dict]
312
+
313
+ all_circuits = []
314
+ for target_op in two_qubit_ops:
315
+
316
+ def _map_op(op: ops.Operation) -> ops.Operation:
317
+ num_qubits = protocols.num_qubits(op)
318
+ if num_qubits <= 1:
319
+ return op
320
+ assert num_qubits == 2
321
+ return target_op.with_qubits(*op.qubits)
322
+
323
+ for circuit in circuit_templates:
324
+ all_circuits.append(circuit.map_operations(_map_op))
325
+
326
+ if pool is None:
327
+ simulation_results = [
328
+ simulate_circuit(
329
+ sim.Simulator(seed=np.random.RandomState(), dtype=np.complex128),
330
+ circuit=circuit,
331
+ cycle_depths=cycle_depths,
332
+ )
333
+ for circuit in all_circuits
334
+ ]
335
+ else:
336
+ simulation_results = [[np.empty(0)] for _ in range(len(all_circuits))]
337
+ tasks = [
338
+ pool.submit(
339
+ simulate_circuit,
340
+ simulator=sim.Simulator(seed=np.random.RandomState(), dtype=np.complex128),
341
+ circuit=circuit,
342
+ cycle_depths=cycle_depths,
343
+ )
344
+ for circuit in all_circuits
345
+ ]
346
+ tasks_index = {t: i for i, t in enumerate(tasks)}
347
+ for task in futures.as_completed(tasks):
348
+ sim_result = task.result()
349
+ i = tasks_index[task]
350
+ simulation_results[i] = sim_result
351
+
352
+ if keys is None:
353
+ return simulation_results
354
+
355
+ num_templates = len(circuit_templates)
356
+ return {
357
+ keys[i]: simulation_results[i * num_templates : (i + 1) * num_templates]
358
+ for i in range(len(keys))
359
+ }
360
+
361
+
362
+ def sample_all_circuits(
363
+ sampler: cirq.Sampler, circuits: Sequence[cirq.Circuit], repetitions: int
364
+ ) -> Sequence[dict[str, np.ndarray]]:
365
+ """Calls sampler.run_batch on the given circuits and estimates the state probabilities.
366
+
367
+ Args:
368
+ sampler: A cirq sampler.
369
+ circuits: A sequence of circuits.
370
+ repetitions: An integer, the number of sampling repetitions.
371
+
372
+ Returns:
373
+ For each circuit, a dictionary mapping measurement keys to the estimated probabilities.
374
+ """
375
+ sampling_results = []
376
+ for (result,) in sampler.run_batch(programs=circuits, repetitions=repetitions):
377
+ record = {}
378
+ for key in result.data.keys():
379
+ values = result.data[key]
380
+ sampled_probs = np.bincount(values, minlength=4) / len(values)
381
+ record[key] = sampled_probs
382
+ sampling_results.append(record)
383
+ return sampling_results
384
+
385
+
386
+ def _reshape_sampling_results(
387
+ sampling_results: Sequence[dict[str, np.ndarray]],
388
+ cycle_depths: Sequence[int],
389
+ wide_circuits_info: Sequence[XEBWideCircuitInfo],
390
+ pairs: Sequence[_QUBIT_PAIR_T],
391
+ num_templates: int,
392
+ ) -> _PROBABILITIES_DICT_T:
393
+ cycle_depth_to_index = {d: i for i, d in enumerate(cycle_depths)}
394
+ sampled_probabilities = {
395
+ pair: [[np.empty(0)] * num_templates for _ in range(len(cycle_depths))] for pair in pairs
396
+ }
397
+
398
+ for sampling_result, info in zip(sampling_results, wide_circuits_info, strict=True):
399
+ cycle_depth = info.cycle_depth
400
+ assert cycle_depth is not None
401
+ cycle_idx = cycle_depth_to_index[cycle_depth]
402
+ for template_idx, pair in zip(info.narrow_template_indices, info.pairs, strict=True):
403
+ pair = _canonize_pair(pair)
404
+ key = str(pair)
405
+ sampled_prob = sampling_result.get(key, np.empty(0))
406
+ sampled_probabilities[pair][cycle_idx][template_idx] = sampled_prob
407
+ return sampled_probabilities
408
+
409
+
410
+ def _reshape_simulation_results(
411
+ simulation_results: Union[
412
+ Sequence[Sequence[np.ndarray]], dict[_QUBIT_PAIR_T, Sequence[Sequence[np.ndarray]]]
413
+ ],
414
+ cycle_depths: Sequence[int],
415
+ pairs: Sequence[_QUBIT_PAIR_T],
416
+ num_templates: int,
417
+ ) -> _PROBABILITIES_DICT_T:
418
+ cycle_depth_to_index = {d: i for i, d in enumerate(cycle_depths)}
419
+
420
+ if isinstance(simulation_results, dict):
421
+ pure_probabilities = {
422
+ pair: [[np.empty(0)] * num_templates for _ in range(len(cycle_depths))]
423
+ for pair in pairs
424
+ }
425
+ for pair, simulation_result_for_pair in simulation_results.items():
426
+ for template_idx, template_simulation_result in enumerate(simulation_result_for_pair):
427
+ for cycle_depth, pure_probs in zip(
428
+ cycle_depths, template_simulation_result, strict=True
429
+ ):
430
+ cycle_idx = cycle_depth_to_index[cycle_depth]
431
+ pure_probabilities[pair][cycle_idx][template_idx] = pure_probs
432
+
433
+ return pure_probabilities
434
+ else:
435
+ common_pure_probs = [[np.empty(0)] * num_templates for _ in range(len(cycle_depths))]
436
+ for template_idx, template_simulation_result in enumerate(simulation_results):
437
+ for cycle_depth, pure_probs in zip(
438
+ cycle_depths, template_simulation_result, strict=True
439
+ ):
440
+ cycle_idx = cycle_depth_to_index[cycle_depth]
441
+ common_pure_probs[cycle_idx][template_idx] = pure_probs
442
+ return {pair: common_pure_probs for pair in pairs}
443
+
444
+
445
+ @attrs.frozen
446
+ class XEBFidelity:
447
+ """The estimated fidelity of a given pair at a give cycle depth.
448
+
449
+ Attributes:
450
+ pair: A qubit pair.
451
+ cycle_depth: The depth of the cycle.
452
+ fidelity: The estimated fidelity.
453
+ """
454
+
455
+ pair: _QUBIT_PAIR_T
456
+ cycle_depth: int
457
+ fidelity: float
458
+
459
+
460
+ def _cross_entropy(p: np.ndarray, q: np.ndarray, eps: float = 1e-60) -> float:
461
+ q[q <= 0] = eps # for numerical stability
462
+ return -np.dot(p, np.log2(q))
463
+
464
+
465
+ def estimate_fidelities(
466
+ sampling_results: Sequence[dict[str, np.ndarray]],
467
+ simulation_results: Union[
468
+ Sequence[Sequence[np.ndarray]], dict[_QUBIT_PAIR_T, Sequence[Sequence[np.ndarray]]]
469
+ ],
470
+ cycle_depths: Sequence[int],
471
+ wide_circuits_info: Sequence[XEBWideCircuitInfo],
472
+ pairs: Sequence[_QUBIT_PAIR_T],
473
+ num_templates: int,
474
+ ) -> Sequence[XEBFidelity]:
475
+ """Estimates the fidelities from the given sampling and simulation results.
476
+
477
+ Args:
478
+ sampling_results: The result of `sample_all_circuits`.
479
+ simulation_results: The result of `simulate_circuit_library`,
480
+ cycle_depths: The sequence of cycle depths,
481
+ wide_circuits_info: Sequence of XEBWideCircuitInfo detailing describing
482
+ the sampled circuits.
483
+ pairs: The qubit pairs being tests,
484
+ num_templates: The number of circuit templates used for benchmarking,
485
+
486
+ Returns:
487
+ A sequence of XEBFidelity objects.
488
+ """
489
+
490
+ sampled_probabilities = _reshape_sampling_results(
491
+ sampling_results, cycle_depths, wide_circuits_info, pairs, num_templates
492
+ )
493
+
494
+ pure_probabilities = _reshape_simulation_results(
495
+ simulation_results, cycle_depths, pairs, num_templates
496
+ )
497
+
498
+ records = []
499
+ for pair in pairs:
500
+ for depth_idx, cycle_depth in enumerate(cycle_depths):
501
+ numerator = 0.0
502
+ denominator = 0.0
503
+ for template_idx in range(num_templates):
504
+ pure_probs = pure_probabilities[pair][depth_idx][template_idx]
505
+ sampled_probs = sampled_probabilities[pair][depth_idx][template_idx]
506
+ if len(sampled_probs) == 0:
507
+ continue
508
+ assert (
509
+ len(sampled_probs) == 4
510
+ ), f'{pair=} {cycle_depth=} {template_idx=}: {sampled_probs=}'
511
+ p_uniform = np.ones_like(pure_probs) / len(pure_probs)
512
+ pure_probs /= pure_probs.sum()
513
+ sampled_probs /= sampled_probs.sum()
514
+
515
+ h_up = _cross_entropy(p_uniform, pure_probs) # H[uniform, pure probs]
516
+ h_sp = _cross_entropy(sampled_probs, pure_probs) # H[sampled probs, pure probs]
517
+ h_pp = _cross_entropy(pure_probs, pure_probs) # H[pure probs]
518
+
519
+ y = h_up - h_sp
520
+ x = h_up - h_pp
521
+ numerator += x * y
522
+ denominator += x**2
523
+ fidelity = numerator / denominator
524
+ records.append(XEBFidelity(pair=pair, cycle_depth=cycle_depth, fidelity=fidelity))
525
+ return records
526
+
527
+
528
+ def _extract_pairs(
529
+ sampler: cirq.Sampler,
530
+ target: Union[_TARGET_T, Dict[_QUBIT_PAIR_T, _TARGET_T]],
531
+ qubits: Optional[Sequence[cirq.GridQubit]],
532
+ pairs: Optional[Sequence[_QUBIT_PAIR_T]],
533
+ ) -> Sequence[_QUBIT_PAIR_T]:
534
+ if isinstance(target, dict):
535
+ if pairs is None:
536
+ pairs = tuple(target.keys())
537
+ else:
538
+ assert target.keys() == set(pairs)
539
+ qubits, device_pairs = tqxeb.qubits_and_pairs(sampler, qubits, pairs)
540
+ device_pairs = [_canonize_pair(pair) for pair in device_pairs]
541
+ if pairs is None:
542
+ return device_pairs
543
+ else:
544
+ pairs = [_canonize_pair(p) for p in pairs]
545
+ return tuple(set(pairs) & set(device_pairs))
546
+
547
+
548
+ def parallel_xeb_workflow(
549
+ sampler: cirq.Sampler,
550
+ target: Union[_TARGET_T, Dict[_QUBIT_PAIR_T, _TARGET_T]],
551
+ ideal_target: Optional[Union[_TARGET_T, Dict[_QUBIT_PAIR_T, _TARGET_T]]] = None,
552
+ qubits: Optional[Sequence[cirq.GridQubit]] = None,
553
+ pairs: Optional[Sequence[_QUBIT_PAIR_T]] = None,
554
+ parameters: XEBParameters = XEBParameters(),
555
+ rng: Optional[np.random.Generator] = None,
556
+ pool: Optional[futures.Executor] = None,
557
+ ) -> Sequence[XEBFidelity]:
558
+ """A utility method that runs the full XEB workflow.
559
+
560
+ Args:
561
+ sampler: The quantum engine or simulator to run the circuits.
562
+ target: The entangling gate, op, circuit or dict mapping pairs to ops.
563
+ ideal_target: The ideal target(s) to branch mark against. If None, use `target`.
564
+ qubits: Qubits under test. If None, uses all qubits on the sampler's device.
565
+ pairs: Pairs to use. If not specified, use all pairs between adjacent qubits.
566
+ parameters: An `XEBParameters` containing the parameters of the XEB experiment.
567
+ rng: The random number generator to use.
568
+ pool: An optional `concurrent.futures.Executor` pool.
569
+
570
+ Returns:
571
+ A sequence of XEBFidelity listing the estimated fidelity for each qubit_pair per depth.
572
+
573
+ Raises:
574
+ ValueError: If qubits are not specified and the sampler has no device.
575
+ """
576
+ if rng is None:
577
+ rng = np.random.default_rng()
578
+ rs = np.random.RandomState(rng.integers(0, 10**9))
579
+
580
+ pairs = _extract_pairs(sampler, target, qubits, pairs)
581
+ graph = nx.Graph(pairs)
582
+
583
+ circuit_templates = rqcg.generate_library_of_2q_circuits(
584
+ n_library_circuits=parameters.n_circuits,
585
+ random_state=rs,
586
+ # Any two qubit gate works here since we creating templates rather than the actual circuits.
587
+ two_qubit_gate=ops.CZ,
588
+ max_cycle_depth=max(parameters.cycle_depths),
589
+ )
590
+
591
+ combs_by_layer = rqcg.get_random_combinations_for_device(
592
+ n_library_circuits=len(circuit_templates),
593
+ n_combinations=parameters.n_combinations,
594
+ device_graph=graph,
595
+ random_state=rs,
596
+ )
597
+
598
+ canonical_target = _canonize_target(target)
599
+ if ideal_target is None:
600
+ canonical_ideal_target = canonical_target
601
+ else:
602
+ canonical_ideal_target = _canonize_target(ideal_target)
603
+
604
+ if isinstance(canonical_target, dict):
605
+ assert all(
606
+ all(pair in canonical_target for pair in layer_comb.pairs)
607
+ for layer_comb in combs_by_layer
608
+ )
609
+
610
+ if isinstance(canonical_ideal_target, dict):
611
+ assert all(
612
+ all(pair in canonical_ideal_target for pair in layer_comb.pairs)
613
+ for layer_comb in combs_by_layer
614
+ )
615
+
616
+ wide_circuits_info = create_combination_circuits(
617
+ circuit_templates, combs_by_layer, canonical_target
618
+ )
619
+ wide_circuits_info = [
620
+ info_with_depth
621
+ for info in wide_circuits_info
622
+ for info_with_depth in info.sliced_circuits(parameters.cycle_depths)
623
+ ]
624
+
625
+ # A map {measurement_key: sampled_probs} for each wide circuit
626
+ sampling_results = sample_all_circuits(
627
+ sampler, [info.wide_circuit for info in wide_circuits_info], parameters.n_repetitions
628
+ )
629
+
630
+ # Either of a pure_probability[circuit_template_idx][cycle_depth_index] or
631
+ # A map pure_probability[pair][circuit_template_idx][cycle_depth_index]
632
+ simulation_results = simulate_circuit_library(
633
+ circuit_templates, canonical_ideal_target, parameters.cycle_depths, pool
634
+ )
635
+
636
+ estimated_fidelities = estimate_fidelities(
637
+ sampling_results,
638
+ simulation_results,
639
+ parameters.cycle_depths,
640
+ wide_circuits_info,
641
+ pairs,
642
+ parameters.n_circuits,
643
+ )
644
+ return estimated_fidelities
645
+
646
+
647
+ def parallel_two_qubit_xeb(
648
+ sampler: cirq.Sampler,
649
+ target: Union[_TARGET_T, Dict[_QUBIT_PAIR_T, _TARGET_T]],
650
+ ideal_target: Optional[Union[_TARGET_T, Dict[_QUBIT_PAIR_T, _TARGET_T]]] = None,
651
+ qubits: Optional[Sequence[cirq.GridQubit]] = None,
652
+ pairs: Optional[Sequence[_QUBIT_PAIR_T]] = None,
653
+ parameters: XEBParameters = XEBParameters(),
654
+ rng: Optional[np.random.Generator] = None,
655
+ pool: Optional[futures.Executor] = None,
656
+ ) -> tqxeb.TwoQubitXEBResult:
657
+ """A convenience method that runs the full XEB workflow.
658
+
659
+ Args:
660
+ sampler: The quantum engine or simulator to run the circuits.
661
+ target: The entangling gate, op, circuit or dict mapping pairs to ops.
662
+ ideal_target: The ideal target(s) to branch mark against. If None, use `target`.
663
+ qubits: Qubits under test. If None, uses all qubits on the sampler's device.
664
+ pairs: Pairs to use. If not specified, use all pairs between adjacent qubits.
665
+ parameters: An `XEBParameters` containing the parameters of the XEB experiment.
666
+ rng: The random number generator to use.
667
+ pool: An optional `concurrent.futures.Executor` pool.
668
+
669
+ Returns:
670
+ A `TwoQubitXEBResult` object representing the result.
671
+
672
+ Raises:
673
+ ValueError: If qubits are not specified and the sampler has no device.
674
+ """
675
+ estimated_fidelities = parallel_xeb_workflow(
676
+ sampler, target, ideal_target, qubits, pairs, parameters, rng, pool
677
+ )
678
+ df = pd.DataFrame.from_records([attrs.asdict(ef) for ef in estimated_fidelities])
679
+ return tqxeb.TwoQubitXEBResult(xeb_fitting.fit_exponential_decays(df))
@@ -0,0 +1,445 @@
1
+ # Copyright 2025 The Cirq Developers
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import itertools
16
+ from concurrent import futures
17
+ from typing import Iterator
18
+
19
+ import networkx as nx
20
+ import numpy as np
21
+ import pytest
22
+
23
+ import cirq
24
+ import cirq.experiments.random_quantum_circuit_generation as rqcg
25
+ from cirq.experiments.benchmarking import parallel_xeb as xeb
26
+
27
+ _QUBITS = cirq.LineQubit.range(2)
28
+ _CIRCUIT_TEMPLATES = [
29
+ cirq.Circuit(cirq.X.on_each(_QUBITS), cirq.CZ(*_QUBITS), cirq.Y.on_each(_QUBITS)),
30
+ cirq.Circuit(cirq.Y.on_each(_QUBITS), cirq.CX(*_QUBITS), cirq.Z.on_each(_QUBITS)),
31
+ cirq.Circuit(cirq.Z.on_each(_QUBITS), cirq.CZ(*_QUBITS), cirq.X.on_each(_QUBITS)),
32
+ ]
33
+ _PAIRS = ((cirq.q(0, 0), cirq.q(0, 1)), (cirq.q(0, 2), cirq.q(0, 3)), (cirq.q(0, 4), cirq.q(0, 5)))
34
+
35
+
36
+ class TestXEBWideCircuitInfo:
37
+
38
+ def test_from_circuit(self):
39
+ permutation = [1, 2, 0]
40
+ target = cirq.CircuitOperation(cirq.FrozenCircuit(cirq.CNOT(*_QUBITS)))
41
+ wide_circuit = xeb.XEBWideCircuitInfo.from_narrow_circuits(
42
+ _CIRCUIT_TEMPLATES, permutation=permutation, pairs=_PAIRS, target=target
43
+ )
44
+ assert wide_circuit == xeb.XEBWideCircuitInfo(
45
+ wide_circuit=cirq.Circuit.from_moments(
46
+ [
47
+ cirq.Y.on_each(*_PAIRS[0]),
48
+ cirq.Z.on_each(*_PAIRS[1]),
49
+ cirq.X.on_each(*_PAIRS[2]),
50
+ ],
51
+ cirq.CircuitOperation(
52
+ cirq.FrozenCircuit(
53
+ cirq.CNOT(*_PAIRS[0]), cirq.CNOT(*_PAIRS[1]), cirq.CNOT(*_PAIRS[2])
54
+ )
55
+ ),
56
+ [
57
+ cirq.Z.on_each(*_PAIRS[0]),
58
+ cirq.X.on_each(*_PAIRS[1]),
59
+ cirq.Y.on_each(*_PAIRS[2]),
60
+ ],
61
+ ),
62
+ pairs=_PAIRS,
63
+ narrow_template_indices=permutation,
64
+ )
65
+
66
+ def test_sliced_circuit(self):
67
+ wid_circuit = xeb.XEBWideCircuitInfo(
68
+ wide_circuit=cirq.Circuit.from_moments(
69
+ [
70
+ cirq.Y.on_each(*_PAIRS[0]),
71
+ cirq.Z.on_each(*_PAIRS[1]),
72
+ cirq.X.on_each(*_PAIRS[2]),
73
+ ],
74
+ cirq.CircuitOperation(
75
+ cirq.FrozenCircuit(
76
+ cirq.CNOT(*_PAIRS[0]), cirq.CNOT(*_PAIRS[1]), cirq.CNOT(*_PAIRS[2])
77
+ )
78
+ ),
79
+ [
80
+ cirq.Z.on_each(*_PAIRS[0]),
81
+ cirq.X.on_each(*_PAIRS[1]),
82
+ cirq.Y.on_each(*_PAIRS[2]),
83
+ ],
84
+ ),
85
+ pairs=_PAIRS,
86
+ narrow_template_indices=[1, 2, 0],
87
+ )
88
+ sliced_circuit = xeb.XEBWideCircuitInfo(
89
+ wide_circuit=cirq.Circuit.from_moments(
90
+ [
91
+ cirq.Y.on_each(*_PAIRS[0]),
92
+ cirq.Z.on_each(*_PAIRS[1]),
93
+ cirq.X.on_each(*_PAIRS[2]),
94
+ ],
95
+ cirq.CircuitOperation(
96
+ cirq.FrozenCircuit(
97
+ cirq.CNOT(*_PAIRS[0]), cirq.CNOT(*_PAIRS[1]), cirq.CNOT(*_PAIRS[2])
98
+ )
99
+ ),
100
+ [
101
+ cirq.Z.on_each(*_PAIRS[0]),
102
+ cirq.X.on_each(*_PAIRS[1]),
103
+ cirq.Y.on_each(*_PAIRS[2]),
104
+ ],
105
+ [cirq.measure(p, key=str(p)) for p in _PAIRS],
106
+ ),
107
+ pairs=_PAIRS,
108
+ narrow_template_indices=[1, 2, 0],
109
+ cycle_depth=1,
110
+ )
111
+
112
+ assert wid_circuit.sliced_circuits([1]) == [sliced_circuit]
113
+
114
+
115
+ def test_create_combination_circuits():
116
+ wide_circuits_info = xeb.create_combination_circuits(
117
+ _CIRCUIT_TEMPLATES,
118
+ [rqcg.CircuitLibraryCombination(layer=None, combinations=[[1, 2, 0]], pairs=_PAIRS)],
119
+ target=cirq.CNOT(*_QUBITS),
120
+ )
121
+
122
+ assert wide_circuits_info == [
123
+ xeb.XEBWideCircuitInfo(
124
+ wide_circuit=cirq.Circuit.from_moments(
125
+ [
126
+ cirq.Y.on_each(*_PAIRS[0]),
127
+ cirq.Z.on_each(*_PAIRS[1]),
128
+ cirq.X.on_each(*_PAIRS[2]),
129
+ ],
130
+ [cirq.CNOT(*_PAIRS[0]), cirq.CNOT(*_PAIRS[1]), cirq.CNOT(*_PAIRS[2])],
131
+ [
132
+ cirq.Z.on_each(*_PAIRS[0]),
133
+ cirq.X.on_each(*_PAIRS[1]),
134
+ cirq.Y.on_each(*_PAIRS[2]),
135
+ ],
136
+ ),
137
+ pairs=_PAIRS,
138
+ narrow_template_indices=[1, 2, 0],
139
+ )
140
+ ]
141
+
142
+
143
+ def test_create_combination_circuits_with_target_dict():
144
+ wide_circuits_info = xeb.create_combination_circuits(
145
+ _CIRCUIT_TEMPLATES,
146
+ [rqcg.CircuitLibraryCombination(layer=None, combinations=[[1, 2, 0]], pairs=_PAIRS)],
147
+ target={_PAIRS[0]: cirq.CNOT(*_QUBITS), _PAIRS[1]: cirq.CZ(*_QUBITS)},
148
+ )
149
+
150
+ # Wide circuit is created for the qubit pairs in the intersection between target.keys()
151
+ # and combination.pairs
152
+ assert wide_circuits_info == [
153
+ xeb.XEBWideCircuitInfo(
154
+ wide_circuit=cirq.Circuit.from_moments(
155
+ [cirq.Y.on_each(*_PAIRS[0]), cirq.Z.on_each(*_PAIRS[1])],
156
+ [cirq.CNOT(*_PAIRS[0]), cirq.CZ(*_PAIRS[1])],
157
+ [cirq.Z.on_each(*_PAIRS[0]), cirq.X.on_each(*_PAIRS[1])],
158
+ ),
159
+ pairs=_PAIRS,
160
+ narrow_template_indices=[1, 2, 0],
161
+ )
162
+ ]
163
+
164
+
165
+ def test_simulate_circuit():
166
+ sim = cirq.Simulator(seed=0)
167
+ circuit = cirq.Circuit.from_moments(
168
+ cirq.X.on_each(_QUBITS),
169
+ cirq.CNOT(*_QUBITS),
170
+ cirq.X.on_each(_QUBITS),
171
+ cirq.CNOT(*_QUBITS),
172
+ cirq.X.on_each(_QUBITS),
173
+ cirq.CNOT(*_QUBITS),
174
+ cirq.X.on_each(_QUBITS),
175
+ )
176
+
177
+ result = xeb.simulate_circuit(sim, circuit, [1, 3])
178
+ np.testing.assert_allclose(result, [[0, 1, 0, 0], [1, 0, 0, 0]]) # |01> # |00>
179
+
180
+
181
+ def test_simulate_circuit_library():
182
+ circuit_templates = [
183
+ cirq.Circuit.from_moments(
184
+ cirq.X.on_each(_QUBITS),
185
+ cirq.CNOT(*_QUBITS),
186
+ cirq.X.on_each(_QUBITS),
187
+ cirq.CNOT(*_QUBITS),
188
+ cirq.X.on_each(_QUBITS),
189
+ cirq.CNOT(*_QUBITS),
190
+ cirq.X.on_each(_QUBITS),
191
+ )
192
+ ]
193
+
194
+ result = xeb.simulate_circuit_library(
195
+ circuit_templates=circuit_templates, target_or_dict=cirq.CNOT(*_QUBITS), cycle_depths=(1, 3)
196
+ )
197
+ np.testing.assert_allclose(result, [[[0, 1, 0, 0], [1, 0, 0, 0]]]) # |01> # |00>
198
+
199
+
200
+ def test_simulate_circuit_library_with_target_dict():
201
+ circuit_templates = [
202
+ cirq.Circuit.from_moments(
203
+ [cirq.H(_QUBITS[0]), cirq.X(_QUBITS[1])],
204
+ cirq.CNOT(*_QUBITS),
205
+ [cirq.H(_QUBITS[0]), cirq.X(_QUBITS[1])],
206
+ cirq.CNOT(*_QUBITS),
207
+ [cirq.H(_QUBITS[0]), cirq.X(_QUBITS[1])],
208
+ cirq.CNOT(*_QUBITS),
209
+ [cirq.H(_QUBITS[0]), cirq.X(_QUBITS[1])],
210
+ )
211
+ ]
212
+
213
+ result = xeb.simulate_circuit_library(
214
+ circuit_templates=circuit_templates,
215
+ target_or_dict={_PAIRS[0]: cirq.CNOT(*_QUBITS), _PAIRS[1]: cirq.CZ(*_QUBITS)},
216
+ cycle_depths=(1, 3),
217
+ )
218
+
219
+ # First pair result.
220
+ np.testing.assert_allclose(result[_PAIRS[0]], [[[0.25, 0.25, 0.25, 0.25], [0, 1, 0, 0]]])
221
+
222
+ # Second pair result.
223
+ np.testing.assert_allclose(result[_PAIRS[1]], [[[0, 0, 1, 0], [1, 0, 0, 0]]]) # |10> # |00>
224
+
225
+
226
+ def test_sample_all_circuits():
227
+ wide_circuits = [
228
+ cirq.Circuit.from_moments(
229
+ [cirq.Y.on_each(*_PAIRS[0]), cirq.Z.on_each(*_PAIRS[1]), cirq.X.on_each(*_PAIRS[2])],
230
+ cirq.CircuitOperation(
231
+ cirq.FrozenCircuit(
232
+ cirq.CNOT(*_PAIRS[0]), cirq.CNOT(*_PAIRS[1]), cirq.CNOT(*_PAIRS[2])
233
+ )
234
+ ),
235
+ [cirq.Z.on_each(*_PAIRS[0]), cirq.X.on_each(*_PAIRS[1]), cirq.Y.on_each(*_PAIRS[2])],
236
+ [cirq.measure(p, key=str(p)) for p in _PAIRS],
237
+ )
238
+ ]
239
+ result = xeb.sample_all_circuits(cirq.Simulator(seed=0), circuits=wide_circuits, repetitions=10)
240
+ assert len(result) == len(wide_circuits)
241
+ assert result[0].keys() == {str(p) for p in _PAIRS}
242
+ np.testing.assert_allclose(
243
+ result[0]['(cirq.GridQubit(0, 0), cirq.GridQubit(0, 1))'], [0, 0, 1, 0]
244
+ ) # |10>
245
+ np.testing.assert_allclose(
246
+ result[0]['(cirq.GridQubit(0, 2), cirq.GridQubit(0, 3))'], [0, 0, 0, 1]
247
+ ) # |11>
248
+ np.testing.assert_allclose(
249
+ result[0]['(cirq.GridQubit(0, 4), cirq.GridQubit(0, 5))'], [0, 1, 0, 0]
250
+ ) # |01>
251
+
252
+
253
+ def test_estimate_fidelities():
254
+ sampling_result = [{str(_PAIRS[0]): np.array([0.15, 0.15, 0.35, 0.35])}]
255
+
256
+ simulation_results = [[np.array([0.1, 0.2, 0.3, 0.4])]]
257
+
258
+ result = xeb.estimate_fidelities(
259
+ sampling_results=sampling_result,
260
+ simulation_results=simulation_results,
261
+ cycle_depths=(1,),
262
+ num_templates=1,
263
+ pairs=_PAIRS[:1],
264
+ wide_circuits_info=[
265
+ xeb.XEBWideCircuitInfo(
266
+ wide_circuit=cirq.Circuit.from_moments(
267
+ [cirq.Y.on_each(*_PAIRS[0])],
268
+ cirq.CNOT(*_PAIRS[0]),
269
+ [cirq.Z.on_each(*_PAIRS[0])],
270
+ cirq.measure(_PAIRS[0], key=str(_PAIRS[0])),
271
+ ),
272
+ cycle_depth=1,
273
+ narrow_template_indices=(0,),
274
+ pairs=_PAIRS[:1],
275
+ )
276
+ ],
277
+ )
278
+
279
+ assert result == [
280
+ xeb.XEBFidelity(pair=_PAIRS[0], cycle_depth=1, fidelity=pytest.approx(0.785, abs=2e-4))
281
+ ]
282
+
283
+
284
+ def test_estimate_fidelities_with_dict_target():
285
+ sampling_result = [{str(_PAIRS[0]): np.array([0.15, 0.15, 0.35, 0.35])}]
286
+
287
+ simulation_results = {_PAIRS[0]: [[np.array([0.1, 0.2, 0.3, 0.4])]]}
288
+
289
+ result = xeb.estimate_fidelities(
290
+ sampling_results=sampling_result,
291
+ simulation_results=simulation_results,
292
+ cycle_depths=(1,),
293
+ num_templates=1,
294
+ pairs=_PAIRS[:1],
295
+ wide_circuits_info=[
296
+ xeb.XEBWideCircuitInfo(
297
+ wide_circuit=cirq.Circuit.from_moments(
298
+ [cirq.Y.on_each(*_PAIRS[0])],
299
+ cirq.CNOT(*_PAIRS[0]),
300
+ [cirq.Z.on_each(*_PAIRS[0])],
301
+ cirq.measure(_PAIRS[0], key=str(_PAIRS[0])),
302
+ ),
303
+ cycle_depth=1,
304
+ narrow_template_indices=(0,),
305
+ pairs=_PAIRS[:1],
306
+ )
307
+ ],
308
+ )
309
+
310
+ assert result == [
311
+ xeb.XEBFidelity(pair=_PAIRS[0], cycle_depth=1, fidelity=pytest.approx(0.785, abs=2e-4))
312
+ ]
313
+
314
+
315
+ def _assert_fidelities_approx_equal(fids, expected: float, atol: float):
316
+ fids = np.asarray(fids).tolist()
317
+ fids.sort(reverse=True)
318
+ fids.pop() # discard smallest to make the test robust to randomness
319
+ np.testing.assert_allclose(fids, expected, atol=atol)
320
+
321
+
322
+ @pytest.mark.parametrize('target', [cirq.CZ, cirq.Circuit(cirq.CZ(*_QUBITS)), cirq.CZ(*_QUBITS)])
323
+ @pytest.mark.parametrize('pairs', [_PAIRS[:1], _PAIRS[:2]])
324
+ def test_parallel_two_qubit_xeb(target, pairs):
325
+ sampler = cirq.DensityMatrixSimulator(noise=cirq.depolarize(0.03), seed=0)
326
+ result = xeb.parallel_two_qubit_xeb(
327
+ sampler=sampler,
328
+ target=target,
329
+ pairs=pairs,
330
+ parameters=xeb.XEBParameters(
331
+ n_circuits=10, n_combinations=10, n_repetitions=10, cycle_depths=range(1, 10, 2)
332
+ ),
333
+ )
334
+ _assert_fidelities_approx_equal(result.fidelities.layer_fid, 0.9, atol=0.3)
335
+
336
+
337
+ class ExampleDevice(cirq.Device):
338
+ @property
339
+ def metadata(self) -> cirq.DeviceMetadata:
340
+ qubits = cirq.GridQubit.rect(3, 2, 4, 3)
341
+ graph = nx.Graph(
342
+ pair
343
+ for pair in itertools.combinations(qubits, 2)
344
+ if abs(pair[0].row - pair[1].row) + abs(pair[0].col - pair[1].col) == 1
345
+ )
346
+ return cirq.DeviceMetadata(qubits, graph)
347
+
348
+
349
+ class ExampleProcessor:
350
+ def get_device(self):
351
+ return ExampleDevice()
352
+
353
+
354
+ class DensityMatrixSimulatorWithProcessor(cirq.DensityMatrixSimulator):
355
+ @property
356
+ def processor(self):
357
+ return ExampleProcessor()
358
+
359
+
360
+ def test_parallel_two_qubit_xeb_with_device():
361
+ target = cirq.CZ
362
+ sampler = DensityMatrixSimulatorWithProcessor(noise=cirq.depolarize(0.03), seed=0)
363
+ result = xeb.parallel_two_qubit_xeb(
364
+ sampler=sampler,
365
+ target=target,
366
+ parameters=xeb.XEBParameters(
367
+ n_circuits=10, n_combinations=10, n_repetitions=10, cycle_depths=range(1, 10, 2)
368
+ ),
369
+ )
370
+ _assert_fidelities_approx_equal(result.fidelities.layer_fid, 0.9, atol=0.3)
371
+ qubits = cirq.GridQubit.rect(3, 2, 4, 3)
372
+ pairs = tuple(
373
+ pair
374
+ for pair in itertools.combinations(qubits, 2)
375
+ if abs(pair[0].row - pair[1].row) + abs(pair[0].col - pair[1].col) == 1
376
+ and pair[0] < pair[1]
377
+ )
378
+ assert result.all_qubit_pairs == pairs
379
+
380
+
381
+ def test_parallel_two_qubit_xeb_with_dict_target():
382
+ target = {p: cirq.Circuit(cirq.CZ(*_QUBITS)) for p in _PAIRS[:2]}
383
+ target[_PAIRS[2]] = cirq.CZ(*_QUBITS)
384
+ sampler = cirq.DensityMatrixSimulator(noise=cirq.depolarize(0.03), seed=0)
385
+ result = xeb.parallel_two_qubit_xeb(
386
+ sampler=sampler,
387
+ target=target,
388
+ parameters=xeb.XEBParameters(
389
+ n_circuits=10, n_combinations=10, n_repetitions=10, cycle_depths=range(1, 10, 2)
390
+ ),
391
+ )
392
+ _assert_fidelities_approx_equal(result.fidelities.layer_fid, 0.9, atol=0.3)
393
+ assert result.all_qubit_pairs == _PAIRS
394
+
395
+
396
+ def test_parallel_two_qubit_xeb_with_ideal_target():
397
+ target = {p: cirq.Circuit(cirq.CZ(*_QUBITS)) for p in _PAIRS[:2]}
398
+ target[_PAIRS[2]] = cirq.CZ(*_QUBITS)
399
+ sampler = cirq.DensityMatrixSimulator(noise=cirq.depolarize(0.03), seed=0)
400
+ result = xeb.parallel_two_qubit_xeb(
401
+ sampler=sampler,
402
+ target=target,
403
+ ideal_target=cirq.CZ,
404
+ parameters=xeb.XEBParameters(
405
+ n_circuits=10, n_combinations=10, n_repetitions=10, cycle_depths=range(1, 10, 2)
406
+ ),
407
+ )
408
+ _assert_fidelities_approx_equal(result.fidelities.layer_fid, 0.9, atol=0.3)
409
+ assert result.all_qubit_pairs == _PAIRS
410
+
411
+
412
+ @pytest.fixture
413
+ def threading_pool() -> Iterator[futures.Executor]:
414
+ with futures.ThreadPoolExecutor(1) as pool:
415
+ yield pool
416
+
417
+
418
+ def test_parallel_two_qubit_xeb_with_dict_target_and_pool(threading_pool):
419
+ target = {p: cirq.Circuit(cirq.CZ(*_QUBITS)) for p in _PAIRS}
420
+ sampler = cirq.DensityMatrixSimulator(noise=cirq.depolarize(0.03), seed=0)
421
+ result = xeb.parallel_two_qubit_xeb(
422
+ sampler=sampler,
423
+ target=target,
424
+ parameters=xeb.XEBParameters(
425
+ n_circuits=10, n_combinations=10, n_repetitions=10, cycle_depths=range(1, 10, 2)
426
+ ),
427
+ pool=threading_pool,
428
+ )
429
+ _assert_fidelities_approx_equal(result.fidelities.layer_fid, 0.9, atol=0.3)
430
+ assert result.all_qubit_pairs == _PAIRS
431
+
432
+
433
+ def test_parallel_two_qubit_xeb_with_invalid_input_raises():
434
+ with pytest.raises(AssertionError):
435
+ _ = xeb.parallel_two_qubit_xeb(
436
+ sampler=cirq.Simulator(seed=0), target={_PAIRS[0]: cirq.CZ}, pairs=_PAIRS
437
+ )
438
+
439
+ with pytest.raises(AssertionError):
440
+ _ = xeb.parallel_two_qubit_xeb(
441
+ sampler=cirq.Simulator(seed=0),
442
+ target=cirq.CZ,
443
+ ideal_target={_PAIRS[0]: cirq.CZ},
444
+ pairs=_PAIRS,
445
+ )
@@ -107,6 +107,10 @@ class TwoQubitXEBResult:
107
107
 
108
108
  @functools.cached_property
109
109
  def _qubit_pair_map(self) -> Dict[Tuple[cirq.GridQubit, cirq.GridQubit], int]:
110
+ if isinstance(self.fidelities.index[0][0], ops.Qid):
111
+ return {
112
+ (min(q0, q1), max(q0, q1)): i for i, (q0, q1) in enumerate(self.fidelities.index)
113
+ }
110
114
  return {
111
115
  (min(q0, q1), max(q0, q1)): i
112
116
  for i, (_, _, (q0, q1)) in enumerate(self.fidelities.index)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cirq-core
3
- Version: 1.6.0.dev20250502191313
3
+ Version: 1.6.0.dev20250505215959
4
4
  Summary: A framework for creating, editing, and invoking Noisy Intermediate Scale Quantum (NISQ) circuits.
5
5
  Home-page: http://github.com/quantumlib/cirq
6
6
  Author: The Cirq Developers
@@ -4,8 +4,8 @@ cirq/_compat_test.py,sha256=0m3sYIyxRNv9jvAo6rzJ-cnbpny3KGnAByrbU7bApgQ,34720
4
4
  cirq/_doc.py,sha256=yDyWUD_2JDS0gShfGRb-rdqRt9-WeL7DhkqX7np0Nko,2879
5
5
  cirq/_import.py,sha256=cfocxtT1BJ4HkfZ-VO8YyIhPP-xfqHDkLrzz6eeO5U0,8421
6
6
  cirq/_import_test.py,sha256=6K_v0riZJXOXUphHNkGA8MY-JcmGlezFaGmvrNhm3OQ,1015
7
- cirq/_version.py,sha256=PYDX-58EPDpSkj_agXTAnqZKhV7z_u75QP6ZshYJGCI,1206
8
- cirq/_version_test.py,sha256=TyJfLTAJWt91937_5DAvzXBZiMVEDAvhfBFD4CBqXF0,147
7
+ cirq/_version.py,sha256=fyp0MSaaj4W0ioi69LzAGW42XMYSl0Qpl1zS_dI0_zM,1206
8
+ cirq/_version_test.py,sha256=9i8iz1L6DT_KAgYd2kflJzvQW3ZEQwx7a783BQbCW5A,147
9
9
  cirq/conftest.py,sha256=X7yLFL8GLhg2CjPw0hp5e_dGASfvHx1-QT03aUbhKJw,1168
10
10
  cirq/json_resolver_cache.py,sha256=-4KqEEYb6aps-seafnFTHTp3SZc0D8mr4O-pCKIajn8,13653
11
11
  cirq/py.typed,sha256=VFSlmh_lNwnaXzwY-ZuW-C2Ws5PkuDoVgBdNCs0jXJE,63
@@ -199,7 +199,7 @@ cirq/experiments/t1_decay_experiment.py,sha256=IA108IQ9KEcZilEwu_famESZmdMaxJAUK
199
199
  cirq/experiments/t1_decay_experiment_test.py,sha256=LauXCkcLNxrmM5gNCT5RSWaRQ8OnlV8sCZZ3CBrLjpI,9138
200
200
  cirq/experiments/t2_decay_experiment.py,sha256=RM8KbXmEWfLu865QksIZPCL9h37VQLUKoSTb1Uq_QnI,19176
201
201
  cirq/experiments/t2_decay_experiment_test.py,sha256=dJhtdqgH2majCQ-sstyjog75wkzkzHrHVsQrGpuYXfE,15030
202
- cirq/experiments/two_qubit_xeb.py,sha256=AuxH4aeFnORBrJ7BAGPDLJV9IMunsH9VKFMhVt2k6ZY,22646
202
+ cirq/experiments/two_qubit_xeb.py,sha256=loImf3M7UDPyAhWsVwYn7xNSumBCXZ5LRsgCy6IaaWE,22840
203
203
  cirq/experiments/two_qubit_xeb_test.py,sha256=Enf11AZ91lr8Z4-A32xp8wkqU8TnhK0NXind5_ALMwA,10676
204
204
  cirq/experiments/xeb_fitting.py,sha256=UEPNyjpjoGUNTdQfdRUnPe_Yvdc18GewYN1FdJjWoYg,30401
205
205
  cirq/experiments/xeb_fitting_test.py,sha256=q4d-6dPnnN_E9Qw9laip-opsfhdftJuY3QZfEh_u7Wo,15483
@@ -209,6 +209,9 @@ cirq/experiments/xeb_simulation.py,sha256=w-4TI8KexpI6zJTS8PHmcWWWHfyRcbJYC4Ifin
209
209
  cirq/experiments/xeb_simulation_test.py,sha256=2ETf99fb0b76w9NjH8pGHTVLLmQE6CIv0yzlCB6WbQ8,5646
210
210
  cirq/experiments/z_phase_calibration.py,sha256=EPblzpLl3JXRkmXgD9GS0R7RKf4zVVYAHhGW0c-HZ6E,15128
211
211
  cirq/experiments/z_phase_calibration_test.py,sha256=gyZgldzM0G8bRHp33Jk_eYaYqO2WsfFky_GDtElRpzo,9012
212
+ cirq/experiments/benchmarking/__init__.py,sha256=2jMiP2dmQrH9Z2eKC8koaQqZcOi_-ziuer-aHkLm_bU,709
213
+ cirq/experiments/benchmarking/parallel_xeb.py,sha256=3RyTTnHpqV-8B-TmDS7jBnaLu6GFLKPUFDtOOnMUlwQ,26015
214
+ cirq/experiments/benchmarking/parallel_xeb_test.py,sha256=_puxm9O-OMVMraVJuS2ai3elWrgMObIXR8wV3DnqLJs,15831
212
215
  cirq/interop/__init__.py,sha256=Xt1xU9UegP_jBNa9xaeOFSgtC0lYb_HNHq4hQQ0J20k,784
213
216
  cirq/interop/quirk/__init__.py,sha256=W11jqaExSgvoUkjM_d0Kik4R8bqETF9Ezo27CDEB3iw,1237
214
217
  cirq/interop/quirk/url_to_circuit.py,sha256=VYFm_QSkpgug568oyF6LerZbFDkes3e5dR6YSREvuSQ,14226
@@ -1209,8 +1212,8 @@ cirq/work/sampler.py,sha256=sW0RhIelGABAKbqTM58shwyyCPgf86JIv9IGdJe__js,19186
1209
1212
  cirq/work/sampler_test.py,sha256=mdk1J-WrvbPUYhY41VhWf9_te4DnXr_XMPcugWwc4-I,13281
1210
1213
  cirq/work/zeros_sampler.py,sha256=8_Ne6dBkDANtTZuql7Eb0Qg_E_P3-_gu-ybFzxTbKAQ,2356
1211
1214
  cirq/work/zeros_sampler_test.py,sha256=JIkpBBFPJe5Ba4142vzogyWyboG1Q1ZAm0UVGgOoZn8,3279
1212
- cirq_core-1.6.0.dev20250502191313.dist-info/licenses/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
1213
- cirq_core-1.6.0.dev20250502191313.dist-info/METADATA,sha256=k6cZYHsjHUz0V2e279oMfpHVGMK23nKraMrhpGR7U7A,4908
1214
- cirq_core-1.6.0.dev20250502191313.dist-info/WHEEL,sha256=wXxTzcEDnjrTwFYjLPcsW_7_XihufBwmpiBeiXNBGEA,91
1215
- cirq_core-1.6.0.dev20250502191313.dist-info/top_level.txt,sha256=Sz9iOxHU0IEMLSFGwiwOCaN2e9K-jFbBbtpPN1hB73g,5
1216
- cirq_core-1.6.0.dev20250502191313.dist-info/RECORD,,
1215
+ cirq_core-1.6.0.dev20250505215959.dist-info/licenses/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
1216
+ cirq_core-1.6.0.dev20250505215959.dist-info/METADATA,sha256=Yl8UG4YVGZ3QJLGvWwC5zvMnSXtK5tpS-ChJlB-TJOU,4908
1217
+ cirq_core-1.6.0.dev20250505215959.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
1218
+ cirq_core-1.6.0.dev20250505215959.dist-info/top_level.txt,sha256=Sz9iOxHU0IEMLSFGwiwOCaN2e9K-jFbBbtpPN1hB73g,5
1219
+ cirq_core-1.6.0.dev20250505215959.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.1.0)
2
+ Generator: setuptools (80.3.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5