cirq-core 1.2.0.dev20230717232332__py3-none-any.whl → 1.3.0__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.
Files changed (158) hide show
  1. cirq/__init__.py +5 -0
  2. cirq/_compat.py +26 -11
  3. cirq/_compat_test.py +37 -3
  4. cirq/_version.py +31 -1
  5. cirq/_version_test.py +1 -1
  6. cirq/circuits/circuit.py +106 -32
  7. cirq/circuits/circuit_operation.py +2 -2
  8. cirq/circuits/circuit_operation_test.py +1 -1
  9. cirq/circuits/circuit_test.py +109 -3
  10. cirq/circuits/frozen_circuit.py +80 -5
  11. cirq/circuits/frozen_circuit_test.py +47 -2
  12. cirq/circuits/qasm_output_test.py +9 -9
  13. cirq/conftest.py +1 -2
  14. cirq/contrib/acquaintance/devices.py +1 -1
  15. cirq/contrib/hacks/disable_validation_test.py +1 -1
  16. cirq/contrib/noise_models/noise_models.py +1 -2
  17. cirq/contrib/paulistring/clifford_optimize.py +1 -1
  18. cirq/contrib/paulistring/clifford_target_gateset_test.py +4 -4
  19. cirq/contrib/qcircuit/qcircuit_pdf.py +1 -1
  20. cirq/contrib/quimb/density_matrix.py +2 -3
  21. cirq/contrib/quimb/grid_circuits.py +3 -3
  22. cirq/contrib/quimb/state_vector.py +3 -5
  23. cirq/contrib/routing/utils.py +1 -2
  24. cirq/contrib/svg/svg.py +4 -6
  25. cirq/devices/grid_qubit.py +49 -38
  26. cirq/devices/grid_qubit_test.py +1 -3
  27. cirq/devices/insertion_noise_model.py +21 -1
  28. cirq/devices/insertion_noise_model_test.py +6 -0
  29. cirq/devices/line_qubit.py +67 -40
  30. cirq/devices/named_topologies.py +8 -14
  31. cirq/devices/noise_properties.py +1 -1
  32. cirq/devices/noise_utils.py +7 -5
  33. cirq/devices/noise_utils_test.py +7 -0
  34. cirq/experiments/fidelity_estimation_test.py +1 -1
  35. cirq/experiments/qubit_characterizations.py +6 -5
  36. cirq/experiments/random_quantum_circuit_generation.py +1 -1
  37. cirq/experiments/random_quantum_circuit_generation_test.py +28 -1
  38. cirq/experiments/readout_confusion_matrix.py +6 -6
  39. cirq/experiments/xeb_fitting.py +3 -5
  40. cirq/experiments/xeb_fitting_test.py +2 -2
  41. cirq/experiments/xeb_sampling.py +1 -1
  42. cirq/interop/quirk/url_to_circuit.py +40 -38
  43. cirq/json_resolver_cache.py +2 -0
  44. cirq/linalg/decompositions.py +6 -5
  45. cirq/ops/__init__.py +2 -0
  46. cirq/ops/classically_controlled_operation.py +1 -1
  47. cirq/ops/clifford_gate.py +9 -9
  48. cirq/ops/clifford_gate_test.py +3 -4
  49. cirq/ops/common_channels.py +2 -5
  50. cirq/ops/common_channels_test.py +3 -5
  51. cirq/ops/common_gates_test.py +7 -7
  52. cirq/ops/controlled_operation_test.py +2 -2
  53. cirq/ops/dense_pauli_string.py +3 -0
  54. cirq/ops/eigen_gate_test.py +1 -3
  55. cirq/ops/fourier_transform.py +1 -2
  56. cirq/ops/fsim_gate.py +1 -1
  57. cirq/ops/gate_features_test.py +2 -2
  58. cirq/ops/gate_operation_test.py +1 -2
  59. cirq/ops/greedy_qubit_manager.py +86 -0
  60. cirq/ops/greedy_qubit_manager_test.py +98 -0
  61. cirq/ops/linear_combinations.py +1 -1
  62. cirq/ops/named_qubit.py +55 -18
  63. cirq/ops/parity_gates.py +65 -18
  64. cirq/ops/parity_gates_test.py +41 -2
  65. cirq/ops/pauli_gates.py +2 -2
  66. cirq/ops/pauli_string.py +3 -4
  67. cirq/ops/pauli_string_raw_types_test.py +3 -3
  68. cirq/ops/pauli_string_test.py +3 -4
  69. cirq/ops/random_gate_channel_test.py +3 -3
  70. cirq/ops/raw_types.py +1 -1
  71. cirq/ops/raw_types_test.py +5 -5
  72. cirq/ops/three_qubit_gates.py +12 -8
  73. cirq/protocols/act_on_protocol_test.py +9 -9
  74. cirq/protocols/apply_channel_protocol.py +9 -6
  75. cirq/protocols/apply_unitary_protocol_test.py +1 -1
  76. cirq/protocols/equal_up_to_global_phase_protocol_test.py +2 -2
  77. cirq/protocols/has_stabilizer_effect_protocol.py +52 -6
  78. cirq/protocols/has_stabilizer_effect_protocol_test.py +21 -8
  79. cirq/protocols/has_unitary_protocol_test.py +1 -3
  80. cirq/protocols/json_serialization.py +6 -6
  81. cirq/protocols/json_serialization_test.py +7 -14
  82. cirq/protocols/json_test_data/InsertionNoiseModel.json +91 -0
  83. cirq/protocols/json_test_data/InsertionNoiseModel.repr +4 -0
  84. cirq/protocols/json_test_data/OpIdentifier.json +45 -10
  85. cirq/protocols/json_test_data/OpIdentifier.repr +7 -1
  86. cirq/protocols/json_test_data/spec.py +4 -0
  87. cirq/protocols/measurement_key_protocol_test.py +1 -1
  88. cirq/protocols/unitary_protocol_test.py +13 -16
  89. cirq/qis/clifford_tableau.py +7 -8
  90. cirq/qis/measures.py +1 -1
  91. cirq/qis/states.py +2 -3
  92. cirq/sim/__init__.py +2 -0
  93. cirq/sim/classical_simulator.py +107 -0
  94. cirq/sim/classical_simulator_test.py +207 -0
  95. cirq/sim/clifford/clifford_simulator_test.py +7 -7
  96. cirq/sim/clifford/stabilizer_simulation_state.py +2 -2
  97. cirq/sim/clifford/stabilizer_state_ch_form.py +7 -7
  98. cirq/sim/density_matrix_simulation_state.py +19 -4
  99. cirq/sim/density_matrix_simulator_test.py +5 -13
  100. cirq/sim/simulation_state_test.py +13 -14
  101. cirq/sim/simulator_test.py +6 -9
  102. cirq/sim/state_vector_simulation_state.py +1 -1
  103. cirq/study/resolver.py +41 -41
  104. cirq/study/resolver_test.py +13 -12
  105. cirq/testing/__init__.py +4 -1
  106. cirq/testing/circuit_compare.py +1 -1
  107. cirq/testing/circuit_compare_test.py +11 -11
  108. cirq/testing/consistent_controlled_gate_op.py +15 -1
  109. cirq/testing/consistent_controlled_gate_op_test.py +12 -3
  110. cirq/testing/consistent_decomposition.py +0 -1
  111. cirq/testing/consistent_protocols.py +6 -1
  112. cirq/testing/consistent_protocols_test.py +5 -10
  113. cirq/testing/consistent_qasm.py +2 -4
  114. cirq/testing/consistent_qasm_test.py +2 -3
  115. cirq/testing/consistent_specified_has_unitary_test.py +1 -3
  116. cirq/testing/equals_tester.py +1 -1
  117. cirq/testing/equals_tester_test.py +5 -5
  118. cirq/testing/equivalent_repr_eval_test.py +1 -3
  119. cirq/testing/gate_features_test.py +6 -6
  120. cirq/testing/order_tester_test.py +1 -3
  121. cirq/testing/random_circuit_test.py +1 -3
  122. cirq/transformers/__init__.py +3 -0
  123. cirq/transformers/analytical_decompositions/__init__.py +1 -0
  124. cirq/transformers/analytical_decompositions/three_qubit_decomposition.py +1 -2
  125. cirq/transformers/analytical_decompositions/three_qubit_decomposition_test.py +2 -5
  126. cirq/transformers/analytical_decompositions/two_qubit_state_preparation.py +38 -0
  127. cirq/transformers/analytical_decompositions/two_qubit_state_preparation_test.py +18 -0
  128. cirq/transformers/expand_composite_test.py +4 -4
  129. cirq/transformers/heuristic_decompositions/gate_tabulation_math_utils.py +1 -1
  130. cirq/transformers/heuristic_decompositions/two_qubit_gate_tabulation.py +1 -2
  131. cirq/transformers/merge_k_qubit_gates_test.py +2 -2
  132. cirq/transformers/qubit_management_transformers.py +177 -0
  133. cirq/transformers/qubit_management_transformers_test.py +250 -0
  134. cirq/transformers/routing/route_circuit_cqc.py +23 -4
  135. cirq/transformers/routing/route_circuit_cqc_test.py +42 -0
  136. cirq/transformers/stratify.py +10 -11
  137. cirq/transformers/target_gatesets/compilation_target_gateset_test.py +10 -10
  138. cirq/transformers/target_gatesets/cz_gateset_test.py +8 -10
  139. cirq/transformers/transformer_primitives.py +138 -28
  140. cirq/value/abc_alt_test.py +4 -4
  141. cirq/value/duration.py +68 -37
  142. cirq/value/duration_test.py +2 -0
  143. cirq/value/measurement_key_test.py +1 -1
  144. cirq/value/product_state.py +4 -8
  145. cirq/value/value_equality_attr.py +12 -5
  146. cirq/vis/heatmap.py +7 -4
  147. cirq/vis/heatmap_test.py +14 -4
  148. cirq/vis/histogram.py +4 -4
  149. cirq/vis/state_histogram.py +10 -6
  150. cirq/vis/state_histogram_test.py +2 -0
  151. cirq/work/observable_measurement_data_test.py +1 -1
  152. cirq/work/observable_measurement_test.py +2 -2
  153. cirq/work/zeros_sampler.py +1 -1
  154. {cirq_core-1.2.0.dev20230717232332.dist-info → cirq_core-1.3.0.dist-info}/METADATA +11 -19
  155. {cirq_core-1.2.0.dev20230717232332.dist-info → cirq_core-1.3.0.dist-info}/RECORD +158 -150
  156. {cirq_core-1.2.0.dev20230717232332.dist-info → cirq_core-1.3.0.dist-info}/WHEEL +1 -1
  157. {cirq_core-1.2.0.dev20230717232332.dist-info → cirq_core-1.3.0.dist-info}/LICENSE +0 -0
  158. {cirq_core-1.2.0.dev20230717232332.dist-info → cirq_core-1.3.0.dist-info}/top_level.txt +0 -0
