cirq-core 1.6.0.dev20250623211210__py3-none-any.whl → 1.6.0.dev20250624211923__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/__init__.py CHANGED
@@ -380,6 +380,7 @@ from cirq.transformers import (
380
380
  merge_operations_to_circuit_op as merge_operations_to_circuit_op,
381
381
  merge_single_qubit_gates_to_phased_x_and_z as merge_single_qubit_gates_to_phased_x_and_z,
382
382
  merge_single_qubit_gates_to_phxz as merge_single_qubit_gates_to_phxz,
383
+ merge_single_qubit_gates_to_phxz_symbolized as merge_single_qubit_gates_to_phxz_symbolized,
383
384
  merge_single_qubit_moments_to_phxz as merge_single_qubit_moments_to_phxz,
384
385
  optimize_for_target_gateset as optimize_for_target_gateset,
385
386
  parameterized_2q_op_to_sqrt_iswap_operations as parameterized_2q_op_to_sqrt_iswap_operations,
cirq/_version.py CHANGED
@@ -28,4 +28,4 @@ if sys.version_info < (3, 11, 0): # pragma: no cover
28
28
  'of cirq (e.g. "python -m pip install cirq==1.5.0")'
29
29
  )
30
30
 
31
- __version__ = "1.6.0.dev20250623211210"
31
+ __version__ = "1.6.0.dev20250624211923"
cirq/_version_test.py CHANGED
@@ -3,4 +3,4 @@ import cirq
3
3
 
4
4
 
5
5
  def test_version() -> None:
6
- assert cirq.__version__ == "1.6.0.dev20250623211210"
6
+ assert cirq.__version__ == "1.6.0.dev20250624211923"
@@ -28,7 +28,9 @@ from cirq.contrib.shuffle_circuits import run_shuffled_with_readout_benchmarking
28
28
  from cirq.experiments.readout_confusion_matrix import TensoredConfusionMatrices
29
29
 
30
30
  if TYPE_CHECKING:
31
- from cirq.experiments import SingleQubitReadoutCalibrationResult
31
+ from cirq.experiments.single_qubit_readout_calibration import (
32
+ SingleQubitReadoutCalibrationResult,
33
+ )
32
34
  from cirq.study import ResultDict
33
35
 
34
36
 
