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