@@ -54,8 +54,7 @@ class QuditGate(cirq.Gate):
54
54
  def test_assert_qasm_is_consistent_with_unitary():
55
55
  try:
56
56
  import qiskit as _
57
- except ImportError:
58
- # coverage: ignore
57
+ except ImportError: # pragma: no cover
59
58
  warnings.warn(
60
59
  "Skipped test_assert_qasm_is_consistent_with_unitary "
61
60
  "because qiskit isn't installed to verify against."
@@ -84,7 +83,7 @@ def test_assert_qasm_is_consistent_with_unitary():
84
83
  )
85
84
 
86
85
  # Checks that code is valid.
87
- with pytest.raises(AssertionError, match='Check your OPENQASM'):
86
+ with pytest.raises(AssertionError, match='QASM not consistent'):
88
87
  cirq.testing.assert_qasm_is_consistent_with_unitary(
89
88
  Fixed(np.array([[1, 0], [0, -1]]), 'JUNK$&*@($#::=[];')
90
89
  )
@@ -32,12 +32,10 @@ def test_assert_specifies_has_unitary_if_unitary_from_apply():
32
32
  class Bad(cirq.Operation):
33
33
  @property
34
34
  def qubits(self):
35
- # coverage: ignore
36
35
  return ()
37
36
 
