1# Copyright 2018 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"""Utility methods related to optimizing quantum circuits."""
16
17import math
18from typing import List, Optional, Tuple, cast
19
20import numpy as np
21import sympy
22
23from cirq import ops, linalg, protocols
24from cirq.linalg.tolerance import near_zero_mod
25
26
27def is_negligible_turn(turns: float, tolerance: float) -> bool:
28    if isinstance(turns, sympy.Basic):
29        if not turns.is_constant():
30            return False
31        turns = float(turns)
32    return abs(_signed_mod_1(turns)) <= tolerance
33
34
35def _signed_mod_1(x: float) -> float:
36    return (x + 0.5) % 1 - 0.5
37
38
39def single_qubit_matrix_to_pauli_rotations(
40    mat: np.ndarray, atol: float = 0
41) -> List[Tuple[ops.Pauli, float]]:
42    """Implements a single-qubit operation with few rotations.
43
44    Args:
45        mat: The 2x2 unitary matrix of the operation to implement.
46        atol: A limit on the amount of absolute error introduced by the
47            construction.
48
49    Returns:
50        A list of (Pauli, half_turns) tuples that, when applied in order,
51        perform the desired operation.
52    """
53
54    def is_clifford_rotation(half_turns):
55        return near_zero_mod(half_turns, 0.5, atol=atol)
56
57    def to_quarter_turns(half_turns):
58        return round(2 * half_turns) % 4
59
60    def is_quarter_turn(half_turns):
61        return is_clifford_rotation(half_turns) and to_quarter_turns(half_turns) % 2 == 1
62
63    def is_half_turn(half_turns):
64        return is_clifford_rotation(half_turns) and to_quarter_turns(half_turns) == 2
65
66    def is_no_turn(half_turns):
67        return is_clifford_rotation(half_turns) and to_quarter_turns(half_turns) == 0
68
69    # Decompose matrix
70    z_rad_before, y_rad, z_rad_after = linalg.deconstruct_single_qubit_matrix_into_angles(mat)
71    z_ht_before = z_rad_before / np.pi - 0.5
72    m_ht = y_rad / np.pi
73    m_pauli = ops.X  # type: ops.Pauli
74    z_ht_after = z_rad_after / np.pi + 0.5
75
76    # Clean up angles
77    if is_clifford_rotation(z_ht_before):
78        if (is_quarter_turn(z_ht_before) or is_quarter_turn(z_ht_after)) ^ (
79            is_half_turn(m_ht) and is_no_turn(z_ht_before - z_ht_after)
80        ):
81            z_ht_before += 0.5
82            z_ht_after -= 0.5
83            m_pauli = ops.Y
84        if is_half_turn(z_ht_before) or is_half_turn(z_ht_after):
85            z_ht_before -= 1
86            z_ht_after += 1
87            m_ht = -m_ht
88    if is_no_turn(m_ht):
89        z_ht_before += z_ht_after
90        z_ht_after = 0
91    elif is_half_turn(m_ht):
92        z_ht_after -= z_ht_before
93        z_ht_before = 0
94
95    # Generate operations
96    rotation_list = [(ops.Z, z_ht_before), (m_pauli, m_ht), (ops.Z, z_ht_after)]
97    return [(pauli, ht) for pauli, ht in rotation_list if not is_no_turn(ht)]
98
99
100def single_qubit_matrix_to_gates(
101    mat: np.ndarray, tolerance: float = 0
102) -> List[ops.SingleQubitGate]:
103    """Implements a single-qubit operation with few gates.
104
105    Args:
106        mat: The 2x2 unitary matrix of the operation to implement.
107        tolerance: A limit on the amount of error introduced by the
108            construction.
109
110    Returns:
111        A list of gates that, when applied in order, perform the desired
112            operation.
113    """
114    rotations = single_qubit_matrix_to_pauli_rotations(mat, tolerance)
115    return [cast(ops.SingleQubitGate, pauli) ** ht for pauli, ht in rotations]
116
117
118def single_qubit_op_to_framed_phase_form(mat: np.ndarray) -> Tuple[np.ndarray, complex, complex]:
119    """Decomposes a 2x2 unitary M into U^-1 * diag(1, r) * U * diag(g, g).
120
121    U translates the rotation axis of M to the Z axis.
122    g fixes a global phase factor difference caused by the translation.
123    r's phase is the amount of rotation around M's rotation axis.
124
125    This decomposition can be used to decompose controlled single-qubit
126    rotations into controlled-Z operations bordered by single-qubit operations.
127
128    Args:
129      mat:  The qubit operation as a 2x2 unitary matrix.
130
131    Returns:
132        A 2x2 unitary U, the complex relative phase factor r, and the complex
133        global phase factor g. Applying M is equivalent (up to global phase) to
134        applying U, rotating around the Z axis to apply r, then un-applying U.
135        When M is controlled, the control must be rotated around the Z axis to
136        apply g.
137    """
138    vals, vecs = linalg.unitary_eig(mat)
139    u = np.conj(vecs).T
140    r = vals[1] / vals[0]
141    g = vals[0]
142    return u, r, g
143
144
145def _deconstruct_single_qubit_matrix_into_gate_turns(mat: np.ndarray) -> Tuple[float, float, float]:
146    """Breaks down a 2x2 unitary into gate parameters.
147
148    Args:
149        mat: The 2x2 unitary matrix to break down.
150
151    Returns:
152       A tuple containing the amount to rotate around an XY axis, the phase of
153       that axis, and the amount to phase around Z. All results will be in
154       fractions of a whole turn, with values canonicalized into the range
155       [-0.5, 0.5).
156    """
157    pre_phase, rotation, post_phase = linalg.deconstruct_single_qubit_matrix_into_angles(mat)
158
159    # Figure out parameters of the actual gates we will do.
160    tau = 2 * np.pi
161    xy_turn = rotation / tau
162    xy_phase_turn = 0.25 - pre_phase / tau
163    total_z_turn = (post_phase + pre_phase) / tau
164
165    # Normalize turns into the range [-0.5, 0.5).
166    return (_signed_mod_1(xy_turn), _signed_mod_1(xy_phase_turn), _signed_mod_1(total_z_turn))
167
168
169def single_qubit_matrix_to_phased_x_z(
170    mat: np.ndarray, atol: float = 0
171) -> List[ops.SingleQubitGate]:
172    """Implements a single-qubit operation with a PhasedX and Z gate.
173
174    If one of the gates isn't needed, it will be omitted.
175
176    Args:
177        mat: The 2x2 unitary matrix of the operation to implement.
178        atol: A limit on the amount of error introduced by the
179            construction.
180
181    Returns:
182        A list of gates that, when applied in order, perform the desired
183            operation.
184    """
185
186    xy_turn, xy_phase_turn, total_z_turn = _deconstruct_single_qubit_matrix_into_gate_turns(mat)
187
188    # Build the intended operation out of non-negligible XY and Z rotations.
189    result = [
190        ops.PhasedXPowGate(exponent=2 * xy_turn, phase_exponent=2 * xy_phase_turn),
191        ops.Z ** (2 * total_z_turn),
192    ]
193    result = [g for g in result if protocols.trace_distance_bound(g) > atol]
194
195    # Special case: XY half-turns can absorb Z rotations.
196    if len(result) == 2 and math.isclose(abs(xy_turn), 0.5, abs_tol=atol):
197        return [ops.PhasedXPowGate(phase_exponent=2 * xy_phase_turn + total_z_turn)]
198
199    return result
200
201
202def single_qubit_matrix_to_phxz(
203    mat: np.ndarray,
204    atol: float = 0,
205) -> Optional[ops.PhasedXZGate]:
206    """Implements a single-qubit operation with a PhasedXZ gate.
207
208    Under the hood, this uses deconstruct_single_qubit_matrix_into_angles which
209    converts the given matrix to a series of three rotations around the Z, Y, Z
210    axes. This is then converted to a phased X rotation followed by a Z, in the
211    form of a single PhasedXZ gate.
212
213    Args:
214        mat: The 2x2 unitary matrix of the operation to implement.
215        atol: A limit on the amount of error introduced by the
216            construction.
217
218    Returns:
219        A PhasedXZ gate that implements the given matrix, or None if it is
220        close to identity (trace distance <= atol).
221    """
222
223    xy_turn, xy_phase_turn, total_z_turn = _deconstruct_single_qubit_matrix_into_gate_turns(mat)
224
225    # Build the intended operation out of non-negligible XY and Z rotations.
226    g = ops.PhasedXZGate(
227        axis_phase_exponent=2 * xy_phase_turn,
228        x_exponent=2 * xy_turn,
229        z_exponent=2 * total_z_turn,
230    )
231
232    if protocols.trace_distance_bound(g) <= atol:
233        return None
234
235    # Special case: XY half-turns can absorb Z rotations.
236    if math.isclose(abs(xy_turn), 0.5, abs_tol=atol):
237        g = ops.PhasedXZGate(
238            axis_phase_exponent=2 * xy_phase_turn + total_z_turn,
239            x_exponent=1,
240            z_exponent=0,
241        )
242
243    return g
244