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