1# Copyright 2019 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. 14import math 15from typing import Iterator, List, Optional 16 17import numpy as np 18import scipy.linalg 19 20import cirq 21import cirq_google as google 22from cirq_google.ops import SycamoreGate 23from cirq_google.optimizers.two_qubit_gates.gate_compilation import GateTabulation 24 25 26UNITARY_ZZ = np.kron(cirq.unitary(cirq.Z), cirq.unitary(cirq.Z)) 27PAULI_OPS = [ 28 np.eye(2), 29 cirq.unitary(cirq.X), 30 cirq.unitary(cirq.Y), 31 cirq.unitary(cirq.Z), 32] 33 34 35class ConvertToSycamoreGates(cirq.PointOptimizer): 36 """Attempts to convert non-native gates into SycamoreGates. 37 38 First, checks if the given operation is already a native sycamore operation. 39 40 Second, checks if the operation has a known unitary. If so, and the gate 41 is a 1-qubit or 2-qubit gate, then performs circuit synthesis of the 42 operation. 43 44 Third, attempts to `cirq.decompose` to the operation. 45 46 Fourth, if ignore_failures is set, gives up and returns the gate unchanged. 47 Otherwise raises a TypeError. 48 """ 49 50 # TODO(#3388) Add documentation for Raises. 51 # pylint: disable=missing-raises-doc 52 def __init__(self, tabulation: Optional[GateTabulation] = None, ignore_failures=False) -> None: 53 """Inits ConvertToSycamoreGates. 54 55 Args: 56 tabulation: If set, a tabulation for the Sycamore gate to use for 57 decomposing Matrix gates. If unset, an analytic calculation is 58 used for Matrix gates. To get a GateTabulation, call the 59 gate_product_tabulation method with a base gate (in this case, 60 usually cirq_google.SYC) and a maximum infidelity. 61 ignore_failures: If set, gates that fail to convert are forwarded 62 unchanged. If not set, conversion failures raise a TypeError. 63 """ 64 super().__init__() 65 self.ignore_failures = ignore_failures 66 if tabulation is not None and not isinstance(tabulation, GateTabulation): 67 raise ValueError("provided tabulation must be of type GateTabulation") 68 self.tabulation = tabulation 69 70 # pylint: enable=missing-raises-doc 71 def _is_native_sycamore_op(self, op: cirq.Operation) -> bool: 72 """Check if the given operation is native to a Sycamore device. 73 74 Args: 75 op: Input operation. 76 77 Returns: 78 True if the operation is native to the gmon, false otherwise. 79 """ 80 gate = op.gate 81 82 if isinstance( 83 gate, 84 ( 85 SycamoreGate, 86 cirq.MeasurementGate, 87 cirq.PhasedXZGate, 88 cirq.PhasedXPowGate, 89 cirq.XPowGate, 90 cirq.YPowGate, 91 cirq.ZPowGate, 92 ), 93 ): 94 return True 95 96 if ( 97 isinstance(gate, cirq.FSimGate) 98 and math.isclose(gate.theta, np.pi / 2) 99 and math.isclose(gate.phi, np.pi / 6) 100 ): 101 return True 102 103 if gate is None and isinstance(op.untagged, cirq.CircuitOperation): 104 subcircuit = op.untagged.circuit 105 return all(self._is_native_sycamore_op(op) for op in subcircuit.all_operations()) 106 107 return False 108 109 # TODO(#3388) Add summary line to docstring. 110 # pylint: disable=docstring-first-line-empty 111 def _convert_one(self, op: cirq.Operation) -> cirq.OP_TREE: 112 """ 113 Decomposer intercept: Upon cirq.protocols.decompose catch and 114 return new OP_Tree 115 116 This should decompose based on number of qubits. 117 """ 118 if len(op.qubits) == 1: 119 return _phased_x_z_ops(cirq.unitary(op, None), op.qubits[0]) 120 elif len(op.qubits) == 2: 121 return known_two_q_operations_to_sycamore_operations( 122 op.qubits[0], op.qubits[1], op, self.tabulation 123 ) 124 return NotImplemented 125 126 # pylint: enable=docstring-first-line-empty 127 def convert(self, op: cirq.Operation) -> List[cirq.Operation]: 128 def on_stuck_raise(bad): 129 return TypeError( 130 "Don't know how to work with {!r}. " 131 "It isn't a native xmon operation, " 132 "a 1 or 2 qubit gate with a known unitary, " 133 "or composite.".format(bad) 134 ) 135 136 return cirq.decompose( 137 op, 138 keep=self._is_native_sycamore_op, 139 intercepting_decomposer=self._convert_one, 140 on_stuck_raise=None if self.ignore_failures else on_stuck_raise, 141 preserve_structure=True, # keep CircuitOps but decompose their contents 142 ) 143 144 def optimization_at( 145 self, circuit: cirq.Circuit, index: int, op: cirq.Operation 146 ) -> Optional[cirq.PointOptimizationSummary]: 147 if op.gate is None and not isinstance(op.untagged, cirq.CircuitOperation): 148 return None 149 150 gate = op.gate 151 152 # Check for a SWAP and ZZPowGate together 153 if isinstance(gate, cirq.ZZPowGate) or gate == cirq.SWAP: 154 gate2 = None 155 rads = None 156 next_index = circuit.next_moment_operating_on(op.qubits, index + 1) 157 if next_index is not None: 158 ops_in_front = list({circuit.operation_at(q, next_index) for q in op.qubits}) 159 if len(ops_in_front) == 1 and ops_in_front[0] is not None: 160 gate2 = ops_in_front[0].gate 161 else: 162 next_index = 0 163 164 if isinstance(gate, cirq.SwapPowGate) and isinstance(gate2, cirq.ZZPowGate): 165 rads = gate2.exponent * np.pi / 2 166 if isinstance(gate, cirq.ZZPowGate) and gate2 == cirq.SWAP: 167 rads = gate.exponent * np.pi / 2 168 if rads is not None: 169 return cirq.PointOptimizationSummary( 170 clear_span=next_index - index + 1, 171 clear_qubits=op.qubits, 172 new_operations=swap_rzz(rads, op.qubits[0], op.qubits[1]), 173 ) 174 175 converted = self.convert(op) 176 if len(converted) == 1 and converted[0] is op: 177 return None 178 179 return cirq.PointOptimizationSummary( 180 clear_span=1, new_operations=converted, clear_qubits=op.qubits 181 ) 182 183 184# TODO(#3388) Add documentation for Raises. 185# pylint: disable=missing-raises-doc 186def known_two_q_operations_to_sycamore_operations( 187 qubit_a: cirq.Qid, 188 qubit_b: cirq.Qid, 189 op: cirq.Operation, 190 tabulation: Optional[GateTabulation] = None, 191) -> cirq.OP_TREE: 192 """Synthesizes a known gate operation to a sycamore operation. 193 194 This function dispatches based on gate type 195 196 Args: 197 qubit_a: first qubit of GateOperation 198 qubit_b: second qubit of GateOperation 199 op: operation to decompose 200 tabulation: A tabulation for the Sycamore gate to use for 201 decomposing gates. 202 Returns: 203 New operations iterable object 204 """ 205 gate = op.gate 206 if isinstance(gate, cirq.PhasedISwapPowGate): 207 if math.isclose(gate.exponent, 1): 208 return decompose_phased_iswap_into_syc(gate.phase_exponent, qubit_a, qubit_b) 209 elif math.isclose(gate.phase_exponent, 0.25): 210 return decompose_phased_iswap_into_syc_precomputed( 211 gate.exponent * np.pi / 2, qubit_a, qubit_b 212 ) 213 else: 214 raise ValueError( 215 "To decompose PhasedISwapPowGate, it must have a phase_exponent" 216 " of .25 OR an exponent of 1.0, but got: {!r}".format(op) 217 ) 218 if isinstance(gate, cirq.CNotPowGate): 219 return [ 220 cirq.Y(qubit_b) ** -0.5, 221 cphase(gate.exponent * np.pi, qubit_a, qubit_b), 222 cirq.Y(qubit_b) ** 0.5, 223 ] 224 elif isinstance(gate, cirq.CZPowGate): 225 if math.isclose(gate.exponent, 1): # check if CZ or CPHASE 226 return decompose_cz_into_syc(qubit_a, qubit_b) 227 else: 228 # because CZPowGate == diag([1, 1, 1, e^{i pi phi}]) 229 return cphase(gate.exponent * np.pi, qubit_a, qubit_b) 230 elif isinstance(gate, cirq.SwapPowGate) and math.isclose(gate.exponent, 1): 231 return decompose_swap_into_syc(qubit_a, qubit_b) 232 elif isinstance(gate, cirq.ISwapPowGate) and math.isclose(gate.exponent, 1): 233 return decompose_iswap_into_syc(qubit_a, qubit_b) 234 elif isinstance(gate, cirq.ZZPowGate): 235 return rzz(gate.exponent * np.pi / 2, *op.qubits) 236 elif cirq.unitary(gate, None) is not None: 237 if tabulation: 238 return decompose_arbitrary_into_syc_tabulation(qubit_a, qubit_b, op, tabulation) 239 else: 240 return decompose_arbitrary_into_syc_analytic(qubit_a, qubit_b, op) 241 else: 242 raise ValueError(f"Unrecognized gate: {op!r}") 243 244 245# pylint: enable=missing-raises-doc 246def decompose_phased_iswap_into_syc( 247 phase_exponent: float, a: cirq.Qid, b: cirq.Qid 248) -> cirq.OP_TREE: 249 """Decompose PhasedISwap with an exponent of 1. 250 251 This should only be called if the Gate has an exponent of 1 - otherwise, 252 decompose_phased_iswap_into_syc_precomputed should be used instead. The 253 advantage of using this function is that the resulting circuit will be 254 smaller. 255 256 Args: 257 phase_exponent: The exponent on the Z gates. 258 a: First qubit id to operate on 259 b: Second qubit id to operate on 260 Returns: 261 a Cirq program implementing the Phased ISWAP gate 262 263 """ 264 265 yield cirq.Z(a) ** phase_exponent, 266 yield cirq.Z(b) ** -phase_exponent, 267 yield decompose_iswap_into_syc(a, b), 268 yield cirq.Z(a) ** -phase_exponent, 269 yield cirq.Z(b) ** phase_exponent, 270 271 272def decompose_phased_iswap_into_syc_precomputed( 273 theta: float, a: cirq.Qid, b: cirq.Qid 274) -> cirq.OP_TREE: 275 """Decompose PhasedISwap into sycamore gates using precomputed coefficients. 276 277 This should only be called if the Gate has a phase_exponent of .25. If the 278 gate has an exponent of 1, decompose_phased_iswap_into_syc should be used 279 instead. Converting PhasedISwap gates to Sycamore is not supported if 280 neither of these constraints are satisfied. 281 282 This synthesize a PhasedISwap in terms of four sycamore gates. This 283 compilation converts the gate into a circuit involving two CZ gates, which 284 themselves are each represented as two Sycamore gates and single-qubit 285 rotations 286 287 Args: 288 theta: rotation parameter 289 a: First qubit id to operate on 290 b: Second qubit id to operate on 291 Returns: 292 a Cirq program implementing the Phased ISWAP gate 293 294 """ 295 296 yield cirq.PhasedXPowGate(phase_exponent=0.41175161497166024, exponent=0.5653807577895922).on(a) 297 yield cirq.PhasedXPowGate(phase_exponent=1.0, exponent=0.5).on(b), 298 yield (cirq.Z ** 0.7099892314883478).on(b), 299 yield (cirq.Z ** 0.6746023442550453).on(a), 300 yield SycamoreGate().on(a, b) 301 yield cirq.PhasedXPowGate(phase_exponent=-0.5154334589432878, exponent=0.5228733015013345).on(b) 302 yield cirq.PhasedXPowGate(phase_exponent=0.06774925307475355).on(a) 303 yield SycamoreGate().on(a, b), 304 yield cirq.PhasedXPowGate(phase_exponent=-0.5987667922766213, exponent=0.4136540654256824).on(a) 305 yield (cirq.Z ** -0.9255092746611595).on(b) 306 yield (cirq.Z ** -1.333333333333333).on(a) 307 yield cirq.rx(-theta).on(a) 308 yield cirq.rx(-theta).on(b) 309 310 yield cirq.PhasedXPowGate(phase_exponent=0.5678998743900456, exponent=0.5863459345743176).on(a) 311 yield cirq.PhasedXPowGate(phase_exponent=0.3549946157441739).on(b) 312 yield SycamoreGate().on(a, b) 313 yield cirq.PhasedXPowGate(phase_exponent=-0.5154334589432878, exponent=0.5228733015013345).on(b) 314 yield cirq.PhasedXPowGate(phase_exponent=0.06774925307475355).on(a) 315 yield SycamoreGate().on(a, b) 316 yield cirq.PhasedXPowGate(phase_exponent=-0.8151665352515929, exponent=0.8906746535691492).on(a) 317 yield cirq.PhasedXPowGate(phase_exponent=-0.07449072533884049, exponent=0.5).on(b) 318 yield (cirq.Z ** -0.9255092746611595).on(b) 319 yield (cirq.Z ** -0.9777346353961884).on(a) 320 321 322def decompose_arbitrary_into_syc_tabulation( 323 qubit_a: cirq.Qid, qubit_b: cirq.Qid, op: cirq.Operation, tabulation: GateTabulation 324) -> cirq.OP_TREE: 325 """Synthesize an arbitrary 2 qubit operation to a sycamore operation using 326 the given Tabulation. 327 328 Args: 329 qubit_a: first qubit of the operation 330 qubit_b: second qubit of the operation 331 op: operation to decompose 332 tabulation: A tabulation for the Sycamore gate. 333 Returns: 334 New operations iterable object 335 """ 336 result = tabulation.compile_two_qubit_gate(cirq.unitary(op)) 337 local_gates = result.local_unitaries 338 for i, (gate_a, gate_b) in enumerate(local_gates): 339 yield from _phased_x_z_ops(gate_a, qubit_a) 340 yield from _phased_x_z_ops(gate_b, qubit_b) 341 if i != len(local_gates) - 1: 342 yield google.SYC.on(qubit_a, qubit_b) 343 344 345def decompose_arbitrary_into_syc_analytic( 346 qubit_a: cirq.Qid, qubit_b: cirq.Qid, op: cirq.Operation 347) -> cirq.OP_TREE: 348 """Synthesize an arbitrary 2 qubit operation to a sycamore operation using 349 the given Tabulation. 350 351 Args: 352 qubit_a: first qubit of the operation 353 qubit_b: second qubit of the operation 354 op: operation to decompose 355 tabulation: A tabulation for the Sycamore gate. 356 Returns: 357 New operations iterable object 358 """ 359 new_ops = cirq.two_qubit_matrix_to_operations(qubit_a, qubit_b, op, allow_partial_czs=True) 360 for new_op in new_ops: 361 num_qubits = len(new_op.qubits) 362 if num_qubits == 1: 363 (a,) = new_op.qubits 364 yield from _phased_x_z_ops(cirq.unitary(new_op), a) 365 elif num_qubits == 2: 366 a, b = op.qubits 367 yield from cirq.flatten_to_ops( 368 known_two_q_operations_to_sycamore_operations(a, b, new_op) 369 ) 370 371 372def decompose_cz_into_syc(a: cirq.Qid, b: cirq.Qid): 373 """Decompose CZ into sycamore gates using precomputed coefficients""" 374 yield cirq.PhasedXPowGate(phase_exponent=0.5678998743900456, exponent=0.5863459345743176).on(a) 375 yield cirq.PhasedXPowGate(phase_exponent=0.3549946157441739).on(b) 376 yield google.SYC.on(a, b) 377 yield cirq.PhasedXPowGate(phase_exponent=-0.5154334589432878, exponent=0.5228733015013345).on(b) 378 yield cirq.PhasedXPowGate(phase_exponent=0.06774925307475355).on(a) 379 yield google.SYC.on(a, b), 380 yield cirq.PhasedXPowGate(phase_exponent=-0.5987667922766213, exponent=0.4136540654256824).on( 381 a 382 ), 383 yield (cirq.Z ** -0.9255092746611595).on(b), 384 yield (cirq.Z ** -1.333333333333333).on(a), 385 386 387def decompose_iswap_into_syc(a: cirq.Qid, b: cirq.Qid): 388 """Decompose ISWAP into sycamore gates using precomputed coefficients""" 389 yield cirq.PhasedXPowGate(phase_exponent=-0.27250925776964596, exponent=0.2893438375555899).on( 390 a 391 ) 392 yield google.SYC.on(a, b) 393 yield cirq.PhasedXPowGate(phase_exponent=0.8487591858680898, exponent=0.9749387200813147).on(b), 394 yield cirq.PhasedXPowGate(phase_exponent=-0.3582574564210601).on(a), 395 yield google.SYC.on(a, b) 396 yield cirq.PhasedXPowGate(phase_exponent=0.9675022326694225, exponent=0.6908986856555526).on(a), 397 yield google.SYC.on(a, b), 398 yield cirq.PhasedXPowGate(phase_exponent=0.9161706861686068, exponent=0.14818318325264102).on( 399 b 400 ), 401 yield cirq.PhasedXPowGate(phase_exponent=0.9408341606787907).on(a), 402 yield (cirq.Z ** -1.1551880579397293).on(b), 403 yield (cirq.Z ** 0.31848343246696365).on(a), 404 405 406def decompose_swap_into_syc(a: cirq.Qid, b: cirq.Qid): 407 """Decompose SWAP into sycamore gates using precomputed coefficients""" 408 yield cirq.PhasedXPowGate(phase_exponent=0.44650378384076217, exponent=0.8817921214052824).on(a) 409 yield cirq.PhasedXPowGate(phase_exponent=-0.7656774060816165, exponent=0.6628666504604785).on(b) 410 yield google.SYC.on(a, b) 411 yield cirq.PhasedXPowGate(phase_exponent=-0.6277589946716742, exponent=0.5659160932099687).on(a) 412 yield google.SYC.on(a, b) 413 yield cirq.PhasedXPowGate(phase_exponent=0.28890767199499257, exponent=0.4340839067900317).on(b) 414 yield cirq.PhasedXPowGate(phase_exponent=-0.22592784059288928).on(a) 415 yield google.SYC.on(a, b) 416 yield cirq.PhasedXPowGate(phase_exponent=-0.4691261557936808, exponent=0.7728525693920243).on(a) 417 yield cirq.PhasedXPowGate(phase_exponent=-0.8150261316932077, exponent=0.11820787859471782).on( 418 b 419 ) 420 yield (cirq.Z ** -0.7384700844660306).on(b) 421 yield (cirq.Z ** -0.7034535141382525).on(a) 422 423 424# TODO(#3388) Add summary line to docstring. 425# pylint: disable=docstring-first-line-empty 426def find_local_equivalents(unitary1: np.ndarray, unitary2: np.ndarray): 427 """ 428 Given two unitaries with the same interaction coefficients but different 429 local unitary rotations determine the local unitaries that turns 430 one type of gate into another. 431 432 1) perform the kak decomp on each unitary and confirm interaction terms 433 are equivalent 434 2) identify the elements of SU(2) to transform unitary2 into unitary1 435 436 Args: 437 unitary1: unitary that we target 438 unitary2: unitary that we transform the local gates to the target 439 Returns: 440 four 2x2 unitaries. first two are pre-rotations last two are post 441 rotations. 442 """ 443 kak_u1 = cirq.kak_decomposition(unitary1) 444 kak_u2 = cirq.kak_decomposition(unitary2) 445 446 u_0 = kak_u1.single_qubit_operations_after[0] @ kak_u2.single_qubit_operations_after[0].conj().T 447 u_1 = kak_u1.single_qubit_operations_after[1] @ kak_u2.single_qubit_operations_after[1].conj().T 448 449 v_0 = ( 450 kak_u2.single_qubit_operations_before[0].conj().T @ kak_u1.single_qubit_operations_before[0] 451 ) 452 v_1 = ( 453 kak_u2.single_qubit_operations_before[1].conj().T @ kak_u1.single_qubit_operations_before[1] 454 ) 455 456 return v_0, v_1, u_0, u_1 457 458 459# pylint: enable=docstring-first-line-empty 460def create_corrected_circuit( 461 target_unitary: np.ndarray, program: cirq.Circuit, q0: cirq.Qid, q1: cirq.Qid 462) -> cirq.OP_TREE: 463 # Get the local equivalents 464 b_0, b_1, a_0, a_1 = find_local_equivalents( 465 target_unitary, program.unitary(qubit_order=cirq.QubitOrder.explicit([q0, q1])) 466 ) 467 468 # Apply initial corrections 469 yield from _phased_x_z_ops(b_0, q0) 470 yield from _phased_x_z_ops(b_1, q1) 471 472 # Apply interaction part 473 yield program 474 475 # Apply final corrections 476 yield from _phased_x_z_ops(a_0, q0) 477 yield from _phased_x_z_ops(a_1, q1) 478 479 480def _phased_x_z_ops(mat: np.ndarray, q: cirq.Qid) -> Iterator[cirq.Operation]: 481 gate = cirq.single_qubit_matrix_to_phxz(mat) 482 if gate: 483 yield gate(q) 484 485 486def rzz(theta: float, q0: cirq.Qid, q1: cirq.Qid) -> cirq.OP_TREE: 487 """Generate exp(-1j * theta * zz) from Sycamore gates. 488 489 Args: 490 theta: rotation parameter 491 q0: First qubit id to operate on 492 q1: Second qubit id to operate on 493 Returns: 494 a Cirq program implementing the Ising gate 495 rtype: 496 cirq.OP_Tree 497 """ 498 phi = -np.pi / 24 499 c_phi = np.cos(2 * phi) 500 target_unitary = scipy.linalg.expm(-1j * theta * UNITARY_ZZ) 501 502 if abs(np.cos(theta)) > c_phi: 503 c2 = abs(np.sin(theta)) / c_phi 504 else: 505 c2 = abs(np.cos(theta)) / c_phi 506 507 # Prepare program that has same Schmidt coeffs as exp(1j theta ZZ) 508 program = cirq.Circuit( 509 google.SYC.on(q0, q1), cirq.rx(2 * np.arccos(c2)).on(q1), google.SYC.on(q0, q1) 510 ) 511 512 yield create_corrected_circuit(target_unitary, program, q0, q1) 513 514 515def cphase(theta: float, q0: cirq.Qid, q1: cirq.Qid) -> cirq.OP_TREE: 516 """Implements a cphase using the Ising gate generated from 2 Sycamore gates. 517 518 A CPHASE gate has the matrix diag([1, 1, 1, exp(1j * theta)]) and can 519 be mapped to the Ising gate by prep and post rotations of Z-pi/4. 520 We drop the global phase shift of theta/4. 521 522 Args: 523 theta: exp(1j * theta ) 524 q0: First qubit id to operate on 525 q1: Second qubit id to operate on 526 returns: 527 a cirq program implementing cphase 528 """ 529 yield rzz(-theta / 4, q0, q1) 530 yield cirq.rz(theta / 2).on(q0) 531 yield cirq.rz(theta / 2).on(q1) 532 533 534def swap_rzz(theta: float, q0: cirq.Qid, q1: cirq.Qid) -> cirq.OP_TREE: 535 """An implementation of SWAP * EXP(1j theta ZZ) using three sycamore gates. 536 537 This builds off of the zztheta method. A sycamore gate following the 538 zz-gate is a SWAP EXP(1j (THETA - pi/24) ZZ). 539 540 Args: 541 theta: exp(1j * theta ) 542 q0: First qubit id to operate on 543 q1: Second qubit id to operate on 544 Returns: 545 The circuit that implements ZZ followed by a swap 546 """ 547 548 # Set interaction part. 549 circuit = cirq.Circuit() 550 angle_offset = np.pi / 24 - np.pi / 4 551 circuit.append(google.SYC(q0, q1)) 552 circuit.append(rzz(theta - angle_offset, q0, q1)) 553 554 # Get the intended circuit. 555 intended_circuit = cirq.Circuit( 556 cirq.SWAP(q0, q1), cirq.ZZPowGate(exponent=2 * theta / np.pi, global_shift=-0.5).on(q0, q1) 557 ) 558 559 yield create_corrected_circuit(intended_circuit, circuit, q0, q1) 560