cirq-core 1.6.0.dev20250501173104__py3-none-any.whl → 1.6.0.dev20250501231232__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 +1 -1
- cirq/_version_test.py +1 -1
- cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation.py +211 -107
- cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation_test.py +347 -3
- cirq/transformers/analytical_decompositions/two_qubit_to_cz.py +18 -18
- cirq/transformers/analytical_decompositions/two_qubit_to_fsim.py +18 -19
- cirq/transformers/analytical_decompositions/two_qubit_to_ms.py +8 -10
- cirq/transformers/analytical_decompositions/two_qubit_to_sqrt_iswap.py +26 -28
- cirq/transformers/drop_empty_moments.py +4 -2
- cirq/transformers/drop_negligible_operations.py +6 -4
- cirq/transformers/dynamical_decoupling.py +6 -4
- cirq/transformers/dynamical_decoupling_test.py +8 -6
- cirq/transformers/eject_phased_paulis.py +14 -12
- cirq/transformers/eject_z.py +8 -6
- cirq/transformers/expand_composite.py +5 -3
- cirq/transformers/gauge_compiling/sqrt_cz_gauge.py +3 -1
- cirq/transformers/heuristic_decompositions/two_qubit_gate_tabulation.py +4 -1
- cirq/transformers/insertion_sort.py +6 -4
- cirq/transformers/measurement_transformers.py +21 -21
- cirq/transformers/merge_k_qubit_gates.py +11 -9
- cirq/transformers/merge_k_qubit_gates_test.py +5 -3
- cirq/transformers/merge_single_qubit_gates.py +15 -13
- cirq/transformers/optimize_for_target_gateset.py +14 -12
- cirq/transformers/optimize_for_target_gateset_test.py +7 -3
- cirq/transformers/qubit_management_transformers.py +10 -8
- cirq/transformers/randomized_measurements.py +9 -7
- cirq/transformers/routing/initial_mapper.py +5 -3
- cirq/transformers/routing/line_initial_mapper.py +15 -13
- cirq/transformers/routing/mapping_manager.py +9 -9
- cirq/transformers/routing/route_circuit_cqc.py +17 -15
- cirq/transformers/routing/visualize_routed_circuit.py +7 -6
- cirq/transformers/stratify.py +13 -11
- cirq/transformers/synchronize_terminal_measurements.py +9 -9
- cirq/transformers/target_gatesets/compilation_target_gateset.py +19 -17
- cirq/transformers/target_gatesets/compilation_target_gateset_test.py +11 -7
- cirq/transformers/target_gatesets/cz_gateset.py +4 -2
- cirq/transformers/target_gatesets/sqrt_iswap_gateset.py +5 -3
- cirq/transformers/transformer_api.py +17 -15
- cirq/transformers/transformer_primitives.py +22 -20
- cirq/transformers/transformer_primitives_test.py +3 -1
- cirq/value/classical_data.py +26 -26
- cirq/value/condition.py +23 -21
- cirq/value/duration.py +11 -8
- cirq/value/linear_dict.py +22 -20
- cirq/value/periodic_value.py +4 -4
- cirq/value/probability.py +3 -1
- cirq/value/product_state.py +14 -12
- cirq/work/collector.py +7 -5
- cirq/work/observable_measurement.py +24 -22
- cirq/work/observable_measurement_data.py +9 -7
- cirq/work/observable_readout_calibration.py +4 -1
- cirq/work/observable_readout_calibration_test.py +4 -1
- cirq/work/observable_settings.py +4 -2
- cirq/work/pauli_sum_collector.py +8 -6
- {cirq_core-1.6.0.dev20250501173104.dist-info → cirq_core-1.6.0.dev20250501231232.dist-info}/METADATA +1 -1
- {cirq_core-1.6.0.dev20250501173104.dist-info → cirq_core-1.6.0.dev20250501231232.dist-info}/RECORD +59 -59
- {cirq_core-1.6.0.dev20250501173104.dist-info → cirq_core-1.6.0.dev20250501231232.dist-info}/WHEEL +0 -0
- {cirq_core-1.6.0.dev20250501173104.dist-info → cirq_core-1.6.0.dev20250501231232.dist-info}/licenses/LICENSE +0 -0
- {cirq_core-1.6.0.dev20250501173104.dist-info → cirq_core-1.6.0.dev20250501231232.dist-info}/top_level.txt +0 -0
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
+
import itertools
|
|
15
16
|
import random
|
|
16
17
|
from typing import Dict, Sequence
|
|
17
18
|
|
|
@@ -32,9 +33,14 @@ def _create_ghz(number_of_qubits: int, qubits: Sequence[cirq.Qid]) -> cirq.Circu
|
|
|
32
33
|
return ghz_circuit
|
|
33
34
|
|
|
34
35
|
|
|
35
|
-
def _generate_random_pauli_string(
|
|
36
|
+
def _generate_random_pauli_string(
|
|
37
|
+
qubits: Sequence[cirq.Qid], enable_coeff: bool = False, allow_pauli_i: bool = True
|
|
38
|
+
):
|
|
36
39
|
pauli_ops = [cirq.I, cirq.X, cirq.Y, cirq.Z]
|
|
37
40
|
|
|
41
|
+
if not allow_pauli_i:
|
|
42
|
+
pauli_ops = [cirq.X, cirq.Y, cirq.Z]
|
|
43
|
+
|
|
38
44
|
operators = {q: random.choice(pauli_ops) for q in qubits}
|
|
39
45
|
# Ensure at least one non-identity.
|
|
40
46
|
operators[random.choice(qubits)] = random.choice(pauli_ops[1:])
|
|
@@ -45,6 +51,49 @@ def _generate_random_pauli_string(qubits: Sequence[cirq.Qid], enable_coeff: bool
|
|
|
45
51
|
return cirq.PauliString(operators)
|
|
46
52
|
|
|
47
53
|
|
|
54
|
+
def _generate_qwc_paulis(
|
|
55
|
+
input_pauli: cirq.PauliString, num_output: int, exclude_input_pauli: bool = False
|
|
56
|
+
) -> list[cirq.PauliString]:
|
|
57
|
+
"""Generates PauliStrings that are Qubit-Wise Commuting (QWC)
|
|
58
|
+
with the input_pauli.
|
|
59
|
+
|
|
60
|
+
All operations in input_pauli must not be pauli I.
|
|
61
|
+
"""
|
|
62
|
+
allowed_paulis_per_qubit = []
|
|
63
|
+
qubits = input_pauli.qubits
|
|
64
|
+
|
|
65
|
+
for qubit in qubits:
|
|
66
|
+
pauli_op = input_pauli.get(qubit, cirq.I)
|
|
67
|
+
|
|
68
|
+
allowed_pauli_op = []
|
|
69
|
+
if pauli_op == cirq.I:
|
|
70
|
+
allowed_pauli_op = [cirq.I, cirq.X, cirq.Y, cirq.Z] # pragma: no cover
|
|
71
|
+
elif pauli_op == cirq.X:
|
|
72
|
+
allowed_pauli_op = [cirq.I, cirq.X]
|
|
73
|
+
elif pauli_op == cirq.Y:
|
|
74
|
+
allowed_pauli_op = [cirq.I, cirq.Y]
|
|
75
|
+
elif pauli_op == cirq.Z:
|
|
76
|
+
allowed_pauli_op = [cirq.I, cirq.Z]
|
|
77
|
+
|
|
78
|
+
allowed_paulis_per_qubit.append(allowed_pauli_op)
|
|
79
|
+
|
|
80
|
+
qwc_paulis: list[cirq.PauliString] = []
|
|
81
|
+
|
|
82
|
+
for pauli_combination in itertools.product(*allowed_paulis_per_qubit):
|
|
83
|
+
pauli_dict = {}
|
|
84
|
+
for i, qid in enumerate(qubits):
|
|
85
|
+
pauli_dict[qid] = pauli_combination[i]
|
|
86
|
+
|
|
87
|
+
qwc_pauli: cirq.PauliString = cirq.PauliString(pauli_dict)
|
|
88
|
+
if exclude_input_pauli and qwc_pauli == input_pauli:
|
|
89
|
+
continue # pragma: no cover
|
|
90
|
+
if all(q == cirq.I for q in qwc_pauli):
|
|
91
|
+
continue
|
|
92
|
+
qwc_paulis.append(qwc_pauli)
|
|
93
|
+
|
|
94
|
+
return qwc_paulis if num_output > len(qwc_paulis) else random.sample(qwc_paulis, num_output)
|
|
95
|
+
|
|
96
|
+
|
|
48
97
|
def _ideal_expectation_based_on_pauli_string(
|
|
49
98
|
pauli_string: cirq.PauliString, final_state_vector: np.ndarray
|
|
50
99
|
) -> float:
|
|
@@ -149,6 +198,60 @@ def test_pauli_string_measurement_errors_with_coefficient_no_noise() -> None:
|
|
|
149
198
|
}
|
|
150
199
|
|
|
151
200
|
|
|
201
|
+
def test_group_pauli_string_measurement_errors_no_noise_with_coefficient() -> None:
|
|
202
|
+
"""Test that the mitigated expectation is close to the ideal expectation
|
|
203
|
+
based on the group of Pauli strings"""
|
|
204
|
+
|
|
205
|
+
qubits = cirq.LineQubit.range(5)
|
|
206
|
+
circuit = cirq.FrozenCircuit(_create_ghz(5, qubits))
|
|
207
|
+
sampler = cirq.Simulator()
|
|
208
|
+
|
|
209
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, list[list[cirq.PauliString]]] = {}
|
|
210
|
+
circuits_to_pauli[circuit] = [
|
|
211
|
+
_generate_qwc_paulis(
|
|
212
|
+
_generate_random_pauli_string(qubits, enable_coeff=True, allow_pauli_i=False), 100, True
|
|
213
|
+
)
|
|
214
|
+
for _ in range(3)
|
|
215
|
+
]
|
|
216
|
+
circuits_to_pauli[circuit].append([cirq.PauliString({q: cirq.X for q in qubits})])
|
|
217
|
+
|
|
218
|
+
circuits_with_pauli_expectations = measure_pauli_strings(
|
|
219
|
+
circuits_to_pauli, sampler, 100, 100, 100, 100
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
for circuit_with_pauli_expectations in circuits_with_pauli_expectations:
|
|
223
|
+
assert isinstance(circuit_with_pauli_expectations.circuit, cirq.FrozenCircuit)
|
|
224
|
+
|
|
225
|
+
expected_val_simulation = sampler.simulate(
|
|
226
|
+
circuit_with_pauli_expectations.circuit.unfreeze()
|
|
227
|
+
)
|
|
228
|
+
final_state_vector = expected_val_simulation.final_state_vector
|
|
229
|
+
|
|
230
|
+
for pauli_string_measurement_results in circuit_with_pauli_expectations.results:
|
|
231
|
+
# Since there is no noise, the mitigated and unmitigated expectations should be the same
|
|
232
|
+
assert np.isclose(
|
|
233
|
+
pauli_string_measurement_results.mitigated_expectation,
|
|
234
|
+
pauli_string_measurement_results.unmitigated_expectation,
|
|
235
|
+
)
|
|
236
|
+
assert np.isclose(
|
|
237
|
+
pauli_string_measurement_results.mitigated_expectation,
|
|
238
|
+
_ideal_expectation_based_on_pauli_string(
|
|
239
|
+
pauli_string_measurement_results.pauli_string, final_state_vector
|
|
240
|
+
),
|
|
241
|
+
atol=4 * pauli_string_measurement_results.mitigated_stddev,
|
|
242
|
+
)
|
|
243
|
+
assert isinstance(
|
|
244
|
+
pauli_string_measurement_results.calibration_result,
|
|
245
|
+
SingleQubitReadoutCalibrationResult,
|
|
246
|
+
)
|
|
247
|
+
assert pauli_string_measurement_results.calibration_result.zero_state_errors == {
|
|
248
|
+
q: 0 for q in pauli_string_measurement_results.pauli_string.qubits
|
|
249
|
+
}
|
|
250
|
+
assert pauli_string_measurement_results.calibration_result.one_state_errors == {
|
|
251
|
+
q: 0 for q in pauli_string_measurement_results.pauli_string.qubits
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
|
|
152
255
|
def test_pauli_string_measurement_errors_with_noise() -> None:
|
|
153
256
|
"""Test that the mitigated expectation is close to the ideal expectation
|
|
154
257
|
based on the Pauli string"""
|
|
@@ -196,6 +299,57 @@ def test_pauli_string_measurement_errors_with_noise() -> None:
|
|
|
196
299
|
assert 0.0045 < error < 0.0055
|
|
197
300
|
|
|
198
301
|
|
|
302
|
+
def test_group_pauli_string_measurement_errors_with_noise() -> None:
|
|
303
|
+
"""Test that the mitigated expectation is close to the ideal expectation
|
|
304
|
+
based on the group Pauli strings"""
|
|
305
|
+
qubits = cirq.LineQubit.range(7)
|
|
306
|
+
circuit = cirq.FrozenCircuit(_create_ghz(7, qubits))
|
|
307
|
+
sampler = NoisySingleQubitReadoutSampler(p0=0.1, p1=0.005, seed=1234)
|
|
308
|
+
simulator = cirq.Simulator()
|
|
309
|
+
|
|
310
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, list[list[cirq.PauliString]]] = {}
|
|
311
|
+
circuits_to_pauli[circuit] = [
|
|
312
|
+
_generate_qwc_paulis(
|
|
313
|
+
_generate_random_pauli_string(qubits, enable_coeff=True, allow_pauli_i=False), 5
|
|
314
|
+
)
|
|
315
|
+
]
|
|
316
|
+
|
|
317
|
+
circuits_with_pauli_expectations = measure_pauli_strings(
|
|
318
|
+
circuits_to_pauli, sampler, 800, 1000, 800, np.random.default_rng()
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
for circuit_with_pauli_expectations in circuits_with_pauli_expectations:
|
|
322
|
+
assert isinstance(circuit_with_pauli_expectations.circuit, cirq.FrozenCircuit)
|
|
323
|
+
|
|
324
|
+
expected_val_simulation = simulator.simulate(
|
|
325
|
+
circuit_with_pauli_expectations.circuit.unfreeze()
|
|
326
|
+
)
|
|
327
|
+
final_state_vector = expected_val_simulation.final_state_vector
|
|
328
|
+
|
|
329
|
+
for pauli_string_measurement_results in circuit_with_pauli_expectations.results:
|
|
330
|
+
assert np.isclose(
|
|
331
|
+
pauli_string_measurement_results.mitigated_expectation,
|
|
332
|
+
_ideal_expectation_based_on_pauli_string(
|
|
333
|
+
pauli_string_measurement_results.pauli_string, final_state_vector
|
|
334
|
+
),
|
|
335
|
+
atol=4 * pauli_string_measurement_results.mitigated_stddev,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
assert isinstance(
|
|
339
|
+
pauli_string_measurement_results.calibration_result,
|
|
340
|
+
SingleQubitReadoutCalibrationResult,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
for (
|
|
344
|
+
error
|
|
345
|
+
) in pauli_string_measurement_results.calibration_result.zero_state_errors.values():
|
|
346
|
+
assert 0.08 < error < 0.12
|
|
347
|
+
for (
|
|
348
|
+
error
|
|
349
|
+
) in pauli_string_measurement_results.calibration_result.one_state_errors.values():
|
|
350
|
+
assert 0.0045 < error < 0.0055
|
|
351
|
+
|
|
352
|
+
|
|
199
353
|
def test_many_circuits_input_measurement_with_noise() -> None:
|
|
200
354
|
"""Test that the mitigated expectation is close to the ideal expectation
|
|
201
355
|
based on the Pauli string for multiple circuits"""
|
|
@@ -285,6 +439,36 @@ def test_allow_measurement_without_readout_mitigation() -> None:
|
|
|
285
439
|
assert pauli_string_measurement_results.calibration_result is None
|
|
286
440
|
|
|
287
441
|
|
|
442
|
+
def test_allow_group_pauli_measurement_without_readout_mitigation() -> None:
|
|
443
|
+
"""Test that the function allows to measure without error mitigation"""
|
|
444
|
+
qubits = cirq.LineQubit.range(7)
|
|
445
|
+
circuit = cirq.FrozenCircuit(_create_ghz(7, qubits))
|
|
446
|
+
sampler = NoisySingleQubitReadoutSampler(p0=0.1, p1=0.005, seed=1234)
|
|
447
|
+
|
|
448
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, list[list[cirq.PauliString]]] = {}
|
|
449
|
+
circuits_to_pauli[circuit] = [
|
|
450
|
+
_generate_qwc_paulis(_generate_random_pauli_string(qubits, True), 2, True),
|
|
451
|
+
_generate_qwc_paulis(_generate_random_pauli_string(qubits), 4),
|
|
452
|
+
_generate_qwc_paulis(_generate_random_pauli_string(qubits), 6),
|
|
453
|
+
]
|
|
454
|
+
|
|
455
|
+
circuits_with_pauli_expectations = measure_pauli_strings(
|
|
456
|
+
circuits_to_pauli, sampler, 100, 100, 0, np.random.default_rng()
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
for circuit_with_pauli_expectations in circuits_with_pauli_expectations:
|
|
460
|
+
assert isinstance(circuit_with_pauli_expectations.circuit, cirq.FrozenCircuit)
|
|
461
|
+
|
|
462
|
+
for pauli_string_measurement_results in circuit_with_pauli_expectations.results:
|
|
463
|
+
# Since there's no mitigation, the mitigated and unmitigated expectations
|
|
464
|
+
# should be the same
|
|
465
|
+
assert np.isclose(
|
|
466
|
+
pauli_string_measurement_results.mitigated_expectation,
|
|
467
|
+
pauli_string_measurement_results.unmitigated_expectation,
|
|
468
|
+
)
|
|
469
|
+
assert pauli_string_measurement_results.calibration_result is None
|
|
470
|
+
|
|
471
|
+
|
|
288
472
|
def test_many_circuits_with_coefficient() -> None:
|
|
289
473
|
"""Test that the mitigated expectation is close to the ideal expectation
|
|
290
474
|
based on the Pauli string for multiple circuits"""
|
|
@@ -344,6 +528,77 @@ def test_many_circuits_with_coefficient() -> None:
|
|
|
344
528
|
assert 0.0045 < error < 0.0055
|
|
345
529
|
|
|
346
530
|
|
|
531
|
+
def test_many_group_pauli_in_circuits_with_coefficient() -> None:
|
|
532
|
+
"""Test that the mitigated expectation is close to the ideal expectation
|
|
533
|
+
based on the Pauli string for multiple circuits"""
|
|
534
|
+
qubits_1 = cirq.LineQubit.range(3)
|
|
535
|
+
qubits_2 = [
|
|
536
|
+
cirq.GridQubit(0, 1),
|
|
537
|
+
cirq.GridQubit(1, 1),
|
|
538
|
+
cirq.GridQubit(1, 0),
|
|
539
|
+
cirq.GridQubit(1, 2),
|
|
540
|
+
cirq.GridQubit(2, 1),
|
|
541
|
+
]
|
|
542
|
+
qubits_3 = cirq.LineQubit.range(8)
|
|
543
|
+
|
|
544
|
+
circuit_1 = cirq.FrozenCircuit(_create_ghz(3, qubits_1))
|
|
545
|
+
circuit_2 = cirq.FrozenCircuit(_create_ghz(5, qubits_2))
|
|
546
|
+
circuit_3 = cirq.FrozenCircuit(_create_ghz(8, qubits_3))
|
|
547
|
+
|
|
548
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, list[list[cirq.PauliString]]] = {}
|
|
549
|
+
circuits_to_pauli[circuit_1] = [
|
|
550
|
+
_generate_qwc_paulis(
|
|
551
|
+
_generate_random_pauli_string(qubits_1, enable_coeff=True, allow_pauli_i=False), 4
|
|
552
|
+
)
|
|
553
|
+
]
|
|
554
|
+
circuits_to_pauli[circuit_2] = [
|
|
555
|
+
_generate_qwc_paulis(
|
|
556
|
+
_generate_random_pauli_string(qubits_2, enable_coeff=True, allow_pauli_i=False), 5
|
|
557
|
+
)
|
|
558
|
+
]
|
|
559
|
+
circuits_to_pauli[circuit_3] = [
|
|
560
|
+
_generate_qwc_paulis(
|
|
561
|
+
_generate_random_pauli_string(qubits_3, enable_coeff=True, allow_pauli_i=False), 6
|
|
562
|
+
)
|
|
563
|
+
]
|
|
564
|
+
|
|
565
|
+
sampler = NoisySingleQubitReadoutSampler(p0=0.03, p1=0.005, seed=1234)
|
|
566
|
+
simulator = cirq.Simulator()
|
|
567
|
+
|
|
568
|
+
circuits_with_pauli_expectations = measure_pauli_strings(
|
|
569
|
+
circuits_to_pauli, sampler, 1000, 1000, 1000, np.random.default_rng()
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
for circuit_with_pauli_expectations in circuits_with_pauli_expectations:
|
|
573
|
+
assert isinstance(circuit_with_pauli_expectations.circuit, cirq.FrozenCircuit)
|
|
574
|
+
|
|
575
|
+
expected_val_simulation = simulator.simulate(
|
|
576
|
+
circuit_with_pauli_expectations.circuit.unfreeze()
|
|
577
|
+
)
|
|
578
|
+
final_state_vector = expected_val_simulation.final_state_vector
|
|
579
|
+
|
|
580
|
+
for pauli_string_measurement_results in circuit_with_pauli_expectations.results:
|
|
581
|
+
assert np.isclose(
|
|
582
|
+
pauli_string_measurement_results.mitigated_expectation,
|
|
583
|
+
_ideal_expectation_based_on_pauli_string(
|
|
584
|
+
pauli_string_measurement_results.pauli_string, final_state_vector
|
|
585
|
+
),
|
|
586
|
+
atol=4 * pauli_string_measurement_results.mitigated_stddev,
|
|
587
|
+
)
|
|
588
|
+
assert isinstance(
|
|
589
|
+
pauli_string_measurement_results.calibration_result,
|
|
590
|
+
SingleQubitReadoutCalibrationResult,
|
|
591
|
+
)
|
|
592
|
+
for (
|
|
593
|
+
error
|
|
594
|
+
) in pauli_string_measurement_results.calibration_result.zero_state_errors.values():
|
|
595
|
+
assert 0.025 < error < 0.035
|
|
596
|
+
for (
|
|
597
|
+
error
|
|
598
|
+
) in pauli_string_measurement_results.calibration_result.one_state_errors.values():
|
|
599
|
+
assert 0.0045 < error < 0.0055
|
|
600
|
+
|
|
601
|
+
|
|
347
602
|
def test_coefficient_not_real_number() -> None:
|
|
348
603
|
"""Test that the coefficient of input pauli string is not real.
|
|
349
604
|
Should return error in this case"""
|
|
@@ -416,12 +671,13 @@ def test_invalid_input_pauli_string_type() -> None:
|
|
|
416
671
|
circuit_2 = cirq.FrozenCircuit(_create_ghz(5, qubits_2))
|
|
417
672
|
|
|
418
673
|
circuits_to_pauli: Dict[cirq.FrozenCircuit, cirq.FrozenCircuit] = {}
|
|
419
|
-
circuits_to_pauli[circuit_1] =
|
|
674
|
+
circuits_to_pauli[circuit_1] = [_generate_random_pauli_string(qubits_1)] # type: ignore
|
|
675
|
+
circuits_to_pauli[circuit_2] = [circuit_1, circuit_2] # type: ignore
|
|
420
676
|
|
|
421
677
|
with pytest.raises(
|
|
422
678
|
TypeError,
|
|
423
679
|
match="All elements in the Pauli string lists must be cirq.PauliString "
|
|
424
|
-
"instances, got <class 'cirq.circuits.
|
|
680
|
+
"instances, got <class 'cirq.circuits.frozen_circuit.FrozenCircuit'>.",
|
|
425
681
|
):
|
|
426
682
|
measure_pauli_strings(
|
|
427
683
|
circuits_to_pauli, # type: ignore[arg-type]
|
|
@@ -521,3 +777,91 @@ def test_rng_type_mismatch() -> None:
|
|
|
521
777
|
measure_pauli_strings(
|
|
522
778
|
circuits_to_pauli, cirq.Simulator(), 1000, 1000, 1000, "test" # type: ignore[arg-type]
|
|
523
779
|
)
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
def test_pauli_type_mismatch() -> None:
|
|
783
|
+
"""Test that the input paulis are not a sequence of PauliStrings."""
|
|
784
|
+
qubits = cirq.LineQubit.range(5)
|
|
785
|
+
|
|
786
|
+
circuit = cirq.FrozenCircuit(_create_ghz(5, qubits))
|
|
787
|
+
|
|
788
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, int] = {}
|
|
789
|
+
circuits_to_pauli[circuit] = 1
|
|
790
|
+
with pytest.raises(
|
|
791
|
+
TypeError,
|
|
792
|
+
match="Expected all elements to be either a sequence of PauliStrings or sequences of"
|
|
793
|
+
" ops.PauliStrings. Got <class 'int'> instead.",
|
|
794
|
+
):
|
|
795
|
+
measure_pauli_strings(
|
|
796
|
+
circuits_to_pauli, cirq.Simulator(), 1000, 1000, 1000, "test" # type: ignore[arg-type]
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
def test_group_paulis_are_not_qwc() -> None:
|
|
801
|
+
"""Test that the group paulis are not qwc."""
|
|
802
|
+
qubits = cirq.LineQubit.range(5)
|
|
803
|
+
|
|
804
|
+
circuit = cirq.FrozenCircuit(_create_ghz(5, qubits))
|
|
805
|
+
|
|
806
|
+
pauli_str1: cirq.PauliString = cirq.PauliString({qubits[0]: cirq.X, qubits[1]: cirq.Y})
|
|
807
|
+
pauli_str2: cirq.PauliString = cirq.PauliString({qubits[0]: cirq.Y})
|
|
808
|
+
|
|
809
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, list[cirq.PauliString]] = {}
|
|
810
|
+
circuits_to_pauli[circuit] = [[pauli_str1, pauli_str2]] # type: ignore
|
|
811
|
+
with pytest.raises(
|
|
812
|
+
ValueError,
|
|
813
|
+
match="The group of Pauli strings are not " "Qubit-Wise Commuting with each other.",
|
|
814
|
+
):
|
|
815
|
+
measure_pauli_strings(
|
|
816
|
+
circuits_to_pauli, cirq.Simulator(), 1000, 1000, 1000, np.random.default_rng()
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
def test_empty_group_paulis_not_allowed() -> None:
|
|
821
|
+
"""Test that the group paulis are empty"""
|
|
822
|
+
qubits = cirq.LineQubit.range(5)
|
|
823
|
+
|
|
824
|
+
circuit = cirq.FrozenCircuit(_create_ghz(5, qubits))
|
|
825
|
+
|
|
826
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, list[cirq.PauliString]] = {}
|
|
827
|
+
circuits_to_pauli[circuit] = [[]] # type: ignore
|
|
828
|
+
with pytest.raises(ValueError, match="Empty group of Pauli strings is not allowed"):
|
|
829
|
+
measure_pauli_strings(
|
|
830
|
+
circuits_to_pauli, cirq.Simulator(), 1000, 1000, 1000, np.random.default_rng()
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
def test_group_paulis_type_mismatch() -> None:
|
|
835
|
+
"""Test that the group paulis type is not correct"""
|
|
836
|
+
qubits_1 = cirq.LineQubit.range(3)
|
|
837
|
+
qubits_2 = [
|
|
838
|
+
cirq.GridQubit(0, 1),
|
|
839
|
+
cirq.GridQubit(1, 1),
|
|
840
|
+
cirq.GridQubit(1, 0),
|
|
841
|
+
cirq.GridQubit(1, 2),
|
|
842
|
+
cirq.GridQubit(2, 1),
|
|
843
|
+
]
|
|
844
|
+
qubits_3 = cirq.LineQubit.range(8)
|
|
845
|
+
|
|
846
|
+
circuit_1 = cirq.FrozenCircuit(_create_ghz(3, qubits_1))
|
|
847
|
+
circuit_2 = cirq.FrozenCircuit(_create_ghz(5, qubits_2))
|
|
848
|
+
circuit_3 = cirq.FrozenCircuit(_create_ghz(8, qubits_3))
|
|
849
|
+
|
|
850
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, list[list[cirq.PauliString]]] = {}
|
|
851
|
+
circuits_to_pauli[circuit_1] = [
|
|
852
|
+
_generate_qwc_paulis(
|
|
853
|
+
_generate_random_pauli_string(qubits_1, enable_coeff=True, allow_pauli_i=False), 6
|
|
854
|
+
)
|
|
855
|
+
for _ in range(3)
|
|
856
|
+
]
|
|
857
|
+
circuits_to_pauli[circuit_2] = [_generate_random_pauli_string(qubits_2, True) for _ in range(3)]
|
|
858
|
+
circuits_to_pauli[circuit_3] = [_generate_random_pauli_string(qubits_3, True) for _ in range(3)]
|
|
859
|
+
|
|
860
|
+
with pytest.raises(
|
|
861
|
+
TypeError,
|
|
862
|
+
match="Expected all elements to be sequences of ops.PauliString, "
|
|
863
|
+
"but found <class 'cirq.ops.pauli_string.PauliString'>.",
|
|
864
|
+
):
|
|
865
|
+
measure_pauli_strings(
|
|
866
|
+
circuits_to_pauli, cirq.Simulator(), 1000, 1000, 1000, np.random.default_rng()
|
|
867
|
+
)
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
"""Utility methods for decomposing two-qubit unitaries into CZ gates."""
|
|
16
16
|
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
17
19
|
from typing import cast, Iterable, List, Optional, Sequence, Tuple, TYPE_CHECKING
|
|
18
20
|
|
|
19
21
|
import numpy as np
|
|
@@ -31,8 +33,8 @@ if TYPE_CHECKING:
|
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
def _remove_partial_czs_or_fail(
|
|
34
|
-
operations: Iterable[
|
|
35
|
-
) -> List[
|
|
36
|
+
operations: Iterable[cirq.Operation], atol: float
|
|
37
|
+
) -> List[cirq.Operation]:
|
|
36
38
|
result = []
|
|
37
39
|
for op in operations:
|
|
38
40
|
if isinstance(op.gate, ops.CZPowGate):
|
|
@@ -49,8 +51,8 @@ def _remove_partial_czs_or_fail(
|
|
|
49
51
|
|
|
50
52
|
|
|
51
53
|
def two_qubit_matrix_to_cz_operations(
|
|
52
|
-
q0:
|
|
53
|
-
q1:
|
|
54
|
+
q0: cirq.Qid,
|
|
55
|
+
q1: cirq.Qid,
|
|
54
56
|
mat: np.ndarray,
|
|
55
57
|
allow_partial_czs: bool,
|
|
56
58
|
atol: float = 1e-8,
|
|
@@ -85,13 +87,13 @@ def two_qubit_matrix_to_cz_operations(
|
|
|
85
87
|
|
|
86
88
|
|
|
87
89
|
def two_qubit_matrix_to_diagonal_and_cz_operations(
|
|
88
|
-
q0:
|
|
89
|
-
q1:
|
|
90
|
+
q0: cirq.Qid,
|
|
91
|
+
q1: cirq.Qid,
|
|
90
92
|
mat: np.ndarray,
|
|
91
93
|
allow_partial_czs: bool = False,
|
|
92
94
|
atol: float = 1e-8,
|
|
93
95
|
clean_operations: bool = True,
|
|
94
|
-
) -> Tuple[np.ndarray, List[
|
|
96
|
+
) -> Tuple[np.ndarray, List[cirq.Operation]]:
|
|
95
97
|
"""Decomposes a 2-qubit unitary to a diagonal and the remaining operations.
|
|
96
98
|
|
|
97
99
|
For a 2-qubit unitary V, return ops, a list of operations and
|
|
@@ -136,7 +138,7 @@ def two_qubit_matrix_to_diagonal_and_cz_operations(
|
|
|
136
138
|
)
|
|
137
139
|
|
|
138
140
|
|
|
139
|
-
def _xx_interaction_via_full_czs(q0:
|
|
141
|
+
def _xx_interaction_via_full_czs(q0: cirq.Qid, q1: cirq.Qid, x: float):
|
|
140
142
|
a = x * -2 / np.pi
|
|
141
143
|
yield ops.H(q1)
|
|
142
144
|
yield ops.CZ(q0, q1)
|
|
@@ -145,7 +147,7 @@ def _xx_interaction_via_full_czs(q0: 'cirq.Qid', q1: 'cirq.Qid', x: float):
|
|
|
145
147
|
yield ops.H(q1)
|
|
146
148
|
|
|
147
149
|
|
|
148
|
-
def _xx_yy_interaction_via_full_czs(q0:
|
|
150
|
+
def _xx_yy_interaction_via_full_czs(q0: cirq.Qid, q1: cirq.Qid, x: float, y: float):
|
|
149
151
|
a = x * -2 / np.pi
|
|
150
152
|
b = y * -2 / np.pi
|
|
151
153
|
yield ops.X(q0) ** 0.5
|
|
@@ -160,9 +162,7 @@ def _xx_yy_interaction_via_full_czs(q0: 'cirq.Qid', q1: 'cirq.Qid', x: float, y:
|
|
|
160
162
|
yield ops.X(q0) ** -0.5
|
|
161
163
|
|
|
162
164
|
|
|
163
|
-
def _xx_yy_zz_interaction_via_full_czs(
|
|
164
|
-
q0: 'cirq.Qid', q1: 'cirq.Qid', x: float, y: float, z: float
|
|
165
|
-
):
|
|
165
|
+
def _xx_yy_zz_interaction_via_full_czs(q0: cirq.Qid, q1: cirq.Qid, x: float, y: float, z: float):
|
|
166
166
|
a = x * -2 / np.pi + 0.5
|
|
167
167
|
b = y * -2 / np.pi + 0.5
|
|
168
168
|
c = z * -2 / np.pi + 0.5
|
|
@@ -192,8 +192,8 @@ def cleanup_operations(operations: Sequence[ops.Operation]):
|
|
|
192
192
|
|
|
193
193
|
|
|
194
194
|
def _kak_decomposition_to_operations(
|
|
195
|
-
q0:
|
|
196
|
-
q1:
|
|
195
|
+
q0: cirq.Qid,
|
|
196
|
+
q1: cirq.Qid,
|
|
197
197
|
kak: linalg.KakDecomposition,
|
|
198
198
|
allow_partial_czs: bool,
|
|
199
199
|
atol: float = 1e-8,
|
|
@@ -232,7 +232,7 @@ def _is_trivial_angle(rad: float, atol: float) -> bool:
|
|
|
232
232
|
|
|
233
233
|
|
|
234
234
|
def _parity_interaction(
|
|
235
|
-
q0:
|
|
235
|
+
q0: cirq.Qid, q1: cirq.Qid, rads: float, atol: float, gate: Optional[ops.Gate] = None
|
|
236
236
|
):
|
|
237
237
|
"""Yields a ZZ interaction framed by the given operation."""
|
|
238
238
|
if abs(rads) < atol:
|
|
@@ -255,14 +255,14 @@ def _parity_interaction(
|
|
|
255
255
|
yield g.on(q0), g.on(q1)
|
|
256
256
|
|
|
257
257
|
|
|
258
|
-
def _do_single_on(u: np.ndarray, q:
|
|
258
|
+
def _do_single_on(u: np.ndarray, q: cirq.Qid, atol: float = 1e-8):
|
|
259
259
|
for gate in single_qubit_decompositions.single_qubit_matrix_to_gates(u, atol):
|
|
260
260
|
yield gate(q)
|
|
261
261
|
|
|
262
262
|
|
|
263
263
|
def _non_local_part(
|
|
264
|
-
q0:
|
|
265
|
-
q1:
|
|
264
|
+
q0: cirq.Qid,
|
|
265
|
+
q1: cirq.Qid,
|
|
266
266
|
interaction_coefficients: Tuple[float, float, float],
|
|
267
267
|
allow_partial_czs: bool,
|
|
268
268
|
atol: float = 1e-8,
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
"""Utility methods for decomposing two-qubit unitaries into FSim gates."""
|
|
16
16
|
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
17
19
|
from typing import Iterable, Iterator, List, Optional, Sequence, TYPE_CHECKING, Union
|
|
18
20
|
|
|
19
21
|
import numpy as np
|
|
@@ -25,11 +27,11 @@ if TYPE_CHECKING:
|
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
def decompose_two_qubit_interaction_into_four_fsim_gates(
|
|
28
|
-
interaction: Union[
|
|
30
|
+
interaction: Union[cirq.SupportsUnitary, np.ndarray],
|
|
29
31
|
*,
|
|
30
|
-
fsim_gate: Union[
|
|
31
|
-
qubits: Optional[Sequence[
|
|
32
|
-
) ->
|
|
32
|
+
fsim_gate: Union[cirq.FSimGate, cirq.ISwapPowGate],
|
|
33
|
+
qubits: Optional[Sequence[cirq.Qid]] = None,
|
|
34
|
+
) -> cirq.Circuit:
|
|
33
35
|
"""Decomposes operations into an FSimGate near theta=pi/2, phi=0.
|
|
34
36
|
|
|
35
37
|
This decomposition is guaranteed to use exactly four of the given FSim
|
|
@@ -110,12 +112,12 @@ def _sticky_0_to_1(v: float, *, atol: float) -> Optional[float]:
|
|
|
110
112
|
|
|
111
113
|
def _decompose_xx_yy_into_two_fsims_ignoring_single_qubit_ops(
|
|
112
114
|
*,
|
|
113
|
-
qubits: Sequence[
|
|
114
|
-
fsim_gate:
|
|
115
|
+
qubits: Sequence[cirq.Qid],
|
|
116
|
+
fsim_gate: cirq.FSimGate,
|
|
115
117
|
canonical_x_kak_coefficient: float,
|
|
116
118
|
canonical_y_kak_coefficient: float,
|
|
117
119
|
atol: float = 1e-8,
|
|
118
|
-
) -> List[
|
|
120
|
+
) -> List[cirq.Operation]:
|
|
119
121
|
x = canonical_x_kak_coefficient
|
|
120
122
|
y = canonical_y_kak_coefficient
|
|
121
123
|
assert 0 <= y <= x <= np.pi / 4
|
|
@@ -174,10 +176,10 @@ _B = _BGate()
|
|
|
174
176
|
|
|
175
177
|
|
|
176
178
|
def _decompose_two_qubit_interaction_into_two_b_gates(
|
|
177
|
-
interaction: Union[
|
|
179
|
+
interaction: Union[cirq.SupportsUnitary, np.ndarray, cirq.KakDecomposition],
|
|
178
180
|
*,
|
|
179
|
-
qubits: Sequence[
|
|
180
|
-
) -> List[
|
|
181
|
+
qubits: Sequence[cirq.Qid],
|
|
182
|
+
) -> List[cirq.Operation]:
|
|
181
183
|
kak = linalg.kak_decomposition(interaction)
|
|
182
184
|
|
|
183
185
|
result = _decompose_interaction_into_two_b_gates_ignoring_single_qubit_ops(
|
|
@@ -192,8 +194,8 @@ def _decompose_two_qubit_interaction_into_two_b_gates(
|
|
|
192
194
|
|
|
193
195
|
|
|
194
196
|
def _decompose_b_gate_into_two_fsims(
|
|
195
|
-
*, fsim_gate:
|
|
196
|
-
) -> List[
|
|
197
|
+
*, fsim_gate: cirq.FSimGate, qubits: Sequence[cirq.Qid]
|
|
198
|
+
) -> List[cirq.Operation]:
|
|
197
199
|
kak = linalg.kak_decomposition(_B)
|
|
198
200
|
|
|
199
201
|
result = _decompose_xx_yy_into_two_fsims_ignoring_single_qubit_ops(
|
|
@@ -211,8 +213,8 @@ def _decompose_b_gate_into_two_fsims(
|
|
|
211
213
|
|
|
212
214
|
|
|
213
215
|
def _decompose_interaction_into_two_b_gates_ignoring_single_qubit_ops(
|
|
214
|
-
qubits: Sequence[
|
|
215
|
-
) -> List[
|
|
216
|
+
qubits: Sequence[cirq.Qid], kak_interaction_coefficients: Iterable[float]
|
|
217
|
+
) -> List[cirq.Operation]:
|
|
216
218
|
"""Decompose using a minimal construction of two-qubit operations.
|
|
217
219
|
|
|
218
220
|
References:
|
|
@@ -236,11 +238,8 @@ def _decompose_interaction_into_two_b_gates_ignoring_single_qubit_ops(
|
|
|
236
238
|
|
|
237
239
|
|
|
238
240
|
def _fix_single_qubit_gates_around_kak_interaction(
|
|
239
|
-
*,
|
|
240
|
-
|
|
241
|
-
operations: List['cirq.Operation'],
|
|
242
|
-
qubits: Sequence['cirq.Qid'],
|
|
243
|
-
) -> Iterator['cirq.Operation']:
|
|
241
|
+
*, desired: cirq.KakDecomposition, operations: List[cirq.Operation], qubits: Sequence[cirq.Qid]
|
|
242
|
+
) -> Iterator[cirq.Operation]:
|
|
244
243
|
"""Adds single qubit operations to complete a desired interaction.
|
|
245
244
|
|
|
246
245
|
Args:
|
|
@@ -19,6 +19,8 @@ Gate compilation methods implemented here are following the paper below:
|
|
|
19
19
|
arXiv:1603.07678
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
22
24
|
from typing import cast, Iterable, List, Optional, Tuple, TYPE_CHECKING
|
|
23
25
|
|
|
24
26
|
import numpy as np
|
|
@@ -31,11 +33,7 @@ if TYPE_CHECKING:
|
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
def two_qubit_matrix_to_ion_operations(
|
|
34
|
-
q0:
|
|
35
|
-
q1: 'cirq.Qid',
|
|
36
|
-
mat: np.ndarray,
|
|
37
|
-
atol: float = 1e-8,
|
|
38
|
-
clean_operations: bool = True,
|
|
36
|
+
q0: cirq.Qid, q1: cirq.Qid, mat: np.ndarray, atol: float = 1e-8, clean_operations: bool = True
|
|
39
37
|
) -> List[ops.Operation]:
|
|
40
38
|
"""Decomposes a two-qubit operation into MS/single-qubit rotation gates.
|
|
41
39
|
|
|
@@ -56,7 +54,7 @@ def two_qubit_matrix_to_ion_operations(
|
|
|
56
54
|
|
|
57
55
|
|
|
58
56
|
def _kak_decomposition_to_operations(
|
|
59
|
-
q0:
|
|
57
|
+
q0: cirq.Qid, q1: cirq.Qid, kak: linalg.KakDecomposition, atol: float = 1e-8
|
|
60
58
|
) -> List[ops.Operation]:
|
|
61
59
|
"""Assumes that the decomposition is canonical."""
|
|
62
60
|
b0, b1 = kak.single_qubit_operations_before
|
|
@@ -74,13 +72,13 @@ def _kak_decomposition_to_operations(
|
|
|
74
72
|
)
|
|
75
73
|
|
|
76
74
|
|
|
77
|
-
def _do_single_on(u: np.ndarray, q:
|
|
75
|
+
def _do_single_on(u: np.ndarray, q: cirq.Qid, atol: float = 1e-8):
|
|
78
76
|
for gate in single_qubit_decompositions.single_qubit_matrix_to_gates(u, atol):
|
|
79
77
|
yield gate(q)
|
|
80
78
|
|
|
81
79
|
|
|
82
80
|
def _parity_interaction(
|
|
83
|
-
q0:
|
|
81
|
+
q0: cirq.Qid, q1: cirq.Qid, rads: float, atol: float, gate: Optional[ops.Gate] = None
|
|
84
82
|
):
|
|
85
83
|
"""Yields an XX interaction framed by the given operation."""
|
|
86
84
|
|
|
@@ -98,8 +96,8 @@ def _parity_interaction(
|
|
|
98
96
|
|
|
99
97
|
|
|
100
98
|
def _non_local_part(
|
|
101
|
-
q0:
|
|
102
|
-
q1:
|
|
99
|
+
q0: cirq.Qid,
|
|
100
|
+
q1: cirq.Qid,
|
|
103
101
|
interaction_coefficients: Tuple[float, float, float],
|
|
104
102
|
atol: float = 1e-8,
|
|
105
103
|
):
|