38
37
  def with_qubits(self, *new_qubits):
39
- # coverage: ignore
40
- return self
38
+ return self # pragma: no cover
41
39
 
42
40
  def _apply_unitary_(self, args):
43
41
  return args.target_tensor
@@ -157,7 +157,7 @@ class _TestsForNotImplemented:
157
157
 
158
158
  def __eq__(self, other: object) -> bool:
159
159
  if other is not self.other:
160
- return NotImplemented # coverage: ignore
160
+ return NotImplemented # pragma: no cover
161
161
  return True
162
162
 
163
163
 
@@ -169,16 +169,16 @@ def test_fails_when_ne_is_inconsistent():
169
169
 
170
170
  def __eq__(self, other):
171
171
  if not isinstance(other, type(self)):
172
- return NotImplemented # coverage: ignore
172
+ return NotImplemented # pragma: no cover
173
173
  return self.x == other.x
174
174
 
175
175
  def __ne__(self, other):
176
176
  if not isinstance(other, type(self)):
177
- return NotImplemented # coverage: ignore
177
+ return NotImplemented # pragma: no cover
178
178
  return self.x == other.x
179
179
 
180
180
  def __hash__(self):
181
- return hash(self.x) # coverage: ignore
181
+ return hash(self.x) # pragma: no cover
182
182
 
183
183
  with pytest.raises(AssertionError, match='inconsistent'):
184
184
  eq.make_equality_group(InconsistentNeImplementation)
@@ -278,7 +278,7 @@ def test_returns_not_implemented_for_other_types():
278
278
  if isinstance(other, (FirstClass, SecondClass)):
279
279
  return self.val == other.val
280
280
  # Ignore coverage, this is just for illustrative purposes.
281
- return NotImplemented # coverage: ignore
281
+ return NotImplemented # pragma: no cover
282
282
 
283
283
  # But we see that this does not work because it fails commutativity of ==
284
284
  assert SecondClass("a") == FirstClass("a")
@@ -306,7 +306,7 @@ def test_returns_not_implemented_for_other_types():
306
306
  if isinstance(other, (ThirdClass, FourthClass)):
307
307
  return self.val == other.val
308
308
  # Ignore coverage, this is just for illustrative purposes.
309
- return NotImplemented # coverage: ignore
309
+ return NotImplemented # pragma: no cover
310
310
 
