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