cirq-core 1.5.0.dev20250324234903__py3-none-any.whl → 1.5.0.dev20250325074340__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/__init__.py +4 -0
- cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation.py +379 -0
- cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation_test.py +528 -0
- cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking.py +40 -19
- cirq/contrib/shuffle_circuits/shuffle_circuits_with_readout_benchmarking_test.py +133 -74
- cirq/linalg/transformations.py +11 -3
- cirq/linalg/transformations_test.py +5 -0
- {cirq_core-1.5.0.dev20250324234903.dist-info → cirq_core-1.5.0.dev20250325074340.dist-info}/METADATA +1 -1
- {cirq_core-1.5.0.dev20250324234903.dist-info → cirq_core-1.5.0.dev20250325074340.dist-info}/RECORD +14 -12
- {cirq_core-1.5.0.dev20250324234903.dist-info → cirq_core-1.5.0.dev20250325074340.dist-info}/LICENSE +0 -0
- {cirq_core-1.5.0.dev20250324234903.dist-info → cirq_core-1.5.0.dev20250325074340.dist-info}/WHEEL +0 -0
- {cirq_core-1.5.0.dev20250324234903.dist-info → cirq_core-1.5.0.dev20250325074340.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,528 @@
|
|
|
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
|
+
from typing import Dict, Sequence
|
|
16
|
+
import random
|
|
17
|
+
|
|
18
|
+
import pytest
|
|
19
|
+
|
|
20
|
+
import cirq
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
from cirq.contrib.paulistring import measure_pauli_strings
|
|
24
|
+
from cirq.experiments.single_qubit_readout_calibration_test import NoisySingleQubitReadoutSampler
|
|
25
|
+
from cirq.experiments import SingleQubitReadoutCalibrationResult
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _create_ghz(number_of_qubits: int, qubits: Sequence[cirq.Qid]) -> cirq.Circuit:
|
|
29
|
+
ghz_circuit = cirq.Circuit(
|
|
30
|
+
cirq.H(qubits[0]),
|
|
31
|
+
*[cirq.CNOT(qubits[i - 1], qubits[i]) for i in range(1, number_of_qubits)],
|
|
32
|
+
)
|
|
33
|
+
return ghz_circuit
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _generate_random_pauli_string(qubits: Sequence[cirq.Qid], enable_coeff: bool = False):
|
|
37
|
+
pauli_ops = [cirq.I, cirq.X, cirq.Y, cirq.Z]
|
|
38
|
+
|
|
39
|
+
# Ensure at least one non-identity.
|
|
40
|
+
operators = {q: cirq.I(q) for q in qubits} # Start with all identities
|
|
41
|
+
# Choose a random subset of qubits to have non-identity operators
|
|
42
|
+
non_identity_qubits = random.sample(qubits, random.randint(1, len(qubits)))
|
|
43
|
+
for q in non_identity_qubits:
|
|
44
|
+
operators[q] = random.choice([cirq.X, cirq.Y, cirq.Z])(q) # Only non-identity ops
|
|
45
|
+
operators = {q: random.choice(pauli_ops) for q in qubits}
|
|
46
|
+
|
|
47
|
+
if enable_coeff:
|
|
48
|
+
coefficient = (2 * random.random() - 1) * 100
|
|
49
|
+
return coefficient * cirq.PauliString(operators)
|
|
50
|
+
return cirq.PauliString(operators)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _ideal_expectation_based_on_pauli_string(
|
|
54
|
+
pauli_string: cirq.PauliString, final_state_vector: np.ndarray
|
|
55
|
+
) -> float:
|
|
56
|
+
return pauli_string.expectation_from_state_vector(
|
|
57
|
+
final_state_vector, qubit_map={q: i for i, q in enumerate(pauli_string.qubits)}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_pauli_string_measurement_errors_no_noise() -> None:
|
|
62
|
+
"""Test that the mitigated expectation is close to the ideal expectation
|
|
63
|
+
based on the Pauli string"""
|
|
64
|
+
|
|
65
|
+
qubits = cirq.LineQubit.range(5)
|
|
66
|
+
circuit = cirq.FrozenCircuit(_create_ghz(5, qubits))
|
|
67
|
+
sampler = cirq.Simulator()
|
|
68
|
+
|
|
69
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, list[cirq.PauliString]] = {}
|
|
70
|
+
circuits_to_pauli[circuit] = [_generate_random_pauli_string(qubits) for _ in range(3)]
|
|
71
|
+
|
|
72
|
+
circuits_with_pauli_expectations = measure_pauli_strings(
|
|
73
|
+
circuits_to_pauli, sampler, 1000, 1000, 1000, 1000
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
for circuit_with_pauli_expectations in circuits_with_pauli_expectations:
|
|
77
|
+
assert isinstance(circuit_with_pauli_expectations.circuit, cirq.FrozenCircuit)
|
|
78
|
+
|
|
79
|
+
expected_val_simulation = sampler.simulate(
|
|
80
|
+
circuit_with_pauli_expectations.circuit.unfreeze()
|
|
81
|
+
)
|
|
82
|
+
final_state_vector = expected_val_simulation.final_state_vector
|
|
83
|
+
|
|
84
|
+
for pauli_string_measurement_results in circuit_with_pauli_expectations.results:
|
|
85
|
+
# Since there is no noise, the mitigated and unmitigated expectations should be the same
|
|
86
|
+
assert np.isclose(
|
|
87
|
+
pauli_string_measurement_results.mitigated_expectation,
|
|
88
|
+
pauli_string_measurement_results.unmitigated_expectation,
|
|
89
|
+
)
|
|
90
|
+
assert np.isclose(
|
|
91
|
+
pauli_string_measurement_results.mitigated_expectation,
|
|
92
|
+
_ideal_expectation_based_on_pauli_string(
|
|
93
|
+
pauli_string_measurement_results.pauli_string, final_state_vector
|
|
94
|
+
),
|
|
95
|
+
atol=4 * pauli_string_measurement_results.mitigated_stddev,
|
|
96
|
+
)
|
|
97
|
+
assert isinstance(
|
|
98
|
+
pauli_string_measurement_results.calibration_result,
|
|
99
|
+
SingleQubitReadoutCalibrationResult,
|
|
100
|
+
)
|
|
101
|
+
assert pauli_string_measurement_results.calibration_result.zero_state_errors == {
|
|
102
|
+
q: 0 for q in pauli_string_measurement_results.pauli_string.qubits
|
|
103
|
+
}
|
|
104
|
+
assert pauli_string_measurement_results.calibration_result.one_state_errors == {
|
|
105
|
+
q: 0 for q in pauli_string_measurement_results.pauli_string.qubits
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_pauli_string_measurement_errors_with_coefficient_no_noise() -> None:
|
|
110
|
+
"""Test that the mitigated expectation is close to the ideal expectation
|
|
111
|
+
based on the Pauli string"""
|
|
112
|
+
|
|
113
|
+
qubits = cirq.LineQubit.range(5)
|
|
114
|
+
circuit = cirq.FrozenCircuit(_create_ghz(5, qubits))
|
|
115
|
+
sampler = cirq.Simulator()
|
|
116
|
+
|
|
117
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, list[cirq.PauliString]] = {}
|
|
118
|
+
circuits_to_pauli[circuit] = [_generate_random_pauli_string(qubits, True) for _ in range(3)]
|
|
119
|
+
|
|
120
|
+
circuits_with_pauli_expectations = measure_pauli_strings(
|
|
121
|
+
circuits_to_pauli, sampler, 1000, 1000, 1000, 1000
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
for circuit_with_pauli_expectations in circuits_with_pauli_expectations:
|
|
125
|
+
assert isinstance(circuit_with_pauli_expectations.circuit, cirq.FrozenCircuit)
|
|
126
|
+
|
|
127
|
+
expected_val_simulation = sampler.simulate(
|
|
128
|
+
circuit_with_pauli_expectations.circuit.unfreeze()
|
|
129
|
+
)
|
|
130
|
+
final_state_vector = expected_val_simulation.final_state_vector
|
|
131
|
+
|
|
132
|
+
for pauli_string_measurement_results in circuit_with_pauli_expectations.results:
|
|
133
|
+
# Since there is no noise, the mitigated and unmitigated expectations should be the same
|
|
134
|
+
assert np.isclose(
|
|
135
|
+
pauli_string_measurement_results.mitigated_expectation,
|
|
136
|
+
pauli_string_measurement_results.unmitigated_expectation,
|
|
137
|
+
)
|
|
138
|
+
assert np.isclose(
|
|
139
|
+
pauli_string_measurement_results.mitigated_expectation,
|
|
140
|
+
_ideal_expectation_based_on_pauli_string(
|
|
141
|
+
pauli_string_measurement_results.pauli_string, final_state_vector
|
|
142
|
+
),
|
|
143
|
+
atol=4 * pauli_string_measurement_results.mitigated_stddev,
|
|
144
|
+
)
|
|
145
|
+
assert isinstance(
|
|
146
|
+
pauli_string_measurement_results.calibration_result,
|
|
147
|
+
SingleQubitReadoutCalibrationResult,
|
|
148
|
+
)
|
|
149
|
+
assert pauli_string_measurement_results.calibration_result.zero_state_errors == {
|
|
150
|
+
q: 0 for q in pauli_string_measurement_results.pauli_string.qubits
|
|
151
|
+
}
|
|
152
|
+
assert pauli_string_measurement_results.calibration_result.one_state_errors == {
|
|
153
|
+
q: 0 for q in pauli_string_measurement_results.pauli_string.qubits
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_pauli_string_measurement_errors_with_noise() -> None:
|
|
158
|
+
"""Test that the mitigated expectation is close to the ideal expectation
|
|
159
|
+
based on the Pauli string"""
|
|
160
|
+
qubits = cirq.LineQubit.range(7)
|
|
161
|
+
circuit = cirq.FrozenCircuit(_create_ghz(7, qubits))
|
|
162
|
+
sampler = NoisySingleQubitReadoutSampler(p0=0.1, p1=0.005, seed=1234)
|
|
163
|
+
simulator = cirq.Simulator()
|
|
164
|
+
|
|
165
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, list[cirq.PauliString]] = {}
|
|
166
|
+
circuits_to_pauli[circuit] = [_generate_random_pauli_string(qubits) for _ in range(3)]
|
|
167
|
+
|
|
168
|
+
circuits_with_pauli_expectations = measure_pauli_strings(
|
|
169
|
+
circuits_to_pauli, sampler, 1000, 1000, 1000, np.random.default_rng()
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
for circuit_with_pauli_expectations in circuits_with_pauli_expectations:
|
|
173
|
+
assert isinstance(circuit_with_pauli_expectations.circuit, cirq.FrozenCircuit)
|
|
174
|
+
|
|
175
|
+
expected_val_simulation = simulator.simulate(
|
|
176
|
+
circuit_with_pauli_expectations.circuit.unfreeze()
|
|
177
|
+
)
|
|
178
|
+
final_state_vector = expected_val_simulation.final_state_vector
|
|
179
|
+
|
|
180
|
+
for pauli_string_measurement_results in circuit_with_pauli_expectations.results:
|
|
181
|
+
assert np.isclose(
|
|
182
|
+
pauli_string_measurement_results.mitigated_expectation,
|
|
183
|
+
_ideal_expectation_based_on_pauli_string(
|
|
184
|
+
pauli_string_measurement_results.pauli_string, final_state_vector
|
|
185
|
+
),
|
|
186
|
+
atol=4 * pauli_string_measurement_results.mitigated_stddev,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
assert isinstance(
|
|
190
|
+
pauli_string_measurement_results.calibration_result,
|
|
191
|
+
SingleQubitReadoutCalibrationResult,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
for (
|
|
195
|
+
error
|
|
196
|
+
) in pauli_string_measurement_results.calibration_result.zero_state_errors.values():
|
|
197
|
+
assert 0.08 < error < 0.12
|
|
198
|
+
for (
|
|
199
|
+
error
|
|
200
|
+
) in pauli_string_measurement_results.calibration_result.one_state_errors.values():
|
|
201
|
+
assert 0.0045 < error < 0.0055
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def test_many_circuits_input_measurement_with_noise() -> None:
|
|
205
|
+
"""Test that the mitigated expectation is close to the ideal expectation
|
|
206
|
+
based on the Pauli string for multiple circuits"""
|
|
207
|
+
qubits_1 = cirq.LineQubit.range(3)
|
|
208
|
+
qubits_2 = [
|
|
209
|
+
cirq.GridQubit(0, 1),
|
|
210
|
+
cirq.GridQubit(1, 1),
|
|
211
|
+
cirq.GridQubit(1, 0),
|
|
212
|
+
cirq.GridQubit(1, 2),
|
|
213
|
+
cirq.GridQubit(2, 1),
|
|
214
|
+
]
|
|
215
|
+
qubits_3 = cirq.LineQubit.range(8)
|
|
216
|
+
|
|
217
|
+
circuit_1 = cirq.FrozenCircuit(_create_ghz(3, qubits_1))
|
|
218
|
+
circuit_2 = cirq.FrozenCircuit(_create_ghz(5, qubits_2))
|
|
219
|
+
circuit_3 = cirq.FrozenCircuit(_create_ghz(8, qubits_3))
|
|
220
|
+
|
|
221
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, list[cirq.PauliString]] = {}
|
|
222
|
+
circuits_to_pauli[circuit_1] = [_generate_random_pauli_string(qubits_1) for _ in range(3)]
|
|
223
|
+
circuits_to_pauli[circuit_2] = [_generate_random_pauli_string(qubits_2) for _ in range(3)]
|
|
224
|
+
circuits_to_pauli[circuit_3] = [_generate_random_pauli_string(qubits_3) for _ in range(3)]
|
|
225
|
+
|
|
226
|
+
sampler = NoisySingleQubitReadoutSampler(p0=0.03, p1=0.005, seed=1234)
|
|
227
|
+
simulator = cirq.Simulator()
|
|
228
|
+
|
|
229
|
+
circuits_with_pauli_expectations = measure_pauli_strings(
|
|
230
|
+
circuits_to_pauli, sampler, 1000, 1000, 1000, np.random.default_rng()
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
for circuit_with_pauli_expectations in circuits_with_pauli_expectations:
|
|
234
|
+
assert isinstance(circuit_with_pauli_expectations.circuit, cirq.FrozenCircuit)
|
|
235
|
+
|
|
236
|
+
expected_val_simulation = simulator.simulate(
|
|
237
|
+
circuit_with_pauli_expectations.circuit.unfreeze()
|
|
238
|
+
)
|
|
239
|
+
final_state_vector = expected_val_simulation.final_state_vector
|
|
240
|
+
|
|
241
|
+
for pauli_string_measurement_results in circuit_with_pauli_expectations.results:
|
|
242
|
+
assert np.isclose(
|
|
243
|
+
pauli_string_measurement_results.mitigated_expectation,
|
|
244
|
+
_ideal_expectation_based_on_pauli_string(
|
|
245
|
+
pauli_string_measurement_results.pauli_string, final_state_vector
|
|
246
|
+
),
|
|
247
|
+
atol=4 * pauli_string_measurement_results.mitigated_stddev,
|
|
248
|
+
)
|
|
249
|
+
assert isinstance(
|
|
250
|
+
pauli_string_measurement_results.calibration_result,
|
|
251
|
+
SingleQubitReadoutCalibrationResult,
|
|
252
|
+
)
|
|
253
|
+
for (
|
|
254
|
+
error
|
|
255
|
+
) in pauli_string_measurement_results.calibration_result.zero_state_errors.values():
|
|
256
|
+
assert 0.025 < error < 0.035
|
|
257
|
+
for (
|
|
258
|
+
error
|
|
259
|
+
) in pauli_string_measurement_results.calibration_result.one_state_errors.values():
|
|
260
|
+
assert 0.0045 < error < 0.0055
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def test_allow_measurement_without_readout_mitigation() -> None:
|
|
264
|
+
"""Test that the function allows to measure without error mitigation"""
|
|
265
|
+
qubits = cirq.LineQubit.range(7)
|
|
266
|
+
circuit = cirq.FrozenCircuit(_create_ghz(7, qubits))
|
|
267
|
+
sampler = NoisySingleQubitReadoutSampler(p0=0.1, p1=0.005, seed=1234)
|
|
268
|
+
|
|
269
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, list[cirq.PauliString]] = {}
|
|
270
|
+
circuits_to_pauli[circuit] = [
|
|
271
|
+
_generate_random_pauli_string(qubits, True),
|
|
272
|
+
_generate_random_pauli_string(qubits),
|
|
273
|
+
_generate_random_pauli_string(qubits),
|
|
274
|
+
]
|
|
275
|
+
|
|
276
|
+
circuits_with_pauli_expectations = measure_pauli_strings(
|
|
277
|
+
circuits_to_pauli, sampler, 1000, 1000, 0, np.random.default_rng()
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
for circuit_with_pauli_expectations in circuits_with_pauli_expectations:
|
|
281
|
+
assert isinstance(circuit_with_pauli_expectations.circuit, cirq.FrozenCircuit)
|
|
282
|
+
|
|
283
|
+
for pauli_string_measurement_results in circuit_with_pauli_expectations.results:
|
|
284
|
+
# Since there's no mitigation, the mitigated and unmitigated expectations
|
|
285
|
+
# should be the same
|
|
286
|
+
assert np.isclose(
|
|
287
|
+
pauli_string_measurement_results.mitigated_expectation,
|
|
288
|
+
pauli_string_measurement_results.unmitigated_expectation,
|
|
289
|
+
)
|
|
290
|
+
assert pauli_string_measurement_results.calibration_result is None
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def test_many_circuits_with_coefficient() -> None:
|
|
294
|
+
"""Test that the mitigated expectation is close to the ideal expectation
|
|
295
|
+
based on the Pauli string for multiple circuits"""
|
|
296
|
+
qubits_1 = cirq.LineQubit.range(3)
|
|
297
|
+
qubits_2 = [
|
|
298
|
+
cirq.GridQubit(0, 1),
|
|
299
|
+
cirq.GridQubit(1, 1),
|
|
300
|
+
cirq.GridQubit(1, 0),
|
|
301
|
+
cirq.GridQubit(1, 2),
|
|
302
|
+
cirq.GridQubit(2, 1),
|
|
303
|
+
]
|
|
304
|
+
qubits_3 = cirq.LineQubit.range(8)
|
|
305
|
+
|
|
306
|
+
circuit_1 = cirq.FrozenCircuit(_create_ghz(3, qubits_1))
|
|
307
|
+
circuit_2 = cirq.FrozenCircuit(_create_ghz(5, qubits_2))
|
|
308
|
+
circuit_3 = cirq.FrozenCircuit(_create_ghz(8, qubits_3))
|
|
309
|
+
|
|
310
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, list[cirq.PauliString]] = {}
|
|
311
|
+
circuits_to_pauli[circuit_1] = [_generate_random_pauli_string(qubits_1, True) for _ in range(3)]
|
|
312
|
+
circuits_to_pauli[circuit_2] = [_generate_random_pauli_string(qubits_2, True) for _ in range(3)]
|
|
313
|
+
circuits_to_pauli[circuit_3] = [_generate_random_pauli_string(qubits_3, True) for _ in range(3)]
|
|
314
|
+
|
|
315
|
+
sampler = NoisySingleQubitReadoutSampler(p0=0.03, p1=0.005, seed=1234)
|
|
316
|
+
simulator = cirq.Simulator()
|
|
317
|
+
|
|
318
|
+
circuits_with_pauli_expectations = measure_pauli_strings(
|
|
319
|
+
circuits_to_pauli, sampler, 1000, 1000, 1000, np.random.default_rng()
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
for circuit_with_pauli_expectations in circuits_with_pauli_expectations:
|
|
323
|
+
assert isinstance(circuit_with_pauli_expectations.circuit, cirq.FrozenCircuit)
|
|
324
|
+
|
|
325
|
+
expected_val_simulation = simulator.simulate(
|
|
326
|
+
circuit_with_pauli_expectations.circuit.unfreeze()
|
|
327
|
+
)
|
|
328
|
+
final_state_vector = expected_val_simulation.final_state_vector
|
|
329
|
+
|
|
330
|
+
for pauli_string_measurement_results in circuit_with_pauli_expectations.results:
|
|
331
|
+
assert np.isclose(
|
|
332
|
+
pauli_string_measurement_results.mitigated_expectation,
|
|
333
|
+
_ideal_expectation_based_on_pauli_string(
|
|
334
|
+
pauli_string_measurement_results.pauli_string, final_state_vector
|
|
335
|
+
),
|
|
336
|
+
atol=4 * pauli_string_measurement_results.mitigated_stddev,
|
|
337
|
+
)
|
|
338
|
+
assert isinstance(
|
|
339
|
+
pauli_string_measurement_results.calibration_result,
|
|
340
|
+
SingleQubitReadoutCalibrationResult,
|
|
341
|
+
)
|
|
342
|
+
for (
|
|
343
|
+
error
|
|
344
|
+
) in pauli_string_measurement_results.calibration_result.zero_state_errors.values():
|
|
345
|
+
assert 0.025 < error < 0.035
|
|
346
|
+
for (
|
|
347
|
+
error
|
|
348
|
+
) in pauli_string_measurement_results.calibration_result.one_state_errors.values():
|
|
349
|
+
assert 0.0045 < error < 0.0055
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def test_coefficient_not_real_number() -> None:
|
|
353
|
+
"""Test that the coefficient of input pauli string is not real.
|
|
354
|
+
Should return error in this case"""
|
|
355
|
+
qubits_1 = cirq.LineQubit.range(3)
|
|
356
|
+
random_pauli_string = _generate_random_pauli_string(qubits_1, True) * (3 + 4j)
|
|
357
|
+
circuit_1 = cirq.FrozenCircuit(_create_ghz(3, qubits_1))
|
|
358
|
+
|
|
359
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, list[cirq.PauliString]] = {}
|
|
360
|
+
circuits_to_pauli[circuit_1] = [
|
|
361
|
+
random_pauli_string,
|
|
362
|
+
_generate_random_pauli_string(qubits_1, True),
|
|
363
|
+
_generate_random_pauli_string(qubits_1, True),
|
|
364
|
+
]
|
|
365
|
+
|
|
366
|
+
with pytest.raises(
|
|
367
|
+
ValueError,
|
|
368
|
+
match="Cannot compute expectation value of a "
|
|
369
|
+
"non-Hermitian PauliString. Coefficient must be real.",
|
|
370
|
+
):
|
|
371
|
+
measure_pauli_strings(
|
|
372
|
+
circuits_to_pauli, cirq.Simulator(), 1000, 1000, 1000, np.random.default_rng()
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def test_empty_input_circuits_to_pauli_mapping() -> None:
|
|
377
|
+
"""Test that the input circuits are empty."""
|
|
378
|
+
|
|
379
|
+
with pytest.raises(ValueError, match="Input circuits must not be empty."):
|
|
380
|
+
measure_pauli_strings(
|
|
381
|
+
[], # type: ignore[arg-type]
|
|
382
|
+
cirq.Simulator(),
|
|
383
|
+
1000,
|
|
384
|
+
1000,
|
|
385
|
+
1000,
|
|
386
|
+
np.random.default_rng(),
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def test_invalid_input_circuit_type() -> None:
|
|
391
|
+
"""Test that the input circuit type is not frozen circuit"""
|
|
392
|
+
qubits = cirq.LineQubit.range(5)
|
|
393
|
+
|
|
394
|
+
qubits_to_pauli: Dict[tuple, list[cirq.PauliString]] = {}
|
|
395
|
+
qubits_to_pauli[tuple(qubits)] = [cirq.PauliString({q: cirq.X for q in qubits})]
|
|
396
|
+
with pytest.raises(
|
|
397
|
+
TypeError, match="All keys in 'circuits_to_pauli' must be FrozenCircuit instances."
|
|
398
|
+
):
|
|
399
|
+
measure_pauli_strings(
|
|
400
|
+
qubits_to_pauli, # type: ignore[arg-type]
|
|
401
|
+
cirq.Simulator(),
|
|
402
|
+
1000,
|
|
403
|
+
1000,
|
|
404
|
+
1000,
|
|
405
|
+
np.random.default_rng(),
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def test_invalid_input_pauli_string_type() -> None:
|
|
410
|
+
"""Test input circuit is not mapping to a paulistring"""
|
|
411
|
+
qubits_1 = cirq.LineQubit.range(5)
|
|
412
|
+
qubits_2 = [
|
|
413
|
+
cirq.GridQubit(0, 1),
|
|
414
|
+
cirq.GridQubit(1, 1),
|
|
415
|
+
cirq.GridQubit(1, 0),
|
|
416
|
+
cirq.GridQubit(1, 2),
|
|
417
|
+
cirq.GridQubit(2, 1),
|
|
418
|
+
]
|
|
419
|
+
|
|
420
|
+
circuit_1 = cirq.FrozenCircuit(_create_ghz(5, qubits_1))
|
|
421
|
+
circuit_2 = cirq.FrozenCircuit(_create_ghz(5, qubits_2))
|
|
422
|
+
|
|
423
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, cirq.FrozenCircuit] = {}
|
|
424
|
+
circuits_to_pauli[circuit_1] = circuit_2
|
|
425
|
+
|
|
426
|
+
with pytest.raises(
|
|
427
|
+
TypeError,
|
|
428
|
+
match="All elements in the Pauli string lists must be cirq.PauliString "
|
|
429
|
+
"instances, got <class 'cirq.circuits.moment.Moment'>.",
|
|
430
|
+
):
|
|
431
|
+
measure_pauli_strings(
|
|
432
|
+
circuits_to_pauli, # type: ignore[arg-type]
|
|
433
|
+
cirq.Simulator(),
|
|
434
|
+
1000,
|
|
435
|
+
1000,
|
|
436
|
+
1000,
|
|
437
|
+
np.random.default_rng(),
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def test_all_pauli_strings_are_pauli_i() -> None:
|
|
442
|
+
"""Test that all input pauli are pauli I"""
|
|
443
|
+
qubits_1 = cirq.LineQubit.range(5)
|
|
444
|
+
qubits_2 = [
|
|
445
|
+
cirq.GridQubit(0, 1),
|
|
446
|
+
cirq.GridQubit(1, 1),
|
|
447
|
+
cirq.GridQubit(1, 0),
|
|
448
|
+
cirq.GridQubit(1, 2),
|
|
449
|
+
cirq.GridQubit(2, 1),
|
|
450
|
+
]
|
|
451
|
+
|
|
452
|
+
circuit_1 = cirq.FrozenCircuit(_create_ghz(5, qubits_1))
|
|
453
|
+
circuit_2 = cirq.FrozenCircuit(_create_ghz(5, qubits_2))
|
|
454
|
+
|
|
455
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, list[cirq.PauliString]] = {}
|
|
456
|
+
circuits_to_pauli[circuit_1] = [
|
|
457
|
+
cirq.PauliString({q: cirq.I for q in qubits_1}),
|
|
458
|
+
cirq.PauliString({q: cirq.X for q in qubits_1}),
|
|
459
|
+
]
|
|
460
|
+
circuits_to_pauli[circuit_2] = [cirq.PauliString({q: cirq.X for q in qubits_2})]
|
|
461
|
+
|
|
462
|
+
with pytest.raises(
|
|
463
|
+
ValueError,
|
|
464
|
+
match="Empty Pauli strings or Pauli strings consisting"
|
|
465
|
+
"only of Pauli I are not allowed. Please provide"
|
|
466
|
+
"valid input Pauli strings.",
|
|
467
|
+
):
|
|
468
|
+
measure_pauli_strings(
|
|
469
|
+
circuits_to_pauli, cirq.Simulator(), 1000, 1000, 1000, np.random.default_rng()
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def test_zero_pauli_repetitions() -> None:
|
|
474
|
+
"""Test that the pauli repetitions are zero."""
|
|
475
|
+
qubits = cirq.LineQubit.range(5)
|
|
476
|
+
|
|
477
|
+
circuit = cirq.FrozenCircuit(_create_ghz(5, qubits))
|
|
478
|
+
|
|
479
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, list[cirq.PauliString]] = {}
|
|
480
|
+
circuits_to_pauli[circuit] = [cirq.PauliString({q: cirq.X for q in qubits})]
|
|
481
|
+
with pytest.raises(ValueError, match="Must provide non-zero pauli_repetitions."):
|
|
482
|
+
measure_pauli_strings(
|
|
483
|
+
circuits_to_pauli, cirq.Simulator(), 0, 1000, 1000, np.random.default_rng()
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def test_negative_num_random_bitstrings() -> None:
|
|
488
|
+
"""Test that the number of random bitstrings is smaller than zero."""
|
|
489
|
+
qubits = cirq.LineQubit.range(5)
|
|
490
|
+
|
|
491
|
+
circuit = cirq.FrozenCircuit(_create_ghz(5, qubits))
|
|
492
|
+
|
|
493
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, list[cirq.PauliString]] = {}
|
|
494
|
+
circuits_to_pauli[circuit] = [cirq.PauliString({q: cirq.X for q in qubits})]
|
|
495
|
+
with pytest.raises(ValueError, match="Must provide zero or more num_random_bitstrings."):
|
|
496
|
+
measure_pauli_strings(
|
|
497
|
+
circuits_to_pauli, cirq.Simulator(), 1000, 1000, -1, np.random.default_rng()
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def test_zero_readout_repetitions() -> None:
|
|
502
|
+
"""Test that the readout repetitions is zero."""
|
|
503
|
+
qubits = cirq.LineQubit.range(5)
|
|
504
|
+
|
|
505
|
+
circuit = cirq.FrozenCircuit(_create_ghz(5, qubits))
|
|
506
|
+
|
|
507
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, list[cirq.PauliString]] = {}
|
|
508
|
+
circuits_to_pauli[circuit] = [cirq.PauliString({q: cirq.X for q in qubits})]
|
|
509
|
+
with pytest.raises(
|
|
510
|
+
ValueError, match="Must provide non-zero readout_repetitions for readout" + " calibration."
|
|
511
|
+
):
|
|
512
|
+
measure_pauli_strings(
|
|
513
|
+
circuits_to_pauli, cirq.Simulator(), 1000, 0, 1000, np.random.default_rng()
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def test_rng_type_mismatch() -> None:
|
|
518
|
+
"""Test that the rng is not a numpy random generator or a seed."""
|
|
519
|
+
qubits = cirq.LineQubit.range(5)
|
|
520
|
+
|
|
521
|
+
circuit = cirq.FrozenCircuit(_create_ghz(5, qubits))
|
|
522
|
+
|
|
523
|
+
circuits_to_pauli: Dict[cirq.FrozenCircuit, list[cirq.PauliString]] = {}
|
|
524
|
+
circuits_to_pauli[circuit] = [cirq.PauliString({q: cirq.X for q in qubits})]
|
|
525
|
+
with pytest.raises(ValueError, match="Must provide a numpy random generator or a seed"):
|
|
526
|
+
measure_pauli_strings(
|
|
527
|
+
circuits_to_pauli, cirq.Simulator(), 1000, 1000, 1000, "test" # type: ignore[arg-type]
|
|
528
|
+
)
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
"""Tools for running circuits in a shuffled order with readout error benchmarking."""
|
|
15
15
|
import time
|
|
16
|
-
from typing import Optional, Union
|
|
16
|
+
from typing import Optional, Union, Dict, Tuple, List
|
|
17
17
|
|
|
18
18
|
import numpy as np
|
|
19
19
|
|
|
@@ -50,9 +50,9 @@ def _validate_input(
|
|
|
50
50
|
if not isinstance(rng_or_seed, np.random.Generator) and not isinstance(rng_or_seed, int):
|
|
51
51
|
raise ValueError("Must provide a numpy random generator or a seed")
|
|
52
52
|
|
|
53
|
-
# Check num_random_bitstrings is bigger than 0
|
|
54
|
-
if num_random_bitstrings
|
|
55
|
-
raise ValueError("Must provide
|
|
53
|
+
# Check num_random_bitstrings is bigger than or equal to 0
|
|
54
|
+
if num_random_bitstrings < 0:
|
|
55
|
+
raise ValueError("Must provide zero or more num_random_bitstrings.")
|
|
56
56
|
|
|
57
57
|
# Check readout_repetitions is bigger than 0
|
|
58
58
|
if readout_repetitions <= 0:
|
|
@@ -157,8 +157,8 @@ def run_shuffled_with_readout_benchmarking(
|
|
|
157
157
|
rng_or_seed: Union[np.random.Generator, int],
|
|
158
158
|
num_random_bitstrings: int = 100,
|
|
159
159
|
readout_repetitions: int = 1000,
|
|
160
|
-
qubits: Optional[
|
|
161
|
-
) -> tuple[list[ResultDict], SingleQubitReadoutCalibrationResult]:
|
|
160
|
+
qubits: Optional[Union[List[ops.Qid], List[List[ops.Qid]]]] = None,
|
|
161
|
+
) -> tuple[list[ResultDict], Dict[Tuple[ops.Qid, ...], SingleQubitReadoutCalibrationResult]]:
|
|
162
162
|
"""Run the circuits in a shuffled order with readout error benchmarking.
|
|
163
163
|
|
|
164
164
|
Args:
|
|
@@ -168,15 +168,16 @@ def run_shuffled_with_readout_benchmarking(
|
|
|
168
168
|
rng_or_seed: A random number generator used to generate readout circuits.
|
|
169
169
|
Or an integer seed.
|
|
170
170
|
num_random_bitstrings: The number of random bitstrings for measuring readout.
|
|
171
|
+
If set to 0, no readout calibration circuits are generated.
|
|
171
172
|
readout_repetitions: The number of repetitions for each readout bitstring.
|
|
172
173
|
qubits: The qubits to benchmark readout errors. If None, all qubits in the
|
|
173
|
-
input_circuits are used.
|
|
174
|
+
input_circuits are used. Can be a list of qubits or a list of tuples
|
|
175
|
+
of qubits.
|
|
174
176
|
|
|
175
177
|
Returns:
|
|
176
178
|
A tuple containing:
|
|
177
179
|
- A list of dictionaries with the unshuffled measurement results.
|
|
178
|
-
- A dictionary mapping each
|
|
179
|
-
where e0 is the 0->1 readout error rate and e1 is the 1->0 readout error rate.
|
|
180
|
+
- A dictionary mapping each tuple of qubits to a SingleQubitReadoutCalibrationResult.
|
|
180
181
|
|
|
181
182
|
"""
|
|
182
183
|
|
|
@@ -185,31 +186,44 @@ def run_shuffled_with_readout_benchmarking(
|
|
|
185
186
|
)
|
|
186
187
|
|
|
187
188
|
# If input qubits is None, extract qubits from input circuits
|
|
189
|
+
qubits_to_measure: List[List[ops.Qid]] = []
|
|
188
190
|
if qubits is None:
|
|
189
191
|
qubits_set: set[ops.Qid] = set()
|
|
190
192
|
for circuit in input_circuits:
|
|
191
193
|
qubits_set.update(circuit.all_qubits())
|
|
192
|
-
|
|
194
|
+
qubits_to_measure = [sorted(qubits_set)]
|
|
195
|
+
elif isinstance(qubits[0], ops.Qid):
|
|
196
|
+
qubits_to_measure = [qubits] # type: ignore
|
|
197
|
+
else:
|
|
198
|
+
qubits_to_measure = qubits # type: ignore
|
|
199
|
+
|
|
200
|
+
# Generate the readout calibration circuits if num_random_bitstrings>0
|
|
201
|
+
# Else all_readout_calibration_circuits and all_random_bitstrings are empty
|
|
202
|
+
all_readout_calibration_circuits = []
|
|
203
|
+
all_random_bitstrings = []
|
|
193
204
|
|
|
194
|
-
# Generate the readout calibration circuits
|
|
195
205
|
rng = (
|
|
196
206
|
rng_or_seed
|
|
197
207
|
if isinstance(rng_or_seed, np.random.Generator)
|
|
198
208
|
else np.random.default_rng(rng_or_seed)
|
|
199
209
|
)
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
210
|
+
if num_random_bitstrings > 0:
|
|
211
|
+
for qubit_group in qubits_to_measure:
|
|
212
|
+
readout_calibration_circuits, random_bitstrings = (
|
|
213
|
+
_generate_readout_calibration_circuits(qubit_group, rng, num_random_bitstrings)
|
|
214
|
+
)
|
|
215
|
+
all_readout_calibration_circuits.extend(readout_calibration_circuits)
|
|
216
|
+
all_random_bitstrings.append(random_bitstrings)
|
|
203
217
|
|
|
204
218
|
# Shuffle the circuits
|
|
205
219
|
if isinstance(circuit_repetitions, int):
|
|
206
220
|
circuit_repetitions = [circuit_repetitions] * len(input_circuits)
|
|
207
221
|
all_repetitions = circuit_repetitions + [readout_repetitions] * len(
|
|
208
|
-
|
|
222
|
+
all_readout_calibration_circuits
|
|
209
223
|
)
|
|
210
224
|
|
|
211
225
|
shuffled_circuits, all_repetitions, unshuf_order = _shuffle_circuits(
|
|
212
|
-
input_circuits +
|
|
226
|
+
input_circuits + all_readout_calibration_circuits, all_repetitions, rng
|
|
213
227
|
)
|
|
214
228
|
|
|
215
229
|
# Run the shuffled circuits and measure
|
|
@@ -222,8 +236,15 @@ def run_shuffled_with_readout_benchmarking(
|
|
|
222
236
|
unshuffled_readout_measurements = unshuffled_measurements[len(input_circuits) :]
|
|
223
237
|
|
|
224
238
|
# Analyze results
|
|
225
|
-
readout_calibration_results =
|
|
226
|
-
|
|
227
|
-
)
|
|
239
|
+
readout_calibration_results = {}
|
|
240
|
+
start_idx = 0
|
|
241
|
+
for qubit_group, random_bitstrings in zip(qubits_to_measure, all_random_bitstrings):
|
|
242
|
+
end_idx = start_idx + len(random_bitstrings)
|
|
243
|
+
group_measurements = unshuffled_readout_measurements[start_idx:end_idx]
|
|
244
|
+
calibration_result = _analyze_readout_results(
|
|
245
|
+
group_measurements, random_bitstrings, readout_repetitions, qubit_group, timestamp
|
|
246
|
+
)
|
|
247
|
+
readout_calibration_results[tuple(qubit_group)] = calibration_result
|
|
248
|
+
start_idx = end_idx
|
|
228
249
|
|
|
229
250
|
return unshuffled_input_circuits_measiurements, readout_calibration_results
|