311
311
  # We see this is fixed:
312
312
  assert ThirdClass("a") == FourthClass("a")
@@ -30,9 +30,7 @@ def test_external():
30
30
 
31
31
 
32
32
  def test_custom_class_repr():
33
- class CustomRepr:
34
- # coverage: ignore
35
-
33
+ class CustomRepr: # pragma: no cover
36
34
  setup_code = """class CustomRepr:
37
35
  def __init__(self, eq_val):
38
36
  self.eq_val = eq_val
@@ -26,11 +26,11 @@ def test_two_qubit_gate_is_abstract_can_implement():
26
26
 
27
27
 
28
28
  def test_two_qubit_gate_validate_pass():
29
- class Dummy(cirq.testing.TwoQubitGate):
29
+ class Example(cirq.testing.TwoQubitGate):
30
30
  def matrix(self):
31
31
  pass
32
32
 
33
- g = Dummy()
33
+ g = Example()
34
34
  q1 = cirq.NamedQubit('q1')
35
35
  q2 = cirq.NamedQubit('q2')
36
36
  q3 = cirq.NamedQubit('q3')
@@ -42,11 +42,11 @@ def test_two_qubit_gate_validate_pass():
42
42
 
43
43
 
44
44
  def test_two_qubit_gate_validate_wrong_number():
45
- class Dummy(cirq.testing.TwoQubitGate):
45
+ class Example(cirq.testing.TwoQubitGate):
46
46
  def matrix(self):
47
47
  pass
48
48
 
49
- g = Dummy()
49
+ g = Example()
50
50
  q1 = cirq.NamedQubit('q1')
51
51
  q2 = cirq.NamedQubit('q2')
52
52
  q3 = cirq.NamedQubit('q3')
@@ -60,11 +60,11 @@ def test_two_qubit_gate_validate_wrong_number():
60
60
 
61
61
 
62
62
  def test_three_qubit_gate_validate():
63
- class Dummy(cirq.testing.ThreeQubitGate):
63
+ class Example(cirq.testing.ThreeQubitGate):
64
64
  def matrix(self):
65
65
  pass
66
66
 
67
- g = Dummy()
67
+ g = Example()
68
68
  a, b, c, d = cirq.LineQubit.range(4)
69
69
 
70
70
  assert g.num_qubits() == 3
@@ -89,9 +89,7 @@ def test_add_ordering_group_incorrect():
89
89
 
90
90
 
91
91
  def test_propagates_internal_errors():
92
- class UnorderableClass:
93
- # coverage: ignore
94
-
92
+ class UnorderableClass: # pragma: no cover
95
93
  def __eq__(self, other):
96
94
  return NotImplemented
97
95
 
@@ -60,8 +60,7 @@ def _cases_for_random_circuit():
60
60
  # number of qubits greater that the number of qubits for the
61
61
  # circuit. In this case, try again.
62
62
  if all(n > n_qubits for n in gate_domain.values()):
63
- # coverage: ignore
64
- continue
63
+ continue # pragma: no cover
65
64
  else:
66
65
  gate_domain = None
67
66
  pass_qubits = random.choice((True, False))
@@ -107,7 +106,6 @@ def test_random_circuit_reproducible_with_seed(seed):
107
106
 
108
107
 
109
108
  def test_random_circuit_not_expected_number_of_qubits():
110
-
111
109
  circuit = cirq.testing.random_circuit(
112
110
  qubits=3, n_moments=1, op_density=1.0, gate_domain={cirq.CNOT: 2}
113
111
  )
@@ -24,6 +24,7 @@ from cirq.transformers.analytical_decompositions import (
24
24
  is_negligible_turn,
25
25
  parameterized_2q_op_to_sqrt_iswap_operations,
26
26
  prepare_two_qubit_state_using_cz,
27
+ prepare_two_qubit_state_using_iswap,
27
28
  prepare_two_qubit_state_using_sqrt_iswap,
28
29
  quantum_shannon_decomposition,
29
30
  single_qubit_matrix_to_gates,
@@ -93,6 +94,8 @@ from cirq.transformers.merge_single_qubit_gates import (
93
94
  merge_single_qubit_moments_to_phxz,
94
95
  )
95
96
 
97
+ from cirq.transformers.qubit_management_transformers import map_clean_and_borrowable_qubits
98
+
96
99
  from cirq.transformers.synchronize_terminal_measurements import synchronize_terminal_measurements
97
100
 
98
101
  from cirq.transformers.transformer_api import (
@@ -66,6 +66,7 @@ from cirq.transformers.analytical_decompositions.two_qubit_to_sqrt_iswap import
66
66
  from cirq.transformers.analytical_decompositions.two_qubit_state_preparation import (
67
67
  prepare_two_qubit_state_using_cz,
68
68
  prepare_two_qubit_state_using_sqrt_iswap,
69
+ prepare_two_qubit_state_using_iswap,
69
70
  )
70
71
 
71
72
  from cirq.transformers.analytical_decompositions.single_to_two_qubit_isometry import (
@@ -56,8 +56,7 @@ def three_qubit_matrix_to_operations(
56
56
 
57
57
  try:
58
58
  from scipy.linalg import cossin
59
- except ImportError: # coverage: ignore
60
- # coverage: ignore
59
+ except ImportError: # pragma: no cover
61
60
  raise ImportError(
62
61
  "cirq.three_qubit_unitary_to_operations requires "
63
62
  "SciPy 1.5.0+, as it uses the cossin function. Please"
@@ -30,15 +30,13 @@ from cirq.transformers.analytical_decompositions.three_qubit_decomposition impor
30
30
 
31
31
 
32
32
  def _skip_if_scipy(*, version_is_greater_than_1_5_0: bool) -> Callable[[Callable], Callable]:
33
- def decorator(func):
33
+ def decorator(func): # pragma: no cover
34
34
  try:
35
35
  # pylint: disable=unused-import
36
36
  from scipy.linalg import cossin
37
37
 
38
- # coverage: ignore
39
38
  return None if version_is_greater_than_1_5_0 else func
40
39
  except ImportError:
41
- # coverage: ignore
42
40
  return func if version_is_greater_than_1_5_0 else None
43
41
 
44
42
  return decorator
@@ -84,8 +82,7 @@ def test_three_qubit_matrix_to_operations_errors():
84
82
  # environment like that, we'll need to ignore the coverage somehow conditionally on
85
83
  # the scipy version.
86
84
  @_skip_if_scipy(version_is_greater_than_1_5_0=True)
87
- # coverage: ignore
88
- def test_three_qubit_matrix_to_operations_scipy_error():
85
+ def test_three_qubit_matrix_to_operations_scipy_error(): # pragma: no cover
89
86
  a, b, c = cirq.LineQubit.range(3)
90
87
  with pytest.raises(ImportError, match="three_qubit.*1.5.0+"):
91
88
  cirq.three_qubit_matrix_to_operations(a, b, c, np.eye(8))
@@ -106,3 +106,41 @@ def prepare_two_qubit_state_using_cz(
106
106
  return op_list + _1q_matrices_to_ops(
107
107
  np.dot(u, np.linalg.inv(u_CZ)), np.dot(vh.T, np.linalg.inv(vh_CZ.T)), q0, q1
108
108
  )
109
+
110
+
111
+ def prepare_two_qubit_state_using_iswap(
112
+ q0: 'cirq.Qid', q1: 'cirq.Qid', state: 'cirq.STATE_VECTOR_LIKE', use_iswap_inv: bool = False
113
+ ) -> List['cirq.Operation']:
114
+ """Prepares the given 2q state from |00> using at-most 1 ISWAP gate + single qubit rotations.
115
+
116
+ Entangled states are prepared using exactly 1 ISWAP gate while product states are prepared
117
+ using only single qubit rotations (0 ISWAP gates)
118
+
119
+ Args:
120
+ q0: The first qubit being operated on.
121
+ q1: The other qubit being operated on.
122
+ state: 4x1 matrix representing two qubit state vector, ordered as 00, 01, 10, 11.
123
+ use_iswap_inv: If True, uses `cirq.ISWAP_INV` instead of `cirq.ISWAP`.
124
+
125
+ Returns:
126
+ List of operations (at-most 1 ISWAP + single qubit rotations) preparing state from |00>.
127
+ """
128
+ state_vector = qis.to_valid_state_vector(state, num_qubits=2)
129
+ state_vector = state_vector / np.linalg.norm(state_vector)
130
+ u, s, vh = np.linalg.svd(state_vector.reshape(2, 2))
131
+ if np.isclose(s[0], 1):
132
+ # Product state can be prepare with just single qubit unitaries.
133
+ return _1q_matrices_to_ops(u, vh.T, q0, q1, True)
134
+ alpha = np.arccos(np.clip(s[0], 0, 1))
135
+ op_list = [
136
+ ops.ry(2 * alpha).on(q0),
137
+ ops.H.on(q1),
138
+ ops.ISWAP_INV.on(q0, q1) if use_iswap_inv else ops.ISWAP.on(q0, q1),
139
+ ]
140
+ intermediate_state = circuits.Circuit(op_list).final_state_vector(
141
+ ignore_terminal_measurements=False, dtype=np.complex64
142
+ )
143
+ u_CZ, _, vh_CZ = np.linalg.svd(intermediate_state.reshape(2, 2))
144
+ return op_list + _1q_matrices_to_ops(
145
+ np.dot(u, np.linalg.inv(u_CZ)), np.dot(vh.T, np.linalg.inv(vh_CZ.T)), q0, q1
146
+ )
@@ -71,6 +71,24 @@ def test_prepare_two_qubit_state_using_cz(state):
71
71
  )
72
72
 
73
73
 
74
+ @pytest.mark.parametrize("state", STATES_TO_PREPARE)
75
+ @pytest.mark.parametrize("use_iswap_inv", [True, False])
76
+ def test_prepare_two_qubit_state_using_iswap(state, use_iswap_inv):
77
+ state = cirq.to_valid_state_vector(state, num_qubits=2)
78
+ q = cirq.LineQubit.range(2)
79
+ circuit = cirq.Circuit(
80
+ cirq.prepare_two_qubit_state_using_iswap(*q, state, use_iswap_inv=use_iswap_inv)
81
+ )
82
+ iswap_gate = cirq.ISWAP_INV if use_iswap_inv else cirq.ISWAP
83
+ ops_iswap = [*circuit.findall_operations(lambda op: op.gate == iswap_gate)]
84
+ ops_2q = [*circuit.findall_operations(lambda op: cirq.num_qubits(op) > 1)]
85
+ assert ops_iswap == ops_2q
86
+ assert len(ops_iswap) <= 1
87
+ assert cirq.allclose_up_to_global_phase(
88
+ circuit.final_state_vector(ignore_terminal_measurements=False, dtype=np.complex64), state
89
+ )
90
+
91
+
74
92
  @pytest.mark.parametrize("state", STATES_TO_PREPARE)
75
93
  @pytest.mark.parametrize("use_sqrt_iswap_inv", [True, False])
76
94
  def test_prepare_two_qubit_state_using_sqrt_iswap(state, use_sqrt_iswap_inv):
@@ -105,14 +105,14 @@ def test_recursive_composite():
105
105
 
106
106
 
107
107
  def test_decompose_returns_not_flat_op_tree():
108
- class DummyGate(cirq.testing.SingleQubitGate):
108
+ class ExampleGate(cirq.testing.SingleQubitGate):
109
109
  def _decompose_(self, qubits):
110
110
  (q0,) = qubits
111
111
  # Yield a tuple of gates instead of yielding a gate
112
112
  yield cirq.X(q0),
113
113
 
114
114
  q0 = cirq.NamedQubit('q0')
115
- circuit = cirq.Circuit(DummyGate()(q0))
115
+ circuit = cirq.Circuit(ExampleGate()(q0))
116
116
 
117
117
  circuit = cirq.expand_composite(circuit)
118
118
  expected = cirq.Circuit(cirq.X(q0))
@@ -120,7 +120,7 @@ def test_decompose_returns_not_flat_op_tree():
120
120
 
121
121
 
122
122
  def test_decompose_returns_deep_op_tree():
123
- class DummyGate(cirq.testing.TwoQubitGate):
123
+ class ExampleGate(cirq.testing.TwoQubitGate):
124
124
  def _decompose_(self, qubits):
125
125
  q0, q1 = qubits
126
126
  # Yield a tuple
@@ -139,7 +139,7 @@ def test_decompose_returns_deep_op_tree():
139
139
  yield generator(2)
140
140
 
141
141
  q0, q1 = cirq.LineQubit.range(2)
142
- circuit = cirq.Circuit(DummyGate()(q0, q1))
142
+ circuit = cirq.Circuit(ExampleGate()(q0, q1))
143
143
 
144
144
  circuit = cirq.expand_composite(circuit)
145
145
  expected = cirq.Circuit(
@@ -174,7 +174,7 @@ def kak_vector_infidelity(
174
174
 
175
175
  # Ensure we consider equivalent vectors for only the smallest input.
176
176
  if k_vec_a.size < k_vec_b.size:
177
- k_vec_a, k_vec_b = k_vec_b, k_vec_a # coverage: ignore
177
+ k_vec_a, k_vec_b = k_vec_b, k_vec_a # pragma: no cover
178
178
 
179
179
  k_vec_a = k_vec_a[..., np.newaxis, :] # (...,1,3)
180
180
  k_vec_b = _kak_equivalent_vectors(k_vec_b) # (...,192,3)
@@ -407,8 +407,7 @@ def two_qubit_gate_product_tabulation(
407
407
  # If all KAK vectors in the mesh have been tabulated, return.
408
408
  missing_vec_inds = np.logical_not(tabulated_kak_inds).nonzero()[0]
409
409
 
410
- if not np.any(missing_vec_inds):
411
- # coverage: ignore
410
+ if not np.any(missing_vec_inds): # pragma: no cover
412
411
  return TwoQubitGateTabulation(
413
412
  base_gate, np.array(kak_vecs), sq_cycles, max_infidelity, summary, ()
414
413
  )
@@ -74,10 +74,10 @@ def test_ignores_2qubit_target():
74
74
 
75
75
 
76
76
  def test_ignore_unsupported_gate():
77
- class UnsupportedDummy(cirq.testing.SingleQubitGate):
77
+ class UnsupportedExample(cirq.testing.SingleQubitGate):
78
78
  pass
79
79
 
80
- c = cirq.Circuit(UnsupportedDummy()(cirq.LineQubit(0)))
80
+ c = cirq.Circuit(UnsupportedExample()(cirq.LineQubit(0)))
81
81
  assert_optimizes(optimized=cirq.merge_k_qubit_unitaries(c, k=1), expected=c)
82
82
 
83
83
 
@@ -0,0 +1,177 @@
1
+ # Copyright 2023 The Cirq Developers
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from typing import Dict, Optional, Set, Tuple, TYPE_CHECKING
16
+
17
+ from cirq import circuits, ops
18
+
19
+ if TYPE_CHECKING:
20
+ import cirq
21
+
22
+
23
+ def _get_qubit_mapping_first_and_last_moment(
24
+ circuit: 'cirq.AbstractCircuit',
25
+ ) -> Dict['cirq.Qid', Tuple[int, int]]:
26
+ """Computes `(first_moment_idx, last_moment_idx)` tuple for each qubit in the input circuit.
27
+
28
+ Args:
29
+ circuit: An input cirq circuit to analyze.
30
+
31
+ Returns:
32
+ A dict mapping each qubit `q` in the input circuit to a tuple of integers
33
+ `(first_moment_idx, last_moment_idx)` where
34
+ - first_moment_idx: Index of leftmost moment containing an operation that acts on `q`.
35
+ - last_moment_idx: Index of rightmost moment containing an operation that acts on `q`.
36
+ """
37
+ ret = {q: (len(circuit), 0) for q in circuit.all_qubits()}
38
+ for i, moment in enumerate(circuit):
39
+ for q in moment.qubits:
40
+ ret[q] = (min(ret[q][0], i), max(ret[q][1], i))
41
+ return ret
42
+
43
+
44
+ def _is_temp(q: 'cirq.Qid') -> bool:
45
+ return isinstance(q, (ops.CleanQubit, ops.BorrowableQubit))
46
+
47
+
48
+ def map_clean_and_borrowable_qubits(
49
+ circuit: 'cirq.AbstractCircuit', *, qm: Optional['cirq.QubitManager'] = None
50
+ ) -> 'cirq.Circuit':
51
+ """Uses `qm: QubitManager` to map all `CleanQubit`/`BorrowableQubit`s to system qubits.
52
+
53
+ `CleanQubit` and `BorrowableQubit` are internal qubit types that are used as placeholder qubits
54
+ to record a clean / dirty ancilla allocation request.
55
+
56
+ This transformer uses the `QubitManager` provided in the input to:
57
+ - Allocate clean ancilla qubits by delegating to `qm.qalloc` for all `CleanQubit`s.
58
+ - Allocate dirty qubits for all `BorrowableQubit` types via the following two steps:
59
+ 1. First analyse the input circuit and check if there are any suitable system qubits
60
+ that can be borrowed, i.e. ones which do not have any overlapping operations
61
+ between circuit[start_index : end_index] where `(start_index, end_index)` is the
62
+ lifespan of temporary borrowable qubit under consideration. If yes, borrow the system
63
+ qubits to replace the temporary `BorrowableQubit`.
64
+ 2. If no system qubits can be borrowed, delegate the request to `qm.qborrow`.
65
+
66
+ Notes:
67
+ 1. The borrow protocol can be made more efficient by also considering the newly
68
+ allocated clean ancilla qubits in step-1 before delegating to `qm.borrow`, but this
69
+ optimization is left as a future improvement.
70
+ 2. As of now, the transformer does not preserve moment structure and defaults to
71
+ inserting all mapped operations in a resulting circuit using EARLIEST strategy. The reason
72
+ is that preserving moment structure forces additional constraints on the qubit allocation
73
+ strategy (i.e. if two operations `op1` and `op2` are in the same moment, then we cannot
74
+ reuse ancilla across `op1` and `op2`). We leave it upto the user to force such constraints
75
+ using the qubit manager instead of making it part of the transformer.
76
+ 3. However, for borrowable system qubits managed by the transformer, we do not reuse qubits
77
+ within the same moment.
78
+ 4. Since this is not implemented using the cirq transformers infrastructure, we currently
79
+ do not support recursive mapping within sub-circuits and that is left as a future TODO.
80
+
81
+ Args:
82
+ circuit: Input `cirq.Circuit` containing temporarily allocated
83
+ `CleanQubit`/`BorrowableQubit`s.
84
+ qm: An instance of `cirq.QubitManager` specifying the strategy to use for allocating /
85
+ / deallocating new ancilla qubits to replace the temporary qubits.
86
+
87
+ Returns:
88
+ An updated `cirq.Circuit` with all `CleanQubit`/`BorrowableQubit` mapped to either existing
89
+ system qubits or new ancilla qubits allocated using the `qm` qubit manager.
90
+ """
91
+ if qm is None:
92
+ qm = ops.GreedyQubitManager(prefix="ancilla")
93
+
94
+ allocated_qubits = {q for q in circuit.all_qubits() if _is_temp(q)}
95
+ qubits_lifespan = _get_qubit_mapping_first_and_last_moment(circuit)
96
+ all_qubits = frozenset(circuit.all_qubits() - allocated_qubits)
97
+ trivial_map = {q: q for q in all_qubits}
98
+ # `allocated_map` maintains the mapping of all temporary qubits seen so far, mapping each of
99
+ # them to either a newly allocated managed ancilla or an existing borrowed system qubit.
100
+ allocated_map: Dict['cirq.Qid', 'cirq.Qid'] = {}
101
+ to_free: Set['cirq.Qid'] = set()
102
+ last_op_idx = -1
103
+
104
+ def map_func(op: 'cirq.Operation', idx: int) -> 'cirq.OP_TREE':
105
+ nonlocal last_op_idx, to_free
106
+ assert isinstance(qm, ops.QubitManager)
107
+
108
+ for q in sorted(to_free):
109
+ is_managed_qubit = allocated_map[q] not in all_qubits
110
+ if idx > last_op_idx or is_managed_qubit:
111
+ # is_managed_qubit: if `q` is mapped to a newly allocated qubit managed by the qubit
112
+ # manager, we can free it immediately after the previous operation ends. This
113
+ # assumes that a managed qubit is not considered by the transformer as part of
114
+ # borrowing qubits (first point of the notes above).
115
+ # idx > last_op_idx: if `q` is mapped to a system qubit, which is not managed by the
116
+ # qubit manager, we free it only at the end of the moment.
117
+ if is_managed_qubit:
118
+ qm.qfree([allocated_map[q]])
119
+ allocated_map.pop(q)
120
+ to_free.remove(q)
121
+
122
+ last_op_idx = idx
123
+
124
+ # To check borrowable qubits, we manually manage only the original system qubits
125
+ # that are not managed by the qubit manager. If any of the system qubits cannot be
126
+ # borrowed, we defer to the qubit manager to allocate a new clean qubit for us.
127
+ # This is a heuristic and can be improved by also checking if any allocated but not
128
+ # yet freed managed qubit can be borrowed for the shorter scope, but we ignore the
129
+ # optimization for the sake of simplicity here.
130
+ borrowable_qubits = set(all_qubits) - set(allocated_map.values())
131
+
132
+ op_temp_qubits = (q for q in op.qubits if _is_temp(q))
133
+ for q in op_temp_qubits:
134
+ # Get the lifespan of this temporarily allocated ancilla qubit `q`.
135
+ st, en = qubits_lifespan[q]
136
+ assert st <= idx <= en
137
+ if en == idx:
138
+ # Mark that this temporarily allocated qubit can be freed after this moment ends.
139
+ to_free.add(q)
140
+ if q in allocated_map or st < idx:
141
+ # The qubit already has a mapping iff we have seen it before.
142
+ assert st < idx and q in allocated_map
143
+ # This line is actually covered by
144
+ # `test_map_clean_and_borrowable_qubits_deallocates_only_once` but pytest-cov seems
145
+ # to not recognize it and hence the pragma: no cover.
146
+ continue # pragma: no cover
147
+
148
+ # This is the first time we are seeing this temporary qubit and need to find a mapping.
149
+ if isinstance(q, ops.CleanQubit):
150
+ # Allocate a new clean qubit if `q` using the qubit manager.
151
+ allocated_map[q] = qm.qalloc(1)[0]
152
+ elif isinstance(q, ops.BorrowableQubit):
153
+ # For each of the system qubits that can be borrowed, check whether they have a
154
+ # conflicting operation in the range [st, en]; which is the scope for which the
155
+ # borrower needs the borrowed qubit for.
156
+ start_frontier = {q: st for q in borrowable_qubits}
157
+ end_frontier = {q: en + 1 for q in borrowable_qubits}
158
+ ops_in_between = circuit.findall_operations_between(start_frontier, end_frontier)
159
+ # Filter the set of borrowable qubits which do not have any conflicting operations.
160
+ filtered_borrowable_qubits = borrowable_qubits - set(
161
+ q for _, op in ops_in_between for q in op.qubits
162
+ )
163
+ if filtered_borrowable_qubits:
164
+ # Allocate a borrowable qubit and remove it from the pool of available qubits.
165
+ allocated_map[q] = min(filtered_borrowable_qubits)
166
+ borrowable_qubits.remove(allocated_map[q])
167
+ else:
168
+ # Use the qubit manager to get a new borrowable qubit, since we couldn't find
169
+ # one from the original system qubits.
170
+ allocated_map[q] = qm.qborrow(1)[0]
171
+ else:
172
+ assert False, f"Unknown temporary qubit type {q}"
173
+
174
+ # Return the transformed operation / decomposed op-tree.
175
+ return op.transform_qubits({**allocated_map, **trivial_map})
176
+
177
+ return circuits.Circuit(map_func(op, idx) for idx, m in enumerate(circuit) for op in m)