cirq-core 1.1.0.dev20221219200817__py3-none-any.whl → 1.2.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 (229) hide show
  1. cirq/__init__.py +8 -0
  2. cirq/_compat.py +29 -4
  3. cirq/_compat_test.py +24 -26
  4. cirq/_version.py +32 -1
  5. cirq/_version_test.py +1 -1
  6. cirq/circuits/_block_diagram_drawer_test.py +4 -3
  7. cirq/circuits/circuit.py +109 -63
  8. cirq/circuits/circuit_operation.py +2 -3
  9. cirq/circuits/circuit_operation_test.py +4 -4
  10. cirq/circuits/circuit_test.py +11 -0
  11. cirq/circuits/frozen_circuit.py +13 -1
  12. cirq/circuits/frozen_circuit_test.py +5 -1
  13. cirq/circuits/moment.py +39 -14
  14. cirq/circuits/moment_test.py +7 -0
  15. cirq/circuits/text_diagram_drawer.py +1 -1
  16. cirq/circuits/text_diagram_drawer_test.py +3 -7
  17. cirq/conftest.py +8 -0
  18. cirq/contrib/acquaintance/bipartite.py +1 -1
  19. cirq/contrib/acquaintance/devices.py +2 -2
  20. cirq/contrib/acquaintance/executor.py +5 -2
  21. cirq/contrib/acquaintance/gates.py +3 -2
  22. cirq/contrib/acquaintance/permutation.py +13 -2
  23. cirq/contrib/acquaintance/testing.py +3 -5
  24. cirq/contrib/paulistring/recombine.py +3 -6
  25. cirq/contrib/qasm_import/_parser.py +17 -21
  26. cirq/contrib/qasm_import/_parser_test.py +30 -45
  27. cirq/contrib/qcircuit/qcircuit_test.py +3 -7
  28. cirq/contrib/quantum_volume/quantum_volume.py +3 -3
  29. cirq/contrib/quimb/mps_simulator.py +1 -1
  30. cirq/contrib/quimb/state_vector.py +2 -0
  31. cirq/contrib/quirk/quirk_gate.py +1 -0
  32. cirq/contrib/svg/svg.py +4 -7
  33. cirq/contrib/svg/svg_test.py +29 -1
  34. cirq/devices/grid_qubit.py +26 -28
  35. cirq/devices/grid_qubit_test.py +21 -5
  36. cirq/devices/line_qubit.py +10 -12
  37. cirq/devices/line_qubit_test.py +9 -2
  38. cirq/devices/named_topologies.py +1 -1
  39. cirq/devices/noise_model.py +4 -1
  40. cirq/devices/superconducting_qubits_noise_properties.py +1 -3
  41. cirq/experiments/n_qubit_tomography.py +1 -1
  42. cirq/experiments/qubit_characterizations.py +2 -2
  43. cirq/experiments/single_qubit_readout_calibration.py +1 -1
  44. cirq/experiments/t2_decay_experiment.py +1 -1
  45. cirq/experiments/xeb_simulation_test.py +2 -2
  46. cirq/interop/quirk/cells/testing.py +1 -1
  47. cirq/json_resolver_cache.py +1 -0
  48. cirq/linalg/__init__.py +2 -0
  49. cirq/linalg/decompositions_test.py +4 -4
  50. cirq/linalg/diagonalize_test.py +5 -6
  51. cirq/linalg/transformations.py +72 -9
  52. cirq/linalg/transformations_test.py +23 -7
  53. cirq/ops/__init__.py +4 -0
  54. cirq/ops/arithmetic_operation.py +4 -6
  55. cirq/ops/classically_controlled_operation.py +10 -3
  56. cirq/ops/clifford_gate.py +1 -7
  57. cirq/ops/common_channels.py +21 -15
  58. cirq/ops/common_gate_families.py +2 -3
  59. cirq/ops/common_gates.py +48 -11
  60. cirq/ops/common_gates_test.py +4 -0
  61. cirq/ops/controlled_gate.py +44 -18
  62. cirq/ops/controlled_operation.py +13 -5
  63. cirq/ops/dense_pauli_string.py +14 -19
  64. cirq/ops/diagonal_gate.py +3 -4
  65. cirq/ops/eigen_gate.py +8 -10
  66. cirq/ops/eigen_gate_test.py +6 -0
  67. cirq/ops/gate_operation.py +11 -6
  68. cirq/ops/gate_operation_test.py +11 -2
  69. cirq/ops/gateset.py +2 -1
  70. cirq/ops/gateset_test.py +38 -5
  71. cirq/ops/global_phase_op.py +28 -2
  72. cirq/ops/global_phase_op_test.py +21 -0
  73. cirq/ops/identity.py +1 -1
  74. cirq/ops/kraus_channel_test.py +2 -2
  75. cirq/ops/linear_combinations.py +7 -6
  76. cirq/ops/linear_combinations_test.py +26 -10
  77. cirq/ops/matrix_gates.py +8 -4
  78. cirq/ops/matrix_gates_test.py +25 -3
  79. cirq/ops/measure_util.py +13 -5
  80. cirq/ops/measure_util_test.py +8 -2
  81. cirq/ops/measurement_gate.py +1 -1
  82. cirq/ops/measurement_gate_test.py +9 -4
  83. cirq/ops/mixed_unitary_channel_test.py +4 -4
  84. cirq/ops/named_qubit.py +2 -4
  85. cirq/ops/parity_gates.py +5 -1
  86. cirq/ops/parity_gates_test.py +6 -0
  87. cirq/ops/pauli_gates.py +9 -9
  88. cirq/ops/pauli_string.py +4 -2
  89. cirq/ops/pauli_string_raw_types.py +4 -11
  90. cirq/ops/pauli_string_test.py +13 -13
  91. cirq/ops/pauli_sum_exponential.py +6 -1
  92. cirq/ops/qubit_manager.py +97 -0
  93. cirq/ops/qubit_manager_test.py +66 -0
  94. cirq/ops/raw_types.py +75 -33
  95. cirq/ops/raw_types_test.py +34 -0
  96. cirq/ops/three_qubit_gates.py +16 -10
  97. cirq/ops/three_qubit_gates_test.py +4 -2
  98. cirq/ops/two_qubit_diagonal_gate.py +3 -3
  99. cirq/ops/wait_gate.py +1 -1
  100. cirq/protocols/__init__.py +1 -0
  101. cirq/protocols/act_on_protocol.py +3 -3
  102. cirq/protocols/act_on_protocol_test.py +5 -5
  103. cirq/protocols/apply_channel_protocol.py +9 -8
  104. cirq/protocols/apply_mixture_protocol.py +8 -8
  105. cirq/protocols/apply_mixture_protocol_test.py +1 -1
  106. cirq/protocols/apply_unitary_protocol.py +66 -19
  107. cirq/protocols/apply_unitary_protocol_test.py +50 -0
  108. cirq/protocols/circuit_diagram_info_protocol.py +7 -9
  109. cirq/protocols/decompose_protocol.py +167 -125
  110. cirq/protocols/decompose_protocol_test.py +132 -2
  111. cirq/protocols/has_stabilizer_effect_protocol.py +2 -1
  112. cirq/protocols/inverse_protocol.py +2 -2
  113. cirq/protocols/json_serialization_test.py +3 -3
  114. cirq/protocols/json_test_data/Linspace.json +20 -7
  115. cirq/protocols/json_test_data/Linspace.repr +4 -1
  116. cirq/protocols/json_test_data/Points.json +19 -8
  117. cirq/protocols/json_test_data/Points.repr +4 -1
  118. cirq/protocols/json_test_data/Result.repr_inward +1 -1
  119. cirq/protocols/json_test_data/ResultDict.repr +1 -1
  120. cirq/protocols/json_test_data/ResultDict.repr_inward +1 -1
  121. cirq/protocols/json_test_data/TrialResult.repr_inward +1 -1
  122. cirq/protocols/json_test_data/XPowGate.json +13 -5
  123. cirq/protocols/json_test_data/XPowGate.repr +1 -1
  124. cirq/protocols/json_test_data/ZPowGate.json +13 -5
  125. cirq/protocols/json_test_data/ZPowGate.repr +1 -1
  126. cirq/protocols/json_test_data/ZipLongest.json +19 -0
  127. cirq/protocols/json_test_data/ZipLongest.repr +1 -0
  128. cirq/protocols/json_test_data/spec.py +1 -0
  129. cirq/protocols/kraus_protocol.py +3 -4
  130. cirq/protocols/measurement_key_protocol.py +3 -1
  131. cirq/protocols/mixture_protocol.py +3 -2
  132. cirq/protocols/phase_protocol.py +3 -3
  133. cirq/protocols/pow_protocol.py +1 -2
  134. cirq/protocols/qasm.py +4 -4
  135. cirq/protocols/qid_shape_protocol.py +8 -8
  136. cirq/protocols/resolve_parameters.py +8 -3
  137. cirq/protocols/resolve_parameters_test.py +3 -3
  138. cirq/protocols/unitary_protocol.py +19 -11
  139. cirq/protocols/unitary_protocol_test.py +37 -0
  140. cirq/qis/channels.py +1 -1
  141. cirq/qis/clifford_tableau.py +4 -5
  142. cirq/qis/quantum_state_representation.py +7 -9
  143. cirq/qis/states.py +21 -13
  144. cirq/qis/states_test.py +7 -0
  145. cirq/sim/clifford/clifford_simulator.py +3 -3
  146. cirq/sim/density_matrix_simulation_state.py +2 -1
  147. cirq/sim/density_matrix_simulator.py +1 -1
  148. cirq/sim/density_matrix_simulator_test.py +9 -5
  149. cirq/sim/density_matrix_utils.py +7 -32
  150. cirq/sim/mux.py +2 -2
  151. cirq/sim/simulation_state.py +58 -18
  152. cirq/sim/simulation_state_base.py +5 -2
  153. cirq/sim/simulation_state_test.py +121 -9
  154. cirq/sim/simulation_utils.py +59 -0
  155. cirq/sim/simulation_utils_test.py +32 -0
  156. cirq/sim/simulator.py +2 -1
  157. cirq/sim/simulator_base_test.py +3 -3
  158. cirq/sim/sparse_simulator.py +1 -1
  159. cirq/sim/sparse_simulator_test.py +5 -5
  160. cirq/sim/state_vector.py +7 -36
  161. cirq/sim/state_vector_simulation_state.py +18 -1
  162. cirq/sim/state_vector_simulator.py +3 -2
  163. cirq/sim/state_vector_simulator_test.py +24 -2
  164. cirq/sim/state_vector_test.py +46 -15
  165. cirq/study/__init__.py +1 -0
  166. cirq/study/flatten_expressions.py +2 -2
  167. cirq/study/resolver.py +2 -0
  168. cirq/study/resolver_test.py +1 -1
  169. cirq/study/result.py +1 -1
  170. cirq/study/sweeps.py +103 -9
  171. cirq/study/sweeps_test.py +64 -0
  172. cirq/testing/__init__.py +4 -0
  173. cirq/testing/circuit_compare.py +15 -18
  174. cirq/testing/consistent_act_on.py +4 -4
  175. cirq/testing/consistent_controlled_gate_op_test.py +1 -1
  176. cirq/testing/consistent_decomposition.py +11 -2
  177. cirq/testing/consistent_decomposition_test.py +8 -1
  178. cirq/testing/consistent_protocols.py +2 -0
  179. cirq/testing/consistent_protocols_test.py +8 -4
  180. cirq/testing/consistent_qasm.py +8 -15
  181. cirq/testing/consistent_specified_has_unitary.py +1 -1
  182. cirq/testing/consistent_unitary.py +85 -0
  183. cirq/testing/consistent_unitary_test.py +96 -0
  184. cirq/testing/equivalent_repr_eval.py +10 -10
  185. cirq/testing/json.py +3 -3
  186. cirq/testing/logs.py +1 -1
  187. cirq/testing/order_tester.py +4 -5
  188. cirq/testing/random_circuit.py +3 -5
  189. cirq/testing/sample_gates.py +79 -0
  190. cirq/testing/sample_gates_test.py +59 -0
  191. cirq/transformers/__init__.py +2 -0
  192. cirq/transformers/analytical_decompositions/__init__.py +8 -0
  193. cirq/transformers/analytical_decompositions/pauli_string_decomposition.py +130 -0
  194. cirq/transformers/analytical_decompositions/pauli_string_decomposition_test.py +58 -0
  195. cirq/transformers/analytical_decompositions/quantum_shannon_decomposition.py +230 -0
  196. cirq/transformers/analytical_decompositions/quantum_shannon_decomposition_test.py +112 -0
  197. cirq/transformers/analytical_decompositions/three_qubit_decomposition_test.py +1 -3
  198. cirq/transformers/analytical_decompositions/two_qubit_to_fsim.py +1 -1
  199. cirq/transformers/expand_composite.py +1 -1
  200. cirq/transformers/heuristic_decompositions/gate_tabulation_math_utils.py +4 -4
  201. cirq/transformers/measurement_transformers.py +4 -4
  202. cirq/transformers/merge_single_qubit_gates.py +17 -4
  203. cirq/transformers/routing/route_circuit_cqc.py +2 -2
  204. cirq/transformers/stratify.py +125 -62
  205. cirq/transformers/stratify_test.py +20 -16
  206. cirq/transformers/transformer_api.py +1 -1
  207. cirq/transformers/transformer_primitives.py +3 -2
  208. cirq/transformers/transformer_primitives_test.py +11 -0
  209. cirq/value/abc_alt.py +3 -2
  210. cirq/value/abc_alt_test.py +1 -0
  211. cirq/value/classical_data.py +10 -10
  212. cirq/value/digits.py +2 -2
  213. cirq/value/linear_dict.py +18 -19
  214. cirq/value/product_state.py +7 -6
  215. cirq/value/value_equality_attr.py +2 -2
  216. cirq/vis/heatmap.py +1 -1
  217. cirq/vis/heatmap_test.py +2 -2
  218. cirq/work/collector.py +2 -2
  219. cirq/work/observable_measurement_data.py +5 -5
  220. cirq/work/observable_readout_calibration.py +3 -1
  221. cirq/work/observable_settings.py +1 -1
  222. cirq/work/pauli_sum_collector.py +9 -8
  223. cirq/work/sampler.py +2 -0
  224. cirq/work/zeros_sampler.py +2 -2
  225. {cirq_core-1.1.0.dev20221219200817.dist-info → cirq_core-1.2.0.dist-info}/METADATA +7 -15
  226. {cirq_core-1.1.0.dev20221219200817.dist-info → cirq_core-1.2.0.dist-info}/RECORD +229 -215
  227. {cirq_core-1.1.0.dev20221219200817.dist-info → cirq_core-1.2.0.dist-info}/WHEEL +1 -1
  228. {cirq_core-1.1.0.dev20221219200817.dist-info → cirq_core-1.2.0.dist-info}/LICENSE +0 -0
  229. {cirq_core-1.1.0.dev20221219200817.dist-info → cirq_core-1.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,79 @@
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
+ import dataclasses
15
+
16
+ import cirq
17
+ import numpy as np
18
+ from cirq import ops, qis, protocols
19
+
20
+
21
+ def _matrix_for_phasing_state(num_qubits, phase_state, phase):
22
+ matrix = qis.eye_tensor((2,) * num_qubits, dtype=np.complex128)
23
+ matrix = matrix.reshape((2**num_qubits, 2**num_qubits))
24
+ matrix[phase_state, phase_state] = phase
25
+ print(num_qubits, phase_state, phase)
26
+ print(matrix)
27
+ return matrix
28
+
29
+
30
+ @dataclasses.dataclass(frozen=True)
31
+ class PhaseUsingCleanAncilla(ops.Gate):
32
+ r"""Phases the state $|phase_state>$ by $\exp(1j * \pi * \theta)$ using one clean ancilla."""
33
+
34
+ theta: float
35
+ phase_state: int = 1
36
+ target_bitsize: int = 1
37
+ ancilla_bitsize: int = 1
38
+
39
+ def _num_qubits_(self):
40
+ return self.target_bitsize
41
+
42
+ def _decompose_with_context_(self, qubits, *, context: protocols.DecompositionContext):
43
+ anc = context.qubit_manager.qalloc(self.ancilla_bitsize)
44
+ cv = [int(x) for x in f'{self.phase_state:0{self.target_bitsize}b}']
45
+ cnot_ladder = [cirq.CNOT(anc[i - 1], anc[i]) for i in range(1, self.ancilla_bitsize)]
46
+
47
+ yield ops.X(anc[0]).controlled_by(*qubits, control_values=cv)
48
+ yield [cnot_ladder, ops.Z(anc[-1]) ** self.theta, reversed(cnot_ladder)]
49
+ yield ops.X(anc[0]).controlled_by(*qubits, control_values=cv)
50
+
51
+ def narrow_unitary(self) -> np.ndarray:
52
+ """Narrowed unitary corresponding to the unitary effect applied on target qubits."""
53
+ phase = np.exp(1j * np.pi * self.theta)
54
+ return _matrix_for_phasing_state(self.target_bitsize, self.phase_state, phase)
55
+
56
+
57
+ @dataclasses.dataclass(frozen=True)
58
+ class PhaseUsingDirtyAncilla(ops.Gate):
59
+ r"""Phases the state $|phase_state>$ by -1 using one dirty ancilla."""
60
+
61
+ phase_state: int = 1
62
+ target_bitsize: int = 1
63
+ ancilla_bitsize: int = 1
64
+
65
+ def _num_qubits_(self):
66
+ return self.target_bitsize
67
+
68
+ def _decompose_with_context_(self, qubits, *, context: protocols.DecompositionContext):
69
+ anc = context.qubit_manager.qalloc(self.ancilla_bitsize)
70
+ cv = [int(x) for x in f'{self.phase_state:0{self.target_bitsize}b}']
71
+ cnot_ladder = [cirq.CNOT(anc[i - 1], anc[i]) for i in range(1, self.ancilla_bitsize)]
72
+ yield ops.X(anc[0]).controlled_by(*qubits, control_values=cv)
73
+ yield [cnot_ladder, ops.Z(anc[-1]), reversed(cnot_ladder)]
74
+ yield ops.X(anc[0]).controlled_by(*qubits, control_values=cv)
75
+ yield [cnot_ladder, ops.Z(anc[-1]), reversed(cnot_ladder)]
76
+
77
+ def narrow_unitary(self) -> np.ndarray:
78
+ """Narrowed unitary corresponding to the unitary effect applied on target qubits."""
79
+ return _matrix_for_phasing_state(self.target_bitsize, self.phase_state, -1)
@@ -0,0 +1,59 @@
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
+ import pytest
15
+
16
+ import numpy as np
17
+ from cirq.testing import sample_gates
18
+ import cirq
19
+
20
+
21
+ @pytest.mark.parametrize('theta', np.linspace(0, 2 * np.pi, 20))
22
+ def test_phase_using_clean_ancilla(theta: float):
23
+ g = sample_gates.PhaseUsingCleanAncilla(theta)
24
+ q = cirq.LineQubit(0)
25
+ qubit_order = cirq.QubitOrder.explicit([q], fallback=cirq.QubitOrder.DEFAULT)
26
+ decomposed_unitary = cirq.Circuit(cirq.decompose_once(g.on(q))).unitary(qubit_order=qubit_order)
27
+ phase = np.exp(1j * np.pi * theta)
28
+ np.testing.assert_allclose(g.narrow_unitary(), np.array([[1, 0], [0, phase]]))
29
+ np.testing.assert_allclose(
30
+ decomposed_unitary,
31
+ # fmt: off
32
+ np.array(
33
+ [
34
+ [1 , 0 , 0 , 0],
35
+ [0 , phase, 0 , 0],
36
+ [0 , 0 , phase, 0],
37
+ [0 , 0 , 0 , 1],
38
+ ]
39
+ ),
40
+ # fmt: on
41
+ )
42
+
43
+
44
+ @pytest.mark.parametrize(
45
+ 'target_bitsize, phase_state', [(1, 0), (1, 1), (2, 0), (2, 1), (2, 2), (2, 3)]
46
+ )
47
+ @pytest.mark.parametrize('ancilla_bitsize', [1, 4])
48
+ def test_phase_using_dirty_ancilla(target_bitsize, phase_state, ancilla_bitsize):
49
+ g = sample_gates.PhaseUsingDirtyAncilla(phase_state, target_bitsize, ancilla_bitsize)
50
+ q = cirq.LineQubit.range(target_bitsize)
51
+ qubit_order = cirq.QubitOrder.explicit(q, fallback=cirq.QubitOrder.DEFAULT)
52
+ decomposed_circuit = cirq.Circuit(cirq.decompose_once(g.on(*q)))
53
+ decomposed_unitary = decomposed_circuit.unitary(qubit_order=qubit_order)
54
+ phase_matrix = np.eye(2**target_bitsize)
55
+ phase_matrix[phase_state, phase_state] = -1
56
+ np.testing.assert_allclose(g.narrow_unitary(), phase_matrix)
57
+ np.testing.assert_allclose(
58
+ decomposed_unitary, np.kron(phase_matrix, np.eye(2**ancilla_bitsize)), atol=1e-5
59
+ )
@@ -25,6 +25,7 @@ from cirq.transformers.analytical_decompositions import (
25
25
  parameterized_2q_op_to_sqrt_iswap_operations,
26
26
  prepare_two_qubit_state_using_cz,
27
27
  prepare_two_qubit_state_using_sqrt_iswap,
28
+ quantum_shannon_decomposition,
28
29
  single_qubit_matrix_to_gates,
29
30
  single_qubit_matrix_to_pauli_rotations,
30
31
  single_qubit_matrix_to_phased_x_z,
@@ -36,6 +37,7 @@ from cirq.transformers.analytical_decompositions import (
36
37
  two_qubit_matrix_to_diagonal_and_cz_operations,
37
38
  two_qubit_matrix_to_ion_operations,
38
39
  two_qubit_matrix_to_sqrt_iswap_operations,
40
+ unitary_to_pauli_string,
39
41
  )
40
42
 
41
43
  from cirq.transformers.heuristic_decompositions import (
@@ -28,6 +28,10 @@ from cirq.transformers.analytical_decompositions.cphase_to_fsim import (
28
28
  decompose_cphase_into_two_fsim,
29
29
  )
30
30
 
31
+ from cirq.transformers.analytical_decompositions.quantum_shannon_decomposition import (
32
+ quantum_shannon_decomposition,
33
+ )
34
+
31
35
  from cirq.transformers.analytical_decompositions.single_qubit_decompositions import (
32
36
  is_negligible_turn,
33
37
  single_qubit_matrix_to_gates,
@@ -67,3 +71,7 @@ from cirq.transformers.analytical_decompositions.two_qubit_state_preparation imp
67
71
  from cirq.transformers.analytical_decompositions.single_to_two_qubit_isometry import (
68
72
  two_qubit_matrix_to_cz_isometry,
69
73
  )
74
+
75
+ from cirq.transformers.analytical_decompositions.pauli_string_decomposition import (
76
+ unitary_to_pauli_string,
77
+ )
@@ -0,0 +1,130 @@
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 Optional, Tuple, cast
16
+
17
+ import numpy as np
18
+ import numpy.typing as npt
19
+
20
+ from cirq.ops import DensePauliString
21
+ from cirq import protocols
22
+
23
+
24
+ def _argmax(V: npt.NDArray) -> Tuple[int, float]:
25
+ """Returns a tuple (index of max number, max number)."""
26
+ V = (V * V.conj()).real
27
+ idx_max = np.argmax(V)
28
+ V[idx_max] = 0
29
+ return cast(int, idx_max), np.max(V)
30
+
31
+
32
+ def _validate_decomposition(decomposition: DensePauliString, U: npt.NDArray, eps: float) -> bool:
33
+ """Returns whether the max absolute value of the elementwise difference is less than eps."""
34
+ got = protocols.unitary(decomposition)
35
+ return np.abs(got - U).max() < eps
36
+
37
+
38
+ def _fast_walsh_hadamard_transform(V: npt.NDArray) -> None:
39
+ """Fast Walsh–Hadamard Transform of an array."""
40
+ m = len(V)
41
+ n = m.bit_length() - 1
42
+ for h in [2**i for i in range(n)]:
43
+ for i in range(0, m, h * 2):
44
+ for j in range(i, i + h):
45
+ x = V[j]
46
+ y = V[j + h]
47
+ V[j] = x + y
48
+ V[j + h] = x - y
49
+
50
+
51
+ def _conjugate_with_hadamard(U: npt.NDArray) -> npt.NDArray:
52
+ """Applies HcUH in O(n4^n) instead of O(8^n)."""
53
+
54
+ U = np.copy(U.T)
55
+ for i in range(U.shape[1]):
56
+ _fast_walsh_hadamard_transform(U[:, i])
57
+ U = U.T
58
+ for i in range(U.shape[1]):
59
+ _fast_walsh_hadamard_transform(U[:, i])
60
+ return U
61
+
62
+
63
+ def unitary_to_pauli_string(U: npt.NDArray, eps: float = 1e-15) -> Optional[DensePauliString]:
64
+ """Attempts to find a pauli string (with possible phase) equivalent to U up to eps.
65
+
66
+ Based on this answer https://shorturl.at/aA079.
67
+ Let x_mask be the index of the maximum number of the first column of U
68
+ and z_mask be the index of the maximum number of the first column of H†UH
69
+ each of these indicies is n-bits long where U is 2^n x 2^n.
70
+
71
+ These two indices/masks encode in binary the indices of the qubits that
72
+ have I, X, Y, Z acting on them as follows:
73
+ x_mask[i] == 1 and z_mask[i] == 0: X acts on the ith qubit
74
+ x_mask[i] == 0 and z_mask[i] == 1: Z acts on the ith qubit
75
+ x_mask[i] == 1 and z_mask[i] == 1: Y acts on the ith qubit
76
+ x_mask[i] == 0 and z_mask[i] == 0: I acts on the ith qubit
77
+
78
+ Args:
79
+ U: A square array whose dimension is a power of 2.
80
+ eps: numbers smaller than `eps` are considered zero.
81
+
82
+ Returns:
83
+ A DensePauliString of None.
84
+
85
+ Raises:
86
+ ValueError: if U is not square with a power of 2 dimension.
87
+ """
88
+
89
+ if len(U.shape) != 2 or U.shape[0] != U.shape[1]:
90
+ raise ValueError(f'Input has a non-square shape {U}')
91
+ n = U.shape[0].bit_length() - 1
92
+ if U.shape[0] != 2**n:
93
+ raise ValueError(f'Input dimension {U.shape[0]} isn\'t a power of 2')
94
+
95
+ x_msk, second_largest = _argmax(U[:, 0])
96
+ if second_largest > eps:
97
+ return None
98
+ U_z = _conjugate_with_hadamard(U)
99
+ z_msk, second_largest = _argmax(U_z[:, 0])
100
+ if second_largest > eps:
101
+ return None
102
+
103
+ def select(i):
104
+ """Returns the gate that acts on the ith qubit."""
105
+ has_x = (x_msk >> i) & 1
106
+ has_z = (z_msk >> i) & 1
107
+ # The mapping is:
108
+ # - has_x and not has_z => X
109
+ # - not has_x and has_z => Z
110
+ # - has_x and has_z => Y
111
+ # - not has_x and not has_z => I
112
+ gate_table = ['IX', 'ZY']
113
+ return gate_table[has_z][has_x]
114
+
115
+ decomposition = DensePauliString(''.join(select(i) for i in reversed(range(n))))
116
+
117
+ guess = protocols.unitary(decomposition)
118
+ if np.abs(guess[x_msk, 0]) < eps:
119
+ return None
120
+ phase = U[x_msk, 0] / guess[x_msk, 0]
121
+ phase /= np.abs(phase) # Make sure |phase| = 1 to avoid rounding issues.
122
+
123
+ decomposition = DensePauliString(
124
+ ''.join(select(i) for i in reversed(range(n))), coefficient=phase
125
+ )
126
+
127
+ if not _validate_decomposition(decomposition, U, eps):
128
+ return None
129
+
130
+ return decomposition
@@ -0,0 +1,58 @@
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 cast
16
+ import itertools
17
+ import cmath
18
+ import pytest
19
+
20
+ import numpy as np
21
+
22
+ from cirq.ops import DensePauliString, T
23
+ from cirq import protocols
24
+ from cirq.transformers.analytical_decompositions import unitary_to_pauli_string
25
+
26
+
27
+ @pytest.mark.parametrize('phase', [cmath.exp(i * 2 * cmath.pi / 5 * 1j) for i in range(5)])
28
+ @pytest.mark.parametrize(
29
+ 'pauli_string', [''.join(p) for p in itertools.product(['', 'I', 'X', 'Y', 'Z'], repeat=4)]
30
+ )
31
+ def test_unitary_to_pauli_string(pauli_string: str, phase: complex):
32
+ want = DensePauliString(pauli_string, coefficient=phase)
33
+ got = unitary_to_pauli_string(protocols.unitary(want))
34
+ assert got is not None
35
+ assert np.all(want.pauli_mask == got.pauli_mask)
36
+ assert np.isclose(cast(np.complex128, want.coefficient), cast(np.complex128, got.coefficient))
37
+
38
+
39
+ def test_unitary_to_pauli_string_non_pauli_input():
40
+ got = unitary_to_pauli_string(protocols.unitary(T))
41
+ assert got is None
42
+
43
+ got = unitary_to_pauli_string(np.array([[1, 0], [1, 0]]))
44
+ assert got is None
45
+
46
+ got = unitary_to_pauli_string(np.array([[1, 1], [0, 2]]))
47
+ assert got is None
48
+
49
+ got = unitary_to_pauli_string(np.array([[0, 0.5], [1, -1]]), eps=1.1)
50
+ assert got is None
51
+
52
+
53
+ def test_invalid_input():
54
+ with pytest.raises(ValueError, match='Input has a non-square shape.*'):
55
+ _ = unitary_to_pauli_string(np.zeros((2, 3)))
56
+
57
+ with pytest.raises(ValueError, match='Input dimension [0-9]* isn\'t a power of 2'):
58
+ _ = unitary_to_pauli_string(np.zeros((3, 3)))
@@ -0,0 +1,230 @@
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
+
16
+ """Utility methods for decomposing arbitrary n-qubit (2^n x 2^n) unitary.
17
+
18
+ Based on:
19
+ Synthesis of Quantum Logic Circuits. Tech. rep. 2006,
20
+ https://arxiv.org/abs/quant-ph/0406176
21
+ """
22
+ from typing import List, Callable, TYPE_CHECKING
23
+
24
+ from scipy.linalg import cossin
25
+
26
+ import numpy as np
27
+
28
+ from cirq import ops
29
+ from cirq.linalg import decompositions, predicates
30
+
31
+ if TYPE_CHECKING:
32
+ import cirq
33
+ from cirq.ops import op_tree
34
+
35
+
36
+ def quantum_shannon_decomposition(qubits: 'List[cirq.Qid]', u: np.ndarray) -> 'op_tree.OpTree':
37
+ """Decomposes n-qubit unitary into CX/YPow/ZPow/CNOT gates, preserving global phase.
38
+
39
+ The algorithm is described in Shende et al.:
40
+ Synthesis of Quantum Logic Circuits. Tech. rep. 2006,
41
+ https://arxiv.org/abs/quant-ph/0406176
42
+
43
+ Args:
44
+ qubits: List of qubits in order of significance
45
+ u: Numpy array for unitary matrix representing gate to be decomposed
46
+
47
+ Calls:
48
+ (Base Case)
49
+ 1. _single_qubit_decomposition
50
+ OR
51
+ (Recursive Case)
52
+ 1. _msb_demuxer
53
+ 2. _multiplexed_cossin
54
+ 3. _msb_demuxer
55
+
56
+ Yields:
57
+ A single 2-qubit or 1-qubit operations from OP TREE
58
+ composed from the set
59
+ { CNOT, rz, ry, ZPowGate }
60
+
61
+ Raises:
62
+ ValueError: If the u matrix is non-unitary
63
+ ValueError: If the u matrix is not of shape (2^n,2^n)
64
+ """
65
+ if not predicates.is_unitary(u): # Check that u is unitary
66
+ raise ValueError(
67
+ "Expected input matrix u to be unitary, \
68
+ but it fails cirq.is_unitary check"
69
+ )
70
+
71
+ n = u.shape[0]
72
+ if n & (n - 1):
73
+ raise ValueError(
74
+ f"Expected input matrix u to be a (2^n x 2^n) shaped numpy array, \
75
+ but instead got shape {u.shape}"
76
+ )
77
+
78
+ if n == 2:
79
+ # Yield a single-qubit decomp if u is 2x2
80
+ yield from _single_qubit_decomposition(qubits[0], u)
81
+ return
82
+
83
+ # Perform a cosine-sine (linalg) decomposition on u
84
+ # X = [ u1 , 0 ] [ cos(theta) , -sin(theta) ] [ v1 , 0 ]
85
+ # [ 0 , u2 ] [ sin(theta) , cos(theta) ] [ 0 , v2 ]
86
+ (u1, u2), theta, (v1, v2) = cossin(u, n / 2, n / 2, separate=True)
87
+
88
+ # Yield ops from decomposition of multiplexed v1/v2 part
89
+ yield from _msb_demuxer(qubits, v1, v2)
90
+
91
+ # Observe that middle part looks like Σ_i( Ry(theta_i)⊗|i><i| )
92
+ # Then most significant qubit is Ry multiplexed over all other qubits
93
+ # Yield ops from multiplexed Ry part
94
+ yield from _multiplexed_cossin(qubits, theta, ops.ry)
95
+
96
+ # Yield ops from decomposition of multiplexed u1/u2 part
97
+ yield from _msb_demuxer(qubits, u1, u2)
98
+
99
+
100
+ def _single_qubit_decomposition(qubit: 'cirq.Qid', u: np.ndarray) -> 'op_tree.OpTree':
101
+ """Decomposes single-qubit gate, and returns list of operations, keeping phase invariant.
102
+
103
+ Args:
104
+ qubit: Qubit on which to apply operations
105
+ u: (2 x 2) Numpy array for unitary representing 1-qubit gate to be decomposed
106
+
107
+ Yields:
108
+ A single operation from OP TREE of 3 operations (rz,ry,ZPowGate)
109
+ """
110
+ # Perform native ZYZ decomposition
111
+ phi_0, phi_1, phi_2 = decompositions.deconstruct_single_qubit_matrix_into_angles(u)
112
+
113
+ # Determine global phase picked up
114
+ phase = np.angle(u[0, 0] / (np.exp(-1j * (phi_0) / 2) * np.cos(phi_1 / 2)))
115
+
116
+ # Append first two operations operations
117
+ yield ops.rz(phi_0).on(qubit)
118
+ yield ops.ry(phi_1).on(qubit)
119
+
120
+ # Append third operation with global phase added
121
+ yield ops.ZPowGate(exponent=phi_2 / np.pi, global_shift=phase / phi_2).on(qubit)
122
+
123
+
124
+ def _msb_demuxer(
125
+ demux_qubits: 'List[cirq.Qid]', u1: np.ndarray, u2: np.ndarray
126
+ ) -> 'op_tree.OpTree':
127
+ """Demultiplexes a unitary matrix that is multiplexed in its most-significant-qubit.
128
+
129
+ Decomposition structure:
130
+ [ u1 , 0 ] = [ V , 0 ][ D , 0 ][ W , 0 ]
131
+ [ 0 , u2 ] [ 0 , V ][ 0 , D* ][ 0 , W ]
132
+
133
+ Gives: ( u1 )( u2* ) = ( V )( D^2 )( V* )
134
+ and: W = ( D )( V* )( u2 )
135
+
136
+ Args:
137
+ demux_qubits: Subset of total qubits involved in this unitary gate
138
+ u1: Upper-left quadrant of total unitary to be decomposed (see diagram)
139
+ u2: Lower-right quadrant of total unitary to be decomposed (see diagram)
140
+
141
+ Calls:
142
+ 1. quantum_shannon_decomposition
143
+ 2. _multiplexed_cossin
144
+ 3. quantum_shannon_decomposition
145
+
146
+ Yields: Single operation from OP TREE of 2-qubit and 1-qubit operations
147
+ """
148
+ # Perform a diagonalization to find values
149
+ u = u1 @ u2.T.conjugate()
150
+ dsquared, V = np.linalg.eig(u)
151
+ d = np.sqrt(dsquared)
152
+ D = np.diag(d)
153
+ W = D @ V.T.conjugate() @ u2
154
+
155
+ # Last term is given by ( I ⊗ W ), demultiplexed
156
+ # Remove most-significant (demuxed) control-qubit
157
+ # Yield operations for QSD on W
158
+ yield from quantum_shannon_decomposition(demux_qubits[1:], W)
159
+
160
+ # Use complex phase of d_i to give theta_i (so d_i* gives -theta_i)
161
+ # Observe that middle part looks like Σ_i( Rz(theta_i)⊗|i><i| )
162
+ # Yield ops from multiplexed Rz part
163
+ yield from _multiplexed_cossin(demux_qubits, -np.angle(d), ops.rz)
164
+
165
+ # Yield operations for QSD on V
166
+ yield from quantum_shannon_decomposition(demux_qubits[1:], V)
167
+
168
+
169
+ def _nth_gray(n: int) -> int:
170
+ # Return the nth Gray Code number
171
+ return n ^ (n >> 1)
172
+
173
+
174
+ def _multiplexed_cossin(
175
+ cossin_qubits: 'List[cirq.Qid]', angles: List[float], rot_func: Callable = ops.ry
176
+ ) -> 'op_tree.OpTree':
177
+ """Performs a multiplexed rotation over all qubits in this unitary matrix,
178
+
179
+ Uses ry and rz multiplexing for quantum shannon decomposition
180
+
181
+ Args:
182
+ cossin_qubits: Subset of total qubits involved in this unitary gate
183
+ angles: List of angles to be multiplexed over for the given type of rotation
184
+ rot_func: Rotation function used for this multiplexing implementation
185
+ (cirq.ry or cirq.rz)
186
+
187
+ Calls:
188
+ No major calls
189
+
190
+ Yields: Single operation from OP TREE from set 1- and 2-qubit gates: {ry,rz,CNOT}
191
+ """
192
+ # Most significant qubit is main qubit with rotation function applied
193
+ main_qubit = cossin_qubits[0]
194
+
195
+ # All other qubits are control qubits
196
+ control_qubits = cossin_qubits[1:]
197
+
198
+ for j in range(len(angles)):
199
+ # The rotation includes a factor of (-1) for each bit in the Gray Code
200
+ # if the position of that bit is also 1
201
+ # The number of factors of -1 is counted using the 1s in the
202
+ # binary representation of the (gray(j) & i)
203
+ # Here, i gives the index for the angle, and
204
+ # j is the iteration of the decomposition
205
+ rotation = sum(
206
+ -angle if bin(_nth_gray(j) & i).count('1') % 2 else angle
207
+ for i, angle in enumerate(angles)
208
+ )
209
+
210
+ # Divide by a factor of 2 for each additional select qubit
211
+ # This is due to the halving in the decomposition applied recursively
212
+ rotation = rotation * 2 / len(angles)
213
+
214
+ # The XOR of the this gray code with the next will give the 1 for the bit
215
+ # corresponding to the CNOT select, else 0
216
+ select_string = _nth_gray(j) ^ _nth_gray(j + 1)
217
+
218
+ # Find the index number where the bit is 1
219
+ select_qubit = next(i for i in range(len(angles)) if (select_string >> i & 1))
220
+
221
+ # Negate the value, since we must index starting at most significant qubit
222
+ # Also the final value will overflow, and it should be the MSB,
223
+ # so introduce max function
224
+ select_qubit = max(-select_qubit - 1, -len(control_qubits))
225
+
226
+ # Add a rotation on the main qubit
227
+ yield rot_func(rotation).on(main_qubit)
228
+
229
+ # Add a CNOT from the select qubit to the main qubit
230
+ yield ops.CNOT(control_qubits[select_qubit], main_qubit)