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
cirq/_version.py
CHANGED
cirq/_version_test.py
CHANGED
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
"""Tools for measuring expectation values of Pauli strings with readout error mitigation."""
|
|
15
|
+
import itertools
|
|
15
16
|
import time
|
|
16
|
-
from typing import Dict, List, Optional, Tuple, Union
|
|
17
|
+
from typing import cast, Dict, FrozenSet, List, Optional, Sequence, Tuple, Union
|
|
17
18
|
|
|
18
19
|
import attrs
|
|
19
20
|
import numpy as np
|
|
@@ -59,8 +60,74 @@ class CircuitToPauliStringsMeasurementResult:
|
|
|
59
60
|
results: List[PauliStringMeasurementResult]
|
|
60
61
|
|
|
61
62
|
|
|
63
|
+
def _commute_or_identity(
|
|
64
|
+
op1: Union[ops.Pauli, ops.IdentityGate], op2: Union[ops.Pauli, ops.IdentityGate]
|
|
65
|
+
) -> bool:
|
|
66
|
+
if op1 == ops.I or op2 == ops.I:
|
|
67
|
+
return True
|
|
68
|
+
return op1 == op2
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _are_two_pauli_strings_qubit_wise_commuting(
|
|
72
|
+
pauli_str1: ops.PauliString,
|
|
73
|
+
pauli_str2: ops.PauliString,
|
|
74
|
+
all_qubits: Union[list[ops.Qid], FrozenSet[ops.Qid]],
|
|
75
|
+
) -> bool:
|
|
76
|
+
for qubit in all_qubits:
|
|
77
|
+
op1 = pauli_str1.get(qubit, default=ops.I)
|
|
78
|
+
op2 = pauli_str2.get(qubit, default=ops.I)
|
|
79
|
+
|
|
80
|
+
if not _commute_or_identity(op1, op2):
|
|
81
|
+
return False
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _validate_group_paulis_qwc(
|
|
86
|
+
pauli_strs: list[ops.PauliString], all_qubits: Union[list[ops.Qid], FrozenSet[ops.Qid]]
|
|
87
|
+
):
|
|
88
|
+
"""Checks if a group of Pauli strings are Qubit-Wise Commuting.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
pauli_strings: A list of cirq.PauliString objects.
|
|
92
|
+
all_qubits: A list of all qubits to consider for the QWC check.
|
|
93
|
+
The check is performed for each qubit in this list.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
True if the group is QWC, False otherwise.
|
|
97
|
+
"""
|
|
98
|
+
if len(pauli_strs) <= 1:
|
|
99
|
+
return True
|
|
100
|
+
for p1, p2 in itertools.combinations(pauli_strs, 2):
|
|
101
|
+
if not _are_two_pauli_strings_qubit_wise_commuting(p1, p2, all_qubits):
|
|
102
|
+
return False
|
|
103
|
+
return True
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _validate_single_pauli_string(pauli_str: ops.PauliString):
|
|
107
|
+
if not isinstance(pauli_str, ops.PauliString):
|
|
108
|
+
raise TypeError(
|
|
109
|
+
f"All elements in the Pauli string lists must be cirq.PauliString "
|
|
110
|
+
f"instances, got {type(pauli_str)}."
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if all(q == ops.I for q in pauli_str) or not pauli_str:
|
|
114
|
+
raise ValueError(
|
|
115
|
+
"Empty Pauli strings or Pauli strings consisting "
|
|
116
|
+
"only of Pauli I are not allowed. Please provide "
|
|
117
|
+
"valid input Pauli strings."
|
|
118
|
+
)
|
|
119
|
+
if pauli_str.coefficient.imag != 0:
|
|
120
|
+
raise ValueError(
|
|
121
|
+
"Cannot compute expectation value of a non-Hermitian PauliString. "
|
|
122
|
+
"Coefficient must be real."
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
62
126
|
def _validate_input(
|
|
63
|
-
circuits_to_pauli:
|
|
127
|
+
circuits_to_pauli: Union[
|
|
128
|
+
Dict[circuits.FrozenCircuit, list[ops.PauliString]],
|
|
129
|
+
Dict[circuits.FrozenCircuit, list[list[ops.PauliString]]],
|
|
130
|
+
],
|
|
64
131
|
pauli_repetitions: int,
|
|
65
132
|
readout_repetitions: int,
|
|
66
133
|
num_random_bitstrings: int,
|
|
@@ -73,25 +140,39 @@ def _validate_input(
|
|
|
73
140
|
if not isinstance(circuit, circuits.FrozenCircuit):
|
|
74
141
|
raise TypeError("All keys in 'circuits_to_pauli' must be FrozenCircuit instances.")
|
|
75
142
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
143
|
+
first_value: Union[list[ops.PauliString], list[list[ops.PauliString]]] = next(
|
|
144
|
+
iter(circuits_to_pauli.values()) # type: ignore
|
|
145
|
+
)
|
|
146
|
+
for circuit, pauli_strs_list in circuits_to_pauli.items():
|
|
147
|
+
if isinstance(pauli_strs_list, Sequence) and isinstance(first_value[0], Sequence):
|
|
148
|
+
for pauli_strs in pauli_strs_list:
|
|
149
|
+
if not pauli_strs:
|
|
150
|
+
raise ValueError("Empty group of Pauli strings is not allowed")
|
|
151
|
+
if not (
|
|
152
|
+
isinstance(pauli_strs, Sequence) and isinstance(pauli_strs[0], ops.PauliString)
|
|
153
|
+
):
|
|
154
|
+
raise TypeError(
|
|
155
|
+
f"Inconsistent type in list for circuit {circuit}. "
|
|
156
|
+
f"Expected all elements to be sequences of ops.PauliString, "
|
|
157
|
+
f"but found {type(pauli_strs)}."
|
|
158
|
+
)
|
|
159
|
+
if not _validate_group_paulis_qwc(pauli_strs, circuit.all_qubits()):
|
|
160
|
+
raise ValueError(
|
|
161
|
+
f"Pauli group containing {pauli_strs} is invalid: "
|
|
162
|
+
f"The group of Pauli strings are not "
|
|
163
|
+
f"Qubit-Wise Commuting with each other."
|
|
164
|
+
)
|
|
165
|
+
for pauli_str in pauli_strs:
|
|
166
|
+
_validate_single_pauli_string(pauli_str)
|
|
167
|
+
elif isinstance(pauli_strs_list, Sequence) and isinstance(first_value[0], ops.PauliString):
|
|
168
|
+
for pauli_str in pauli_strs_list: # type: ignore
|
|
169
|
+
_validate_single_pauli_string(pauli_str)
|
|
170
|
+
else:
|
|
171
|
+
raise TypeError(
|
|
172
|
+
f"Expected all elements to be either a sequence of PauliStrings"
|
|
173
|
+
f" or sequences of ops.PauliStrings. "
|
|
174
|
+
f"Got {type(pauli_strs_list)} instead."
|
|
175
|
+
)
|
|
95
176
|
|
|
96
177
|
# Check rng is a numpy random generator
|
|
97
178
|
if not isinstance(rng_or_seed, np.random.Generator) and not isinstance(rng_or_seed, int):
|
|
@@ -110,32 +191,39 @@ def _validate_input(
|
|
|
110
191
|
raise ValueError("Must provide non-zero readout_repetitions for readout calibration.")
|
|
111
192
|
|
|
112
193
|
|
|
113
|
-
def
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
194
|
+
def _normalize_input_paulis(
|
|
195
|
+
circuits_to_pauli: Union[
|
|
196
|
+
Dict[circuits.FrozenCircuit, list[ops.PauliString]],
|
|
197
|
+
Dict[circuits.FrozenCircuit, list[list[ops.PauliString]]],
|
|
198
|
+
],
|
|
199
|
+
) -> Dict[circuits.FrozenCircuit, list[list[ops.PauliString]]]:
|
|
200
|
+
first_value = next(iter(circuits_to_pauli.values()))
|
|
201
|
+
if (
|
|
202
|
+
first_value
|
|
203
|
+
and isinstance(first_value, list)
|
|
204
|
+
and isinstance(first_value[0], ops.PauliString)
|
|
205
|
+
):
|
|
206
|
+
input_dict = cast(Dict[circuits.FrozenCircuit, List[ops.PauliString]], circuits_to_pauli)
|
|
207
|
+
normalized_circuits_to_pauli: Dict[circuits.FrozenCircuit, list[list[ops.PauliString]]] = {}
|
|
208
|
+
for circuit, paulis in input_dict.items():
|
|
209
|
+
normalized_circuits_to_pauli[circuit] = [[ps] for ps in paulis]
|
|
210
|
+
return normalized_circuits_to_pauli
|
|
211
|
+
return cast(Dict[circuits.FrozenCircuit, List[List[ops.PauliString]]], circuits_to_pauli)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _pauli_strings_to_basis_change_ops(
|
|
215
|
+
pauli_strings: list[ops.PauliString], qid_list: list[ops.Qid]
|
|
216
|
+
):
|
|
130
217
|
operations = []
|
|
131
|
-
for qubit in qid_list:
|
|
132
|
-
|
|
133
|
-
pauli_op =
|
|
218
|
+
for qubit in qid_list:
|
|
219
|
+
for pauli_str in pauli_strings:
|
|
220
|
+
pauli_op = pauli_str.get(qubit, default=ops.I)
|
|
134
221
|
if pauli_op == ops.X:
|
|
135
222
|
operations.append(ops.ry(-np.pi / 2)(qubit)) # =cirq.H
|
|
223
|
+
break
|
|
136
224
|
elif pauli_op == ops.Y:
|
|
137
225
|
operations.append(ops.rx(np.pi / 2)(qubit))
|
|
138
|
-
|
|
226
|
+
break
|
|
139
227
|
return operations
|
|
140
228
|
|
|
141
229
|
|
|
@@ -185,14 +273,14 @@ def _build_many_one_qubits_empty_confusion_matrix(qubits_length: int) -> list[np
|
|
|
185
273
|
|
|
186
274
|
|
|
187
275
|
def _process_pauli_measurement_results(
|
|
188
|
-
qubits:
|
|
189
|
-
|
|
190
|
-
circuit_results:
|
|
276
|
+
qubits: list[ops.Qid],
|
|
277
|
+
pauli_string_groups: list[list[ops.PauliString]],
|
|
278
|
+
circuit_results: list[ResultDict],
|
|
191
279
|
calibration_results: Dict[Tuple[ops.Qid, ...], SingleQubitReadoutCalibrationResult],
|
|
192
280
|
pauli_repetitions: int,
|
|
193
281
|
timestamp: float,
|
|
194
282
|
disable_readout_mitigation: bool = False,
|
|
195
|
-
) ->
|
|
283
|
+
) -> list[PauliStringMeasurementResult]:
|
|
196
284
|
"""Calculates both error-mitigated expectation values and unmitigated expectation values
|
|
197
285
|
from measurement results.
|
|
198
286
|
|
|
@@ -203,7 +291,7 @@ def _process_pauli_measurement_results(
|
|
|
203
291
|
|
|
204
292
|
Args:
|
|
205
293
|
qubits: Qubits to build confusion matrices for. In a sorted order.
|
|
206
|
-
pauli_strings: The
|
|
294
|
+
pauli_strings: The lists of QWC Pauli string groups that are measured.
|
|
207
295
|
circuit_results: A list of ResultDict obtained
|
|
208
296
|
from running the Pauli measurement circuits.
|
|
209
297
|
confusion_matrices: A list of confusion matrices from calibration results.
|
|
@@ -218,62 +306,66 @@ def _process_pauli_measurement_results(
|
|
|
218
306
|
|
|
219
307
|
pauli_measurement_results: List[PauliStringMeasurementResult] = []
|
|
220
308
|
|
|
221
|
-
for
|
|
309
|
+
for pauli_group_index, circuit_result in enumerate(circuit_results):
|
|
222
310
|
measurement_results = circuit_result.measurements["m"]
|
|
311
|
+
pauli_strs = pauli_string_groups[pauli_group_index]
|
|
223
312
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
313
|
+
for pauli_str in pauli_strs:
|
|
314
|
+
qubits_sorted = sorted(pauli_str.qubits)
|
|
315
|
+
qubit_indices = [qubits.index(q) for q in qubits_sorted]
|
|
227
316
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
317
|
+
confusion_matrices = (
|
|
318
|
+
_build_many_one_qubits_confusion_matrix(calibration_results[tuple(qubits_sorted)])
|
|
319
|
+
if disable_readout_mitigation is False
|
|
320
|
+
else _build_many_one_qubits_empty_confusion_matrix(len(qubits_sorted))
|
|
321
|
+
)
|
|
322
|
+
tensored_cm = TensoredConfusionMatrices(
|
|
323
|
+
confusion_matrices,
|
|
324
|
+
[[q] for q in qubits_sorted],
|
|
325
|
+
repetitions=pauli_repetitions,
|
|
326
|
+
timestamp=timestamp,
|
|
327
|
+
)
|
|
239
328
|
|
|
240
|
-
|
|
241
|
-
|
|
329
|
+
# Create a mask for the relevant qubits in the measurement results
|
|
330
|
+
relevant_bits = measurement_results[:, qubit_indices]
|
|
242
331
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
332
|
+
# Calculate the mitigated expectation.
|
|
333
|
+
raw_mitigated_values, raw_d_m = tensored_cm.readout_mitigation_pauli_uncorrelated(
|
|
334
|
+
qubits_sorted, relevant_bits
|
|
335
|
+
)
|
|
336
|
+
mitigated_values_with_coefficient = raw_mitigated_values * pauli_str.coefficient.real
|
|
337
|
+
d_m_with_coefficient = raw_d_m * abs(pauli_str.coefficient.real)
|
|
338
|
+
|
|
339
|
+
# Calculate the unmitigated expectation.
|
|
340
|
+
parity = np.sum(relevant_bits, axis=1) % 2
|
|
341
|
+
raw_unmitigated_values = 1 - 2 * np.mean(parity)
|
|
342
|
+
raw_d_unmit = 2 * np.sqrt(np.mean(parity) * (1 - np.mean(parity)) / pauli_repetitions)
|
|
343
|
+
unmitigated_value_with_coefficient = raw_unmitigated_values * pauli_str.coefficient.real
|
|
344
|
+
d_unmit_with_coefficient = raw_d_unmit * abs(pauli_str.coefficient.real)
|
|
345
|
+
|
|
346
|
+
pauli_measurement_results.append(
|
|
347
|
+
PauliStringMeasurementResult(
|
|
348
|
+
pauli_string=pauli_str,
|
|
349
|
+
mitigated_expectation=mitigated_values_with_coefficient,
|
|
350
|
+
mitigated_stddev=d_m_with_coefficient,
|
|
351
|
+
unmitigated_expectation=unmitigated_value_with_coefficient,
|
|
352
|
+
unmitigated_stddev=d_unmit_with_coefficient,
|
|
353
|
+
calibration_result=(
|
|
354
|
+
calibration_results[tuple(qubits_sorted)]
|
|
355
|
+
if disable_readout_mitigation is False
|
|
356
|
+
else None
|
|
357
|
+
),
|
|
358
|
+
)
|
|
269
359
|
)
|
|
270
|
-
)
|
|
271
360
|
|
|
272
361
|
return pauli_measurement_results
|
|
273
362
|
|
|
274
363
|
|
|
275
364
|
def measure_pauli_strings(
|
|
276
|
-
circuits_to_pauli:
|
|
365
|
+
circuits_to_pauli: Union[
|
|
366
|
+
Dict[circuits.FrozenCircuit, List[ops.PauliString]],
|
|
367
|
+
Dict[circuits.FrozenCircuit, List[List[ops.PauliString]]],
|
|
368
|
+
],
|
|
277
369
|
sampler: work.Sampler,
|
|
278
370
|
pauli_repetitions: int,
|
|
279
371
|
readout_repetitions: int,
|
|
@@ -283,8 +375,8 @@ def measure_pauli_strings(
|
|
|
283
375
|
"""Measures expectation values of Pauli strings on given circuits with/without
|
|
284
376
|
readout error mitigation.
|
|
285
377
|
|
|
286
|
-
This function takes a
|
|
287
|
-
For each circuit
|
|
378
|
+
This function takes a dictionary mapping circuits to lists of QWC Pauli string groups.
|
|
379
|
+
For each circuit and its associated list of QWC pauli string group, it:
|
|
288
380
|
1. Constructs circuits to measure the Pauli string expectation value by
|
|
289
381
|
adding basis change moments and measurement operations.
|
|
290
382
|
2. Runs shuffled readout benchmarking on these circuits to calibrate readout errors.
|
|
@@ -293,8 +385,13 @@ def measure_pauli_strings(
|
|
|
293
385
|
each Pauli string.
|
|
294
386
|
|
|
295
387
|
Args:
|
|
296
|
-
circuits_to_pauli: A dictionary mapping circuits to
|
|
297
|
-
|
|
388
|
+
circuits_to_pauli: A dictionary mapping circuits to either:
|
|
389
|
+
- A list of QWC groups (List[List[ops.PauliString]]). Each QWC group
|
|
390
|
+
is a list of PauliStrings that are mutually Qubit-Wise Commuting.
|
|
391
|
+
Pauli strings within the same group will be calculated using the
|
|
392
|
+
same measurement results.
|
|
393
|
+
- A list of PauliStrings (List[ops.PauliString]). In this case, each
|
|
394
|
+
PauliString is treated as its own measurement group.
|
|
298
395
|
sampler: The sampler to use.
|
|
299
396
|
pauli_repetitions: The number of repetitions for each circuit when measuring
|
|
300
397
|
Pauli strings.
|
|
@@ -319,24 +416,27 @@ def measure_pauli_strings(
|
|
|
319
416
|
rng_or_seed,
|
|
320
417
|
)
|
|
321
418
|
|
|
419
|
+
normalized_circuits_to_pauli = _normalize_input_paulis(circuits_to_pauli)
|
|
420
|
+
|
|
322
421
|
# Extract unique qubit tuples from input pauli strings
|
|
323
422
|
unique_qubit_tuples = set()
|
|
324
|
-
for
|
|
325
|
-
for
|
|
326
|
-
|
|
423
|
+
for pauli_string_groups in normalized_circuits_to_pauli.values():
|
|
424
|
+
for pauli_strings in pauli_string_groups:
|
|
425
|
+
for pauli_string in pauli_strings:
|
|
426
|
+
unique_qubit_tuples.add(tuple(sorted(pauli_string.qubits)))
|
|
327
427
|
# qubits_list is a list of qubit tuples
|
|
328
428
|
qubits_list = sorted(unique_qubit_tuples)
|
|
329
429
|
|
|
330
|
-
# Build the basis-change circuits for each Pauli string
|
|
430
|
+
# Build the basis-change circuits for each Pauli string group
|
|
331
431
|
pauli_measurement_circuits = list[circuits.Circuit]()
|
|
332
|
-
for input_circuit,
|
|
432
|
+
for input_circuit, pauli_string_groups in normalized_circuits_to_pauli.items():
|
|
333
433
|
qid_list = list(sorted(input_circuit.all_qubits()))
|
|
334
434
|
basis_change_circuits = []
|
|
335
435
|
input_circuit_unfrozen = input_circuit.unfreeze()
|
|
336
|
-
for
|
|
436
|
+
for pauli_strings in pauli_string_groups:
|
|
337
437
|
basis_change_circuit = (
|
|
338
438
|
input_circuit_unfrozen
|
|
339
|
-
+
|
|
439
|
+
+ _pauli_strings_to_basis_change_ops(pauli_strings, qid_list)
|
|
340
440
|
+ ops.measure(*qid_list, key="m")
|
|
341
441
|
)
|
|
342
442
|
basis_change_circuits.append(basis_change_circuit)
|
|
@@ -356,14 +456,18 @@ def measure_pauli_strings(
|
|
|
356
456
|
# Process the results to calculate expectation values
|
|
357
457
|
results: List[CircuitToPauliStringsMeasurementResult] = []
|
|
358
458
|
circuit_result_index = 0
|
|
359
|
-
for input_circuit,
|
|
459
|
+
for input_circuit, pauli_string_groups in normalized_circuits_to_pauli.items():
|
|
460
|
+
|
|
360
461
|
qubits_in_circuit = tuple(sorted(input_circuit.all_qubits()))
|
|
361
462
|
|
|
362
463
|
disable_readout_mitigation = False if num_random_bitstrings != 0 else True
|
|
464
|
+
|
|
363
465
|
pauli_measurement_results = _process_pauli_measurement_results(
|
|
364
466
|
list(qubits_in_circuit),
|
|
365
|
-
|
|
366
|
-
circuits_results[
|
|
467
|
+
pauli_string_groups,
|
|
468
|
+
circuits_results[
|
|
469
|
+
circuit_result_index : circuit_result_index + len(pauli_string_groups)
|
|
470
|
+
],
|
|
367
471
|
calibration_results,
|
|
368
472
|
pauli_repetitions,
|
|
369
473
|
time.time(),
|
|
@@ -375,5 +479,5 @@ def measure_pauli_strings(
|
|
|
375
479
|
)
|
|
376
480
|
)
|
|
377
481
|
|
|
378
|
-
circuit_result_index += len(
|
|
482
|
+
circuit_result_index += len(pauli_string_groups)
|
|
379
483
|
return results
|