@@ -217,6 +219,11 @@ def _normalize_input_paulis(
217
219
  return cast(dict[circuits.FrozenCircuit, list[list[ops.PauliString]]], circuits_to_pauli)
218
220
 
219
221
 
222
+ def _extract_readout_qubits(pauli_strings: list[ops.PauliString]) -> list[ops.Qid]:
223
+ """Extracts unique qubits from a list of QWC Pauli strings."""
224
+ return sorted(set(q for ps in pauli_strings for q in ps.qubits))
225
+
226
+
220
227
  def _pauli_strings_to_basis_change_ops(
221
228
  pauli_strings: list[ops.PauliString], qid_list: list[ops.Qid]
222
229
  ):
@@ -315,16 +322,38 @@ def _process_pauli_measurement_results(
315
322
  for pauli_group_index, circuit_result in enumerate(circuit_results):
316
323
  measurement_results = circuit_result.measurements["m"]
317
324
  pauli_strs = pauli_string_groups[pauli_group_index]
325
+ pauli_readout_qubits = _extract_readout_qubits(pauli_strs)
326
+
327
+ calibration_result = (
328
+ calibration_results[tuple(pauli_readout_qubits)]
329
+ if disable_readout_mitigation is False
330
+ else None
331
+ )
318
332
 
319
333
  for pauli_str in pauli_strs:
320
334
  qubits_sorted = sorted(pauli_str.qubits)
321
335
  qubit_indices = [qubits.index(q) for q in qubits_sorted]
322
336
 
323
- confusion_matrices = (
324
- _build_many_one_qubits_confusion_matrix(calibration_results[tuple(qubits_sorted)])
325
- if disable_readout_mitigation is False
326
- else _build_many_one_qubits_empty_confusion_matrix(len(qubits_sorted))
327
- )
337
+ if disable_readout_mitigation:
338
+ pauli_str_calibration_result = None
339
+ confusion_matrices = _build_many_one_qubits_empty_confusion_matrix(
340
+ len(qubits_sorted)
341
+ )
342
+ else:
343
+ if calibration_result is None:
344
+ # This case should be logically impossible if mitigation is on,
345
+ # so we raise an error.
346
+ raise ValueError(
347
+ f"Readout mitigation is enabled, but no calibration result was "
348
+ f"found for qubits {pauli_readout_qubits}."
349
+ )
350
+ pauli_str_calibration_result = calibration_result.readout_result_for_qubits(
351
+ qubits_sorted
352
+ )
353
+ confusion_matrices = _build_many_one_qubits_confusion_matrix(
354
+ pauli_str_calibration_result
355
+ )
356
+
328
357
  tensored_cm = TensoredConfusionMatrices(
329
358
  confusion_matrices,
330
359
  [[q] for q in qubits_sorted],
@@ -356,11 +385,7 @@ def _process_pauli_measurement_results(
356
385
  mitigated_stddev=d_m_with_coefficient,
357
386
  unmitigated_expectation=unmitigated_value_with_coefficient,
358
387
  unmitigated_stddev=d_unmit_with_coefficient,
359
- calibration_result=(
360
- calibration_results[tuple(qubits_sorted)]
361
- if disable_readout_mitigation is False
362
- else None
363
- ),
388
+ calibration_result=pauli_str_calibration_result,
364
389
  )
365
390
  )
366
391
 
@@ -428,8 +453,7 @@ def measure_pauli_strings(
428
453
  unique_qubit_tuples = set()
429
454
  for pauli_string_groups in normalized_circuits_to_pauli.values():
430
455
  for pauli_strings in pauli_string_groups:
431
- for pauli_string in pauli_strings:
432
- unique_qubit_tuples.add(tuple(sorted(pauli_string.qubits)))
456
+ unique_qubit_tuples.add(tuple(_extract_readout_qubits(pauli_strings)))
433
457
  # qubits_list is a list of qubit tuples
434
458
  qubits_list = sorted(unique_qubit_tuples)
435
459
 
@@ -23,7 +23,10 @@ import pytest
23
23
 
24
24
  import cirq
25
25
  from cirq.contrib.paulistring import measure_pauli_strings
26
- from cirq.experiments import SingleQubitReadoutCalibrationResult
26
+ from cirq.contrib.paulistring.pauli_string_measurement_with_readout_mitigation import (
27
+ _process_pauli_measurement_results,
28
+ )
29
+ from cirq.experiments.single_qubit_readout_calibration import SingleQubitReadoutCalibrationResult
27
30
  from cirq.experiments.single_qubit_readout_calibration_test import NoisySingleQubitReadoutSampler
28
31
 
29
32
 
@@ -867,3 +870,37 @@ def test_group_paulis_type_mismatch() -> None:
867
870
  measure_pauli_strings(
868
871
  circuits_to_pauli, cirq.Simulator(), 1000, 1000, 1000, np.random.default_rng()
869
872
  )
873
+
874
+
875
+ def test_process_pauli_measurement_results_raises_error_on_missing_calibration() -> None:
876
+ """Test that the function raises an error if the calibration result is missing."""
877
+ qubits: list[cirq.Qid] = [q for q in cirq.LineQubit.range(5)]
878
+
879
+ measurement_op = cirq.measure(*qubits, key='m')
880
+ test_circuits = list[cirq.Circuit]()
881
+ for _ in range(3):
882
+ circuit_list = []
883
+
884
+ circuit = _create_ghz(5, qubits) + measurement_op
885
+ circuit_list.append(circuit)
886
+ test_circuits.extend(circuit_list)
887
+
888
+ pauli_strings = [_generate_random_pauli_string(qubits, True) for _ in range(3)]
889
+ sampler = cirq.Simulator()
890
+
891
+ circuit_results = sampler.run_batch(test_circuits, repetitions=1000)
892
+
893
+ empty_calibration_result_dict = {tuple(qubits): None}
894
+
895
+ with pytest.raises(
896
+ ValueError,
897
+ match="Readout mitigation is enabled, but no calibration result was found for qubits",
898
+ ):
899
+ _process_pauli_measurement_results(
900
+ qubits,
901
+ [pauli_strings],
902
+ circuit_results[0], # type: ignore[arg-type]
903
+ empty_calibration_result_dict, # type: ignore[arg-type]
904
+ 1000,
905
+ 1.0,
906
+ )
@@ -179,6 +179,17 @@ class SingleQubitReadoutCalibrationResult:
179
179
  ax.set_ylabel('Percentile')
180
180
  return ax
181
181
 
182
+ def readout_result_for_qubits(
183
+ self, readout_qubits: list[ops.Qid]
184
+ ) -> SingleQubitReadoutCalibrationResult:
185
+ """Builds a calibration result for the specific readout qubits."""
186
+ return SingleQubitReadoutCalibrationResult(
187
+ zero_state_errors={qubit: self.zero_state_errors[qubit] for qubit in readout_qubits},
188
+ one_state_errors={qubit: self.one_state_errors[qubit] for qubit in readout_qubits},
189
+ timestamp=self.timestamp,
190
+ repetitions=self.repetitions,
191
+ )
192
+
182
193
  @classmethod
183
194
  def _from_json_dict_(
184
195
  cls, zero_state_errors, one_state_errors, repetitions, timestamp, **kwargs
@@ -100,6 +100,7 @@ from cirq.transformers.merge_single_qubit_gates import (
100
100
  merge_single_qubit_gates_to_phased_x_and_z as merge_single_qubit_gates_to_phased_x_and_z,
101
101
  merge_single_qubit_gates_to_phxz as merge_single_qubit_gates_to_phxz,
102
102
  merge_single_qubit_moments_to_phxz as merge_single_qubit_moments_to_phxz,
103
+ merge_single_qubit_gates_to_phxz_symbolized as merge_single_qubit_gates_to_phxz_symbolized,
103
104
  )
104
105
 
105
106
  from cirq.transformers.qubit_management_transformers import (
@@ -16,13 +16,24 @@
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- from typing import TYPE_CHECKING
19
+ from typing import Callable, cast, Hashable, TYPE_CHECKING
20
20
 
21
21
  from cirq import circuits, ops, protocols
22
- from cirq.transformers import merge_k_qubit_gates, transformer_api, transformer_primitives
22
+ from cirq.study.resolver import ParamResolver
23
+ from cirq.study.sweeps import dict_to_zip_sweep, ListSweep, ProductOrZipSweepLike, Sweep, Zip
24
+ from cirq.transformers import (
25
+ align,
26
+ merge_k_qubit_gates,
27
+ symbolize,
28
+ tag_transformers,
29
+ transformer_api,
30
+ transformer_primitives,
31
+ )
23
32
  from cirq.transformers.analytical_decompositions import single_qubit_decompositions
24
33
 
25
34
  if TYPE_CHECKING:
35
+ import sympy
36
+
26
37
  import cirq
27
38
 
28
39
 
@@ -67,6 +78,7 @@ def merge_single_qubit_gates_to_phxz(
67
78
  circuit: cirq.AbstractCircuit,
68
79
  *,
69
80
  context: cirq.TransformerContext | None = None,
81
+ merge_tags_fn: Callable[[cirq.CircuitOperation], list[Hashable]] | None = None,
70
82
  atol: float = 1e-8,
71
83
  ) -> cirq.Circuit:
72
84
  """Replaces runs of single qubit rotations with a single optional `cirq.PhasedXZGate`.
@@ -77,6 +89,7 @@ def merge_single_qubit_gates_to_phxz(
77
89
  Args:
78
90
  circuit: Input circuit to transform. It will not be modified.
79
91
  context: `cirq.TransformerContext` storing common configurable options for transformers.
92
+ merge_tags_fn: A callable returns the tags to be added to the merged operation.
80
93
  atol: Absolute tolerance to angle error. Larger values allow more negligible gates to be
81
94
  dropped, smaller values increase accuracy.
82
95
 
@@ -84,12 +97,13 @@ def merge_single_qubit_gates_to_phxz(
84
97
  Copy of the transformed input circuit.
85
98
  """
86
99
 
87
- def rewriter(op: cirq.CircuitOperation) -> cirq.OP_TREE:
88
- u = protocols.unitary(op)
89
- if protocols.num_qubits(op) == 0:
100
+ def rewriter(circuit_op: cirq.CircuitOperation) -> cirq.OP_TREE:
101
+ u = protocols.unitary(circuit_op)
102
+ if protocols.num_qubits(circuit_op) == 0:
90
103
  return ops.GlobalPhaseGate(u[0, 0]).on()
91
- gate = single_qubit_decompositions.single_qubit_matrix_to_phxz(u, atol)
92
- return gate(op.qubits[0]) if gate else []
104
+ gate = single_qubit_decompositions.single_qubit_matrix_to_phxz(u, atol) or ops.I
105
+ phxz_op = gate.on(circuit_op.qubits[0])
106
+ return phxz_op.with_tags(*merge_tags_fn(circuit_op)) if merge_tags_fn else phxz_op
93
107
 
94
108
  return merge_k_qubit_gates.merge_k_qubit_unitaries(
95
109
  circuit, k=1, context=context, rewriter=rewriter
@@ -158,3 +172,160 @@ def merge_single_qubit_moments_to_phxz(
158
172
  deep=context.deep if context else False,
159
173
  tags_to_ignore=tuple(tags_to_ignore),
160
174
  ).unfreeze(copy=False)
175
+
176
+
177
+ def _sweep_on_symbols(sweep: Sweep, symbols: set[sympy.Symbol]) -> Sweep:
178
+ new_resolvers: list[cirq.ParamResolver] = []
179
+ for resolver in sweep:
180
+ param_dict: cirq.ParamMappingType = {s: resolver.value_of(s) for s in symbols}
181
+ new_resolvers.append(ParamResolver(param_dict))
182
+ return ListSweep(new_resolvers)
183
+
184
+
185
+ def _calc_phxz_sweeps(
186
+ symbolized_circuit: cirq.Circuit, resolved_circuits: list[cirq.Circuit]
187
+ ) -> Sweep:
188
+ """Return the phxz sweep of the symbolized_circuit on resolved_circuits.
189
+
190
+ Raises:
191
+ ValueError: Structural mismatch: A `resolved_circuit` contains an unexpected gate type.
192
+ Expected a `PhasedXZGate` or `IdentityGate` at a position corresponding to a
193
+ symbolic `PhasedXZGate` in the `symbolized_circuit`.
194
+ """
195
+
196
+ def _extract_axz(op: ops.Operation | None) -> tuple[float, float, float]:
197
+ if not op or not op.gate or not isinstance(op.gate, ops.IdentityGate | ops.PhasedXZGate):
198
+ raise ValueError(f"Expect a PhasedXZGate or IdentityGate on op {op}.")
199
+ if isinstance(op.gate, ops.IdentityGate):
200
+ return 0.0, 0.0, 0.0 # Identity gate's a, x, z in PhasedXZ
201
+ return op.gate.axis_phase_exponent, op.gate.x_exponent, op.gate.z_exponent
202
+
203
+ values_by_params: dict[sympy.Symbol, tuple[float, ...]] = {}
204
+ for mid, moment in enumerate(symbolized_circuit):
205
+ for op in moment.operations:
206
+ if op.gate and isinstance(op.gate, ops.PhasedXZGate) and protocols.is_parameterized(op):
207
+ sa, sx, sz = op.gate.axis_phase_exponent, op.gate.x_exponent, op.gate.z_exponent
208
+ values_by_params[sa], values_by_params[sx], values_by_params[sz] = zip(
209
+ *[_extract_axz(c[mid].operation_at(op.qubits[0])) for c in resolved_circuits]
210
+ )
211
+
212
+ return dict_to_zip_sweep(cast(ProductOrZipSweepLike, values_by_params))
213
+
214
+
215
+ def merge_single_qubit_gates_to_phxz_symbolized(
216
+ circuit: cirq.AbstractCircuit,
217
+ *,
218
+ context: cirq.TransformerContext | None = None,
219
+ sweep: Sweep,
220
+ atol: float = 1e-8,
221
+ ) -> tuple[cirq.Circuit, Sweep]:
222
+ """Merges consecutive single qubit gates as PhasedXZ Gates. Symbolizes if any of
223
+ the consecutive gates is symbolized.
224
+
225
+ Example:
226
+ >>> q0, q1 = cirq.LineQubit.range(2)
227
+ >>> c = cirq.Circuit(\
228
+ cirq.X(q0),\
229
+ cirq.CZ(q0,q1)**sympy.Symbol("cz_exp"),\
230
+ cirq.Y(q0)**sympy.Symbol("y_exp"),\
231
+ cirq.X(q0))
232
+ >>> print(c)
233
+ 0: ───X───@──────────Y^y_exp───X───
234
+
235
+ 1: ───────@^cz_exp─────────────────
236
+ >>> new_circuit, new_sweep = cirq.merge_single_qubit_gates_to_phxz_symbolized(\
237
+ c, sweep=cirq.Zip(cirq.Points(key="cz_exp", points=[0, 1]),\
238
+ cirq.Points(key="y_exp", points=[0, 1])))
239
+ >>> print(new_circuit)
240
+ 0: ───PhXZ(a=-1,x=1,z=0)───@──────────PhXZ(a=a0,x=x0,z=z0)───
241
+
242
+ 1: ────────────────────────@^cz_exp──────────────────────────
243
+ >>> assert new_sweep[0] == cirq.ParamResolver({'a0': -1, 'x0': 1, 'z0': 0, 'cz_exp': 0})
244
+ >>> assert new_sweep[1] == cirq.ParamResolver({'a0': -0.5, 'x0': 0, 'z0': -1, 'cz_exp': 1})
245
+
246
+ Args:
247
+ circuit: Input circuit to transform. It will not be modified.
248
+ context: `cirq.TransformerContext` storing common configurable options for transformers.
249
+ sweep: Sweep of the symbols in the input circuit, updated Sweep will be returned
250
+ based on the transformation.
251
+ atol: Absolute tolerance to angle error. Larger values allow more negligible gates to be
252
+ dropped, smaller values increase accuracy.
253
+
254
+ Returns:
255
+ Copy of the transformed input circuit.
256
+ """
257
+ deep = context.deep if context else False
258
+
259
+ # Tag symbolized single-qubit op.
260
+ symbolized_single_tag = "_tmp_symbolize_tag"
261
+
262
+ circuit_tagged = transformer_primitives.map_operations(
263
+ circuit,
264
+ lambda op, _: (
265
+ op.with_tags(symbolized_single_tag)
266
+ if protocols.is_parameterized(op) and len(op.qubits) == 1
267
+ else op
268
+ ),
269
+ deep=deep,
270
+ )
271
+
272
+ # Step 0, isolate single qubit symbols and resolve the circuit on them.
273
+ single_qubit_gate_symbols: set[sympy.Symbol] = set().union(
274
+ *[
275
+ protocols.parameter_symbols(op) if symbolized_single_tag in op.tags else set()
276
+ for op in circuit_tagged.all_operations()
277
+ ]
278
+ )
279
+ # Remaining symbols, e.g., 2 qubit gates' symbols. Sweep of those symbols keeps unchanged.
280
+ remaining_symbols: set[sympy.Symbol] = set(
281
+ protocols.parameter_symbols(circuit) - single_qubit_gate_symbols
282
+ )
283
+ # If all single qubit gates are not parameterized, call the nonparamerized version of
284
+ # the transformer.
285
+ if not single_qubit_gate_symbols:
286
+ return (merge_single_qubit_gates_to_phxz(circuit, context=context, atol=atol), sweep)
287
+ sweep_of_single: Sweep = _sweep_on_symbols(sweep, single_qubit_gate_symbols)
288
+ # Get all resolved circuits from all sets of resolvers in sweep_of_single.
289
+ resolved_circuits = [
290
+ protocols.resolve_parameters(circuit_tagged, resolver) for resolver in sweep_of_single
291
+ ]
292
+
293
+ # Step 1, merge single qubit gates per resolved circuit, preserving
294
+ # the symbolized_single_tag to indicate the operator is a merged one.
295
+ merged_circuits: list[cirq.Circuit] = [
296
+ merge_single_qubit_gates_to_phxz(
297
+ c,
298
+ context=context,
299
+ merge_tags_fn=lambda circuit_op: (
300
+ [symbolized_single_tag]
301
+ if any(
302
+ symbolized_single_tag in set(op.tags)
303
+ for op in circuit_op.circuit.all_operations()
304
+ )
305
+ else []
306
+ ),
307
+ atol=atol,
308
+ )
309
+ for c in resolved_circuits
310
+ ]
311
+
312
+ # Step 2, get the new symbolized circuit by symbolizing on indexed symbolized_single_tag.
313
+ new_circuit = tag_transformers.remove_tags( # remove the temp tags used to track merges
314
+ symbolize.symbolize_single_qubit_gates_by_indexed_tags(
315
+ tag_transformers.index_tags( # index all 1-qubit-ops merged from ops with symbols
316
+ merged_circuits[0],
317
+ context=transformer_api.TransformerContext(deep=deep),
318
+ target_tags={symbolized_single_tag},
319
+ ),
320
+ symbolize_tag=symbolize.SymbolizeTag(prefix=symbolized_single_tag),
321
+ ),
322
+ remove_if=lambda tag: str(tag).startswith(symbolized_single_tag),
323
+ )
324
+
325
+ # Step 3, get N sets of parameterizations as new_sweep.
326
+ new_sweep = Zip(
327
+ _calc_phxz_sweeps(new_circuit, merged_circuits), # phxz sweeps
328
+ _sweep_on_symbols(sweep, remaining_symbols), # remaining sweeps
329
+ )
330
+
331
+ return align.align_right(new_circuit), new_sweep
@@ -12,7 +12,11 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- from __future__ import annotations
15
+ from unittest import TestCase
16
+ from unittest.mock import Mock, patch
17
+
18
+ import pytest
19
+ import sympy
16
20
 
17
21
  import cirq
18
22
 
@@ -81,7 +85,7 @@ def test_merge_single_qubit_gates_to_phased_x_and_z_deep():
81
85
  cirq.testing.assert_same_circuits(c_new, c_expected)
82
86
 
83
87
 
84
- def _phxz(a: float, x: float, z: float):
88
+ def _phxz(a: float | sympy.Symbol, x: float | sympy.Symbol, z: float | sympy.Symbol):
85
89
  return cirq.PhasedXZGate(axis_phase_exponent=a, x_exponent=x, z_exponent=z)
86
90
 
87
91
 
@@ -233,6 +237,142 @@ def test_merge_single_qubit_gates_to_phased_x_and_z_global_phase():
233
237
  assert c == c2
234
238
 
235
239
 
240
+ class TestMergeSingleQubitGatesSymbolized(TestCase):
241
+ """Test suite for merge_single_qubit_gates_to_phxz_symbolized."""
242
+
243
+ def test_case1(self):
244
+ """Test case diagram.
245
+ Input circuit:
246
+ 0: ───X─────────@────────H[ignore]─H──X──PhXZ(a=a0,x=x0,z=z0)──X──PhXZ(a=a1,x=x1,z=z1)───
247
+
248
+ 1: ───H^h_exp───@^cz_exp─────────────────────────────────────────────────────────────────
249
+ Expected output:
250
+ 0: ───PhXZ(a=-1,x=1,z=0)─────@──────────H[ignore]───PhXZ(a=a1,x=x1,z=z1)───
251
+
252
+ 1: ───PhXZ(a=a0,x=x0,z=z0)───@^cz_exp──────────────────────────────────────
253
+ """
254
+ a, b = cirq.LineQubit.range(2)
255
+ sa0, sa1 = [sympy.Symbol(a) for a in ["a0", "a1"]]
256
+ sx0, sx1 = [sympy.Symbol(x) for x in ["x0", "x1"]]
257
+ sz0, sz1 = [sympy.Symbol(z) for z in ["z0", "z1"]]
258
+ input_circuit = cirq.Circuit(
259
+ cirq.Moment(cirq.X(a), cirq.H(b) ** sympy.Symbol("h_exp")),
260
+ cirq.Moment(cirq.CZ(a, b) ** sympy.Symbol("cz_exp")),
261
+ cirq.Moment(cirq.H(a).with_tags("ignore")),
262
+ cirq.Moment(cirq.H(a)),
263
+ cirq.Moment(cirq.X(a)),
264
+ cirq.Moment(_phxz(sa0, sx0, sz0).on(a)),
265
+ cirq.Moment(cirq.X(a)),
266
+ cirq.Moment(_phxz(sa1, sx1, sz1).on(a)),
267
+ )
268
+ context = cirq.TransformerContext(tags_to_ignore=["ignore"])
269
+ sweep = cirq.Zip(
270
+ cirq.Points(key="h_exp", points=[0, 1]),
271
+ cirq.Points(key="cz_exp", points=[0, 1]),
272
+ cirq.Points(key="a0", points=[0, 1]),
273
+ cirq.Points(key="x0", points=[0, 1]),
274
+ cirq.Points(key="z0", points=[0, 1]),
275
+ cirq.Points(key="a1", points=[0, 1]),
276
+ cirq.Points(key="x1", points=[0, 1]),
277
+ cirq.Points(key="z1", points=[0, 1]),
278
+ )
279
+ output_circuit, new_sweep = cirq.merge_single_qubit_gates_to_phxz_symbolized(
280
+ input_circuit, context=context, sweep=sweep
281
+ )
282
+ expected = cirq.Circuit(
283
+ cirq.Moment(_phxz(-1, 1, 0).on(a), _phxz(sa0, sx0, sz0).on(b)),
284
+ cirq.Moment(cirq.CZ(a, b) ** sympy.Symbol("cz_exp")),
285
+ cirq.Moment(cirq.H(a).with_tags("ignore")),
286
+ cirq.Moment(_phxz(sa1, sx1, sz1).on(a)),
287
+ )
288
+ assert_optimizes(output_circuit, expected)
289
+
290
+ # Check the unitaries are preserved for each set of sweep paramerization.
291
+ for old_resolver, new_resolver in zip(sweep, new_sweep):
292
+ cirq.testing.assert_circuits_have_same_unitary_given_final_permutation(
293
+ cirq.resolve_parameters(input_circuit, old_resolver),
294
+ cirq.resolve_parameters(output_circuit, new_resolver),
295
+ {q: q for q in input_circuit.all_qubits()},
296
+ )
297
+
298
+ def test_with_gauge_compiling_as_sweep_success(self):
299
+ qubits = cirq.LineQubit.range(7)
300
+ c = cirq.Circuit(
301
+ cirq.Moment(cirq.H(qubits[0]), cirq.H(qubits[3])),
302
+ cirq.Moment(cirq.CZ(qubits[0], qubits[2]), cirq.CZ(qubits[3], qubits[5])),
303
+ cirq.Moment(cirq.CZ(qubits[0], qubits[1]), cirq.CZ(qubits[3], qubits[4])),
304
+ cirq.Moment(cirq.CZ(qubits[1], qubits[3]), cirq.CZ(qubits[4], qubits[6])),
305
+ cirq.Moment(cirq.M(*qubits, key='m')),
306
+ )
307
+ old_circuit, old_sweep = cirq.transformers.gauge_compiling.CZGaugeTransformer.as_sweep(
308
+ c, N=50
309
+ )
310
+ new_circuit, new_sweep = cirq.merge_single_qubit_gates_to_phxz_symbolized(
311
+ old_circuit, sweep=old_sweep
312
+ )
313
+ # Check the unitaries are preserved for each set of sweep paramerization.
314
+ for old_resolver, new_resolver in zip(old_sweep, new_sweep):
315
+ cirq.testing.assert_circuits_have_same_unitary_given_final_permutation(
316
+ cirq.resolve_parameters(old_circuit[0:-1], old_resolver),
317
+ cirq.resolve_parameters(new_circuit[0:-1], new_resolver),
318
+ {q: q for q in qubits},
319
+ )
320
+
321
+ def test_case_non_parameterized_singles(self):
322
+ """Test merge_single_qubit_gates_to_phxz_symbolized when all single qubit gates are not
323
+ parameterized."""
324
+
325
+ a, b = cirq.LineQubit.range(2)
326
+ input_circuit = cirq.Circuit(cirq.H(a), cirq.H(a), cirq.CZ(a, b) ** sympy.Symbol("exp"))
327
+ expected_circuit = cirq.merge_single_qubit_gates_to_phxz(input_circuit)
328
+ output_circuit, _ = cirq.merge_single_qubit_gates_to_phxz_symbolized(
329
+ input_circuit, sweep=cirq.Points(key="exp", points=[0.1, 0.2, 0.5])
330
+ )
331
+ assert_optimizes(output_circuit, expected_circuit)
332
+
333
+ def test_fail_different_structures_error(self):
334
+ """Tests that the function raises a ValueError if merged structures of the circuit differ
335
+ for different parameterizations."""
336
+ q0, q1 = cirq.LineQubit.range(2)
337
+ circuit = cirq.Circuit(cirq.H(q0) ** sympy.Symbol("exp"))
338
+ sweep = cirq.Points(key="exp", points=[0.1, 0.2])
339
+
340
+ with patch(
341
+ "cirq.protocols.resolve_parameters",
342
+ side_effect=[ # Mock the return values of resolve_parameters
343
+ cirq.Circuit(cirq.I(q0).with_tags("_tmp_symbolize_tag")),
344
+ cirq.Circuit(cirq.CZ(q0, q1)),
345
+ ],
346
+ ):
347
+ with pytest.raises(ValueError, match="Expect a PhasedXZGate or IdentityGate.*"):
348
+ cirq.merge_single_qubit_gates_to_phxz_symbolized(circuit, sweep=sweep)
349
+
350
+ def test_fail_unexpected_gate_error(self):
351
+ """Tests that the function raises a RuntimeError of unexpected gate."""
352
+ a, b = cirq.LineQubit.range(2)
353
+ circuit = cirq.Circuit(
354
+ cirq.H(a) ** sympy.Symbol("exp1"),
355
+ cirq.X(a),
356
+ cirq.CZ(a, b),
357
+ cirq.Y(a),
358
+ cirq.H(a) ** sympy.Symbol("exp2"),
359
+ )
360
+ sweep = cirq.Points(key="exp1", points=[0.1, 0.2]) * cirq.Points(
361
+ key="exp2", points=[0.1, 0.2]
362
+ )
363
+
364
+ mock_iter = Mock()
365
+ mock_iter.__next__ = Mock(return_value=2)
366
+
367
+ with patch(
368
+ "cirq.transformers.analytical_decompositions"
369
+ ".single_qubit_decompositions.single_qubit_matrix_to_phxz",
370
+ return_value=cirq.H,
371
+ ):
372
+ with pytest.raises(ValueError, match="Expect a PhasedXZGate or IdentityGate.*"):
373
+ cirq.merge_single_qubit_gates_to_phxz_symbolized(circuit, sweep=sweep)
374
+
375
+
236
376
  def test_merge_single_qubit_moments_to_phxz_with_global_phase_in_first_moment():
237
377
  q0 = cirq.LineQubit(0)
238
378
  c_orig = cirq.Circuit(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cirq-core
3
- Version: 1.6.0.dev20250623211210
3
+ Version: 1.6.0.dev20250624211923
4
4
  Summary: A framework for creating, editing, and invoking Noisy Intermediate Scale Quantum (NISQ) circuits.
5
5
  Home-page: http://github.com/quantumlib/cirq
6
6
  Author: The Cirq Developers
@@ -1,11 +1,11 @@
1
- cirq/__init__.py,sha256=wyFgv68HcbpDyWoZb4fuX2rVvK1N_ZolWosyoktPyXs,28219
1
+ cirq/__init__.py,sha256=n670ZaGq1Yuj88GYz87JeuXoFH91-FZLH7RbJ2-vAho,28315
2
2
  cirq/_compat.py,sha256=BCAAJx19-5UXHv3HpCzewinx-b9eDs_C1GHPXPfKLIY,29478
3
3
  cirq/_compat_test.py,sha256=emXpdD5ZvwLRlFAoQB8YatmZyU3b4e9jg6FppMTUhkU,33900
4
4
  cirq/_doc.py,sha256=BrnoABo1hk5RgB3Cgww4zLHUfiyFny0F1V-tOMCbdaU,2909
5
5
  cirq/_import.py,sha256=ixBu4EyGl46Ram2cP3p5eZVEFDW5L2DS-VyTjz4N9iw,8429
6
6
  cirq/_import_test.py,sha256=oF4izzOVZLc7NZ0aZHFcGv-r01eiFFt_JORx_x7_D4s,1089
7
- cirq/_version.py,sha256=G90gnyEVX9JtDLcpB4PPEGwM3AoV34GHtuCPBs-1o_M,1206
8
- cirq/_version_test.py,sha256=meBCITA69x1ZUiBJi5gdRAf3wVjZQh2Fc51TH0ldfgo,155
7
+ cirq/_version.py,sha256=beAG5kxHUOa0_7Sv3GQyzdxwJVncHrXB1gsYHaYzHr8,1206
8
+ cirq/_version_test.py,sha256=8GL4xyx-wxFn7dLr_3cgE_87WnnBlE4CHyc16tLcZ-I,155
9
9
  cirq/conftest.py,sha256=X7yLFL8GLhg2CjPw0hp5e_dGASfvHx1-QT03aUbhKJw,1168
10
10
  cirq/json_resolver_cache.py,sha256=hYyG53VJeV61X0oukK5ndZYega8lkL2FyaL1m0j6h5M,13556
11
11
  cirq/py.typed,sha256=VFSlmh_lNwnaXzwY-ZuW-C2Ws5PkuDoVgBdNCs0jXJE,63
@@ -96,8 +96,8 @@ cirq/contrib/paulistring/optimize.py,sha256=F02c_9nuc8a41XNsA9bzTGaW2kR3hZw-MdaQ
96
96
  cirq/contrib/paulistring/optimize_test.py,sha256=FsmwyYFIGyyiO115oYgmCfaSV3De55Azd0_rzsi_xnU,3618
97
97
  cirq/contrib/paulistring/pauli_string_dag.py,sha256=28bUVNsIS9WYKdyYCNIVrkRwqQOKlkpmCacWow6N6D0,1142
98
98
  cirq/contrib/paulistring/pauli_string_dag_test.py,sha256=nH_1h5LQobV9rb5gitLmrvpIwWwrcRmNdUGDAhFMZtI,1168
99
- cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation.py,sha256=Tz69rtet_u69CpOxERkGjaC0Ty13OTd6THP8tl097No,20057
100
- cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation_test.py,sha256=fo-bNK2T-f8eTfIaPh_2sszYg-v-Epn0EBOEoC8QcCk,35495
99
+ cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation.py,sha256=tjN3R_aq5xwixlO8pCHaiWX4LsXXJHQxAEZgUHSgQac,21021
100
+ cirq/contrib/paulistring/pauli_string_measurement_with_readout_mitigation_test.py,sha256=PLFPOfhoIepk-UhgEBcfvFnFtd0qpqLSPtvrcCaKpd0,36842
101
101
  cirq/contrib/paulistring/pauli_string_optimize.py,sha256=ejHf7Bo0iUvnNBeZ5IN0bT0SIXF79DSGr1NxoAyVfiQ,2960
102
102
  cirq/contrib/paulistring/pauli_string_optimize_test.py,sha256=_14FS9TAvzRsmnTZxJUsMXPNcenv5mb0eD2gGTxvohE,2955
103
103
  cirq/contrib/paulistring/recombine.py,sha256=phJ-SY4zdqZpIZca0iSsY0lK6NdXd0M0sOOWnUdGn5U,4353
@@ -193,7 +193,7 @@ cirq/experiments/random_quantum_circuit_generation.py,sha256=pV6ubukLLdfPXLvJD2t
193
193
  cirq/experiments/random_quantum_circuit_generation_test.py,sha256=4GSfUK2hw2r90JAO7R2zSBqjtwWi8vXuAhw-iK6quwY,16512
194
194
  cirq/experiments/readout_confusion_matrix.py,sha256=ro3miCMr8K-EDW918PHKL_QcqrDUT-_uTXUvJ1PPzEI,20666
195
195
  cirq/experiments/readout_confusion_matrix_test.py,sha256=DNapm_kLa0Mn9vW_KejWKrmM4yqJ8Q-7gqxNjFkOFQU,10668
196
- cirq/experiments/single_qubit_readout_calibration.py,sha256=-sC8QhSxRWQnPcdwserzDa7E0VyrUzwGdmGaPcNumQw,14706
196
+ cirq/experiments/single_qubit_readout_calibration.py,sha256=0vnmKBtcjS8kCbJj_iJ0BWW23W6UCc4X2Ig9N_UUs0s,15243
197
197
  cirq/experiments/single_qubit_readout_calibration_test.py,sha256=HQE29SxyL2mUhvNZnV9F_vOaAIJteox7volmDfZQy8g,7751
198
198
  cirq/experiments/t1_decay_experiment.py,sha256=ojtmkQ5mgiQ-cK389YrvlRXotzHon83HNG_8sae-JdE,7099
199
199
  cirq/experiments/t1_decay_experiment_test.py,sha256=vjm-zV0a2yrFrFhzq03GOMBy5U_XnvX1Ga3NN4FiT3k,9262
@@ -1050,7 +1050,7 @@ cirq/testing/test_data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
1050
1050
  cirq/testing/test_data/test_module_missing_json_test_data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1051
1051
  cirq/testing/test_data/test_module_missing_testspec/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1052
1052
  cirq/testing/test_data/test_module_missing_testspec/json_test_data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1053
- cirq/transformers/__init__.py,sha256=uBx8lSOJSlY2m5QeOTr0E6HaMaqTRwTUu1binpXyaow,7189
1053
+ cirq/transformers/__init__.py,sha256=B4ijV7PR4bN_QBswNqpAehIWTjXuaRMeyccrWz4Gw4w,7285
1054
1054
  cirq/transformers/align.py,sha256=2uLjN5B4wNC661IGbwG5NKb-GSAW93pFCtPxuJkc6fs,3339
1055
1055
  cirq/transformers/align_test.py,sha256=X4ysJuemqqOeKix2rO9SlwF4CBQzEYbgiqgZmlMxbsQ,7722
1056
1056
  cirq/transformers/drop_empty_moments.py,sha256=uZJG9FpUNyA1Mi0xLDuVuhj_siZhPZ1_s08Ry9xQ-1M,1535
@@ -1071,8 +1071,8 @@ cirq/transformers/measurement_transformers.py,sha256=crAYKGK90drr_3GEh5t8-Z87eSE
1071
1071
  cirq/transformers/measurement_transformers_test.py,sha256=mJKYFqqMwZnD8KXVM1tmF2kcIZXkkZKlfOU9XbGxYpQ,29019
1072
1072
  cirq/transformers/merge_k_qubit_gates.py,sha256=v9vY3f52S9QR7D_Om4APSZ65rwJH6Z7g6Gf3pD_9H3I,4380
1073
1073
  cirq/transformers/merge_k_qubit_gates_test.py,sha256=523FmfCC7mGX_zecGaQ2tgdqBvMGiMinwJMIfKBgG8E,14016
1074
- cirq/transformers/merge_single_qubit_gates.py,sha256=86qjW19JTC5t38TdnTKtS-M8AG2tYUxSZ9w_tM59BsU,5968
1075
- cirq/transformers/merge_single_qubit_gates_test.py,sha256=hj0iTOZVgsMxY8dQJl8EYvuCDICqzzs9Yw62p5Emsrk,11213
1074
+ cirq/transformers/merge_single_qubit_gates.py,sha256=jQsO2mTagm4nmLoD0rk-FP4un80D3RJtxBBgAVTyQYg,13664
1075
+ cirq/transformers/merge_single_qubit_gates_test.py,sha256=48U_X4fWZ3BZiFI14kZL6T9TPaHSwfVIPrQWnnTABl0,18076
1076
1076
  cirq/transformers/noise_adding.py,sha256=ZBS-015Kc-BnoO-lMXfEGpCXbPu4whVgTXIB-E16ApQ,4476
1077
1077
  cirq/transformers/noise_adding_test.py,sha256=nHOoKUPBBOkWF6A_JhIO6-G0AuFkLKBCxH7e2BOem9M,2171
1078
1078
  cirq/transformers/optimize_for_target_gateset.py,sha256=QThizLtkzvZpUK-LG77ixuZXghDyWGNn6yHkNrzYR3o,7206
@@ -1220,8 +1220,8 @@ cirq/work/sampler.py,sha256=rxbMWvrhu3gfNSBjZKozw28lLKVvBAS_1EGyPdYe8Xg,19041
1220
1220
  cirq/work/sampler_test.py,sha256=SsMrRvLDYELyOAWLKISjkdEfrBwLYWRsT6D8WrsLM3Q,13533
1221
1221
  cirq/work/zeros_sampler.py,sha256=Fs2JWwq0n9zv7_G5Rm-9vPeHUag7uctcMOHg0JTkZpc,2371
1222
1222
  cirq/work/zeros_sampler_test.py,sha256=lQLgQDGBLtfImryys2HzQ2jOSGxHgc7-koVBUhv8qYk,3345
1223
- cirq_core-1.6.0.dev20250623211210.dist-info/licenses/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
1224
- cirq_core-1.6.0.dev20250623211210.dist-info/METADATA,sha256=IruqpMesSLFKFDyo852-MHqtEd37-Jb2hQtTV1lQgv4,4857
1225
- cirq_core-1.6.0.dev20250623211210.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1226
- cirq_core-1.6.0.dev20250623211210.dist-info/top_level.txt,sha256=Sz9iOxHU0IEMLSFGwiwOCaN2e9K-jFbBbtpPN1hB73g,5
1227
- cirq_core-1.6.0.dev20250623211210.dist-info/RECORD,,
1223
+ cirq_core-1.6.0.dev20250624211923.dist-info/licenses/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
1224
+ cirq_core-1.6.0.dev20250624211923.dist-info/METADATA,sha256=NIRYeYXD3SaMZFqrF4GLXyXVGD4PwQmQKBAUa5yHVno,4857
1225
+ cirq_core-1.6.0.dev20250624211923.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1226
+ cirq_core-1.6.0.dev20250624211923.dist-info/top_level.txt,sha256=Sz9iOxHU0IEMLSFGwiwOCaN2e9K-jFbBbtpPN1hB73g,5
1227
+ cirq_core-1.6.0.dev20250624211923.dist-info/RECORD,,