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.

Files changed (59) hide show
  1. cirq/_version.py +1 -1
  2. cirq/_version_test.py +1 -1
  3. cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation.py +211 -107
  4. cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation_test.py +347 -3
  5. cirq/transformers/analytical_decompositions/two_qubit_to_cz.py +18 -18
  6. cirq/transformers/analytical_decompositions/two_qubit_to_fsim.py +18 -19
  7. cirq/transformers/analytical_decompositions/two_qubit_to_ms.py +8 -10
  8. cirq/transformers/analytical_decompositions/two_qubit_to_sqrt_iswap.py +26 -28
  9. cirq/transformers/drop_empty_moments.py +4 -2
  10. cirq/transformers/drop_negligible_operations.py +6 -4
  11. cirq/transformers/dynamical_decoupling.py +6 -4
  12. cirq/transformers/dynamical_decoupling_test.py +8 -6
  13. cirq/transformers/eject_phased_paulis.py +14 -12
  14. cirq/transformers/eject_z.py +8 -6
  15. cirq/transformers/expand_composite.py +5 -3
  16. cirq/transformers/gauge_compiling/sqrt_cz_gauge.py +3 -1
  17. cirq/transformers/heuristic_decompositions/two_qubit_gate_tabulation.py +4 -1
  18. cirq/transformers/insertion_sort.py +6 -4
  19. cirq/transformers/measurement_transformers.py +21 -21
  20. cirq/transformers/merge_k_qubit_gates.py +11 -9
  21. cirq/transformers/merge_k_qubit_gates_test.py +5 -3
  22. cirq/transformers/merge_single_qubit_gates.py +15 -13
  23. cirq/transformers/optimize_for_target_gateset.py +14 -12
  24. cirq/transformers/optimize_for_target_gateset_test.py +7 -3
  25. cirq/transformers/qubit_management_transformers.py +10 -8
  26. cirq/transformers/randomized_measurements.py +9 -7
  27. cirq/transformers/routing/initial_mapper.py +5 -3
  28. cirq/transformers/routing/line_initial_mapper.py +15 -13
  29. cirq/transformers/routing/mapping_manager.py +9 -9
  30. cirq/transformers/routing/route_circuit_cqc.py +17 -15
  31. cirq/transformers/routing/visualize_routed_circuit.py +7 -6
  32. cirq/transformers/stratify.py +13 -11
  33. cirq/transformers/synchronize_terminal_measurements.py +9 -9
  34. cirq/transformers/target_gatesets/compilation_target_gateset.py +19 -17
  35. cirq/transformers/target_gatesets/compilation_target_gateset_test.py +11 -7
  36. cirq/transformers/target_gatesets/cz_gateset.py +4 -2
  37. cirq/transformers/target_gatesets/sqrt_iswap_gateset.py +5 -3
  38. cirq/transformers/transformer_api.py +17 -15
  39. cirq/transformers/transformer_primitives.py +22 -20
  40. cirq/transformers/transformer_primitives_test.py +3 -1
  41. cirq/value/classical_data.py +26 -26
  42. cirq/value/condition.py +23 -21
  43. cirq/value/duration.py +11 -8
  44. cirq/value/linear_dict.py +22 -20
  45. cirq/value/periodic_value.py +4 -4
  46. cirq/value/probability.py +3 -1
  47. cirq/value/product_state.py +14 -12
  48. cirq/work/collector.py +7 -5
  49. cirq/work/observable_measurement.py +24 -22
  50. cirq/work/observable_measurement_data.py +9 -7
  51. cirq/work/observable_readout_calibration.py +4 -1
  52. cirq/work/observable_readout_calibration_test.py +4 -1
  53. cirq/work/observable_settings.py +4 -2
  54. cirq/work/pauli_sum_collector.py +8 -6
  55. {cirq_core-1.6.0.dev20250501173104.dist-info → cirq_core-1.6.0.dev20250501231232.dist-info}/METADATA +1 -1
  56. {cirq_core-1.6.0.dev20250501173104.dist-info → cirq_core-1.6.0.dev20250501231232.dist-info}/RECORD +59 -59
  57. {cirq_core-1.6.0.dev20250501173104.dist-info → cirq_core-1.6.0.dev20250501231232.dist-info}/WHEEL +0 -0
  58. {cirq_core-1.6.0.dev20250501173104.dist-info → cirq_core-1.6.0.dev20250501231232.dist-info}/licenses/LICENSE +0 -0
  59. {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
@@ -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.dev20250501173104"
31
+ __version__ = "1.6.0.dev20250501231232"
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.dev20250501173104"
6
+ assert cirq.__version__ == "1.6.0.dev20250501231232"
@@ -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: Dict[circuits.FrozenCircuit, list[ops.PauliString]],
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
- for pauli_strings in circuits_to_pauli.values():
77
- for pauli_str in pauli_strings:
78
- if not isinstance(pauli_str, ops.PauliString):
79
- raise TypeError(
80
- f"All elements in the Pauli string lists must be cirq.PauliString "
81
- f"instances, got {type(pauli_str)}."
82
- )
83
-
84
- if all(q == ops.I for q in pauli_str):
85
- raise ValueError(
86
- "Empty Pauli strings or Pauli strings consisting "
87
- "only of Pauli I are not allowed. Please provide "
88
- "valid input Pauli strings."
89
- )
90
- if pauli_str.coefficient.imag != 0:
91
- raise ValueError(
92
- "Cannot compute expectation value of a non-Hermitian PauliString. "
93
- "Coefficient must be real."
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 _pauli_string_to_basis_change_ops(
114
- pauli_string: ops.PauliString, qid_list: list[ops.Qid]
115
- ) -> List[ops.Operation]:
116
- """Creates operations to change to the eigenbasis of the given Pauli string.
117
-
118
- This function constructs a list of ops.Operation that performs basis changes
119
- necessary to measure the given pauli_string in the computational basis.
120
-
121
- Args:
122
- pauli_string: The Pauli string to diagonalize.
123
- qid_list: An ordered list of the qubits in the circuit.
124
-
125
- Returns:
126
- A list of Operations that, when applied before measurement in the
127
- computational basis, effectively measures in the eigenbasis of
128
- pauli_strings.
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: # Iterate over ALL qubits in the circuit
132
- if qubit in pauli_string:
133
- pauli_op = pauli_string[qubit]
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
- # If pauli_op is Z or I, no operation needed
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: List[ops.Qid],
189
- pauli_strings: List[ops.PauliString],
190
- circuit_results: List[ResultDict],
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
- ) -> List[PauliStringMeasurementResult]:
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 list of PauliStrings that are measured.
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 pauli_index, circuit_result in enumerate(circuit_results):
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
- pauli_string = pauli_strings[pauli_index]
225
- qubits_sorted = sorted(pauli_string.qubits)
226
- qubit_indices = [qubits.index(q) for q in qubits_sorted]
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
- confusion_matrices = (
229
- _build_many_one_qubits_confusion_matrix(calibration_results[tuple(qubits_sorted)])
230
- if disable_readout_mitigation is False
231
- else _build_many_one_qubits_empty_confusion_matrix(len(qubits_sorted))
232
- )
233
- tensored_cm = TensoredConfusionMatrices(
234
- confusion_matrices,
235
- [[q] for q in qubits_sorted],
236
- repetitions=pauli_repetitions,
237
- timestamp=timestamp,
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
- # Create a mask for the relevant qubits in the measurement results
241
- relevant_bits = measurement_results[:, qubit_indices]
329
+ # Create a mask for the relevant qubits in the measurement results
330
+ relevant_bits = measurement_results[:, qubit_indices]
242
331
 
243
- # Calculate the mitigated expectation.
244
- raw_mitigated_values, raw_d_m = tensored_cm.readout_mitigation_pauli_uncorrelated(
245
- qubits_sorted, relevant_bits
246
- )
247
- mitigated_values_with_coefficient = raw_mitigated_values * pauli_string.coefficient.real
248
- d_m_with_coefficient = raw_d_m * abs(pauli_string.coefficient.real)
249
-
250
- # Calculate the unmitigated expectation.
251
- parity = np.sum(relevant_bits, axis=1) % 2
252
- raw_unmitigated_values = 1 - 2 * np.mean(parity)
253
- raw_d_unmit = 2 * np.sqrt(np.mean(parity) * (1 - np.mean(parity)) / pauli_repetitions)
254
- unmitigated_value_with_coefficient = raw_unmitigated_values * pauli_string.coefficient
255
- d_unmit_with_coefficient = raw_d_unmit * abs(pauli_string.coefficient)
256
-
257
- pauli_measurement_results.append(
258
- PauliStringMeasurementResult(
259
- pauli_string=pauli_strings[pauli_index],
260
- mitigated_expectation=mitigated_values_with_coefficient,
261
- mitigated_stddev=d_m_with_coefficient,
262
- unmitigated_expectation=unmitigated_value_with_coefficient,
263
- unmitigated_stddev=d_unmit_with_coefficient,
264
- calibration_result=(
265
- calibration_results[tuple(qubits_sorted)]
266
- if disable_readout_mitigation is False
267
- else None
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: Dict[circuits.FrozenCircuit, list[ops.PauliString]],
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 list of circuits and corresponding List[PauliString] to measure.
287
- For each circuit-List[PauliString] pair, it:
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 a list of Pauli strings
297
- to measure.
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 pauli_strings in circuits_to_pauli.values():
325
- for pauli_string in pauli_strings:
326
- unique_qubit_tuples.add(tuple(sorted(pauli_string.qubits)))
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, pauli_strings in circuits_to_pauli.items():
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 pauli_string in pauli_strings:
436
+ for pauli_strings in pauli_string_groups:
337
437
  basis_change_circuit = (
338
438
  input_circuit_unfrozen
339
- + _pauli_string_to_basis_change_ops(pauli_string, qid_list)
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, pauli_strings in circuits_to_pauli.items():
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
- pauli_strings,
366
- circuits_results[circuit_result_index : circuit_result_index + len(pauli_strings)],
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(pauli_strings)
482
+ circuit_result_index += len(pauli_string_groups)
379
483
  return results