Exemple #1
0
def _decompose_interaction_into_two_b_gates_ignoring_single_qubit_ops(
    qubits: Sequence['cirq.Qid'], kak_interaction_coefficients: Iterable[float]
) -> List['cirq.Operation']:
    """Decompose using a minimal construction of two-qubit operations.

    References:
        Minimum construction of two-qubit quantum operations
        https://arxiv.org/abs/quant-ph/0312193
    """
    a, b = qubits
    x, y, z = kak_interaction_coefficients
    r = (np.sin(y) * np.cos(z)) ** 2
    r = max(0.0, r)  # Clamp out-of-range floating point error.
    if r > 0.499999999999:
        rb = [
            ops.ry(np.pi).on(b),
        ]
    else:
        b1 = np.cos(y * 2) * np.cos(z * 2) / (1 - 2 * r)
        b1 = max(0.0, min(1, b1))  # Clamp out-of-range floating point error.
        b2 = np.arcsin(np.sqrt(b1))
        b3 = np.arccos(1 - 4 * r)
        rb = [
            ops.rz(-b2).on(b),
            ops.ry(-b3).on(b),
            ops.rz(-b2).on(b),
        ]
    s = 1 if z < 0 else -1
    return [
        _B(a, b),
        ops.ry(s * 2 * x).on(a),
        *rb,
        _B(a, b),
    ]
Exemple #2
0
def cphase_symbols_to_sqrt_iswap(a, b, turns):
    """Version of cphase_to_sqrt_iswap that works with symbols.

    Note that the formulae contained below will need to be flattened
    into a sweep before serializing.
    """
    theta = sympy.Mod(turns, 2.0) * sympy.pi

    # -1 if theta > pi.  Adds a hacky fudge factor so theta=pi is not 0
    sign = sympy.sign(sympy.pi - theta + 1e-9)

    # For sign = 1: theta. For sign = -1, 2pi-theta
    theta_prime = (sympy.pi - sign * sympy.pi) + sign * theta

    phi = sympy.asin(np.sqrt(2) * sympy.sin(theta_prime / 4))
    xi = sympy.atan(sympy.tan(phi) / np.sqrt(2))

    yield ops.rz(sign * 0.5 * theta_prime).on(a)
    yield ops.rz(sign * 0.5 * theta_prime).on(b)
    yield ops.rx(xi).on(a)
    yield ops.X(b)**(-sign * 0.5)
    yield SQRT_ISWAP_INV(a, b)
    yield ops.rx(-2 * phi).on(a)
    yield SQRT_ISWAP(a, b)
    yield ops.rx(xi).on(a)
    yield ops.X(b)**(sign * 0.5)
Exemple #3
0
def _decompose_abc(
        matrix: np.ndarray
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, float]:
    """Decomposes 2x2 unitary matrix.

    Returns 2x2 special unitary matrices A, B, C and phase delta, such that:
    * ABC = I.
    * AXBXC * exp(1j*delta) = matrix.

    See [1], chapter 4.
    """
    assert matrix.shape == (2, 2)
    delta = np.angle(np.linalg.det(matrix)) * 0.5
    alpha = np.angle(matrix[0, 0]) + np.angle(matrix[0, 1]) - 2 * delta
    beta = np.angle(matrix[0, 0]) - np.angle(matrix[0, 1])

    m00_abs = np.abs(matrix[0, 0])
    if np.abs(m00_abs - 1.0) < 1e-9:
        m00_abs = 1
    theta = 2 * np.arccos(m00_abs)

    a = unitary(ops.rz(-alpha)) @ unitary(ops.ry(-theta / 2))
    b = unitary(ops.ry(theta / 2)) @ unitary(ops.rz((alpha + beta) / 2))
    c = unitary(ops.rz((alpha - beta) / 2))

    x = unitary(ops.X)
    assert np.allclose(a @ b @ c, np.eye(2), atol=1e-2)
    assert np.allclose((a @ x @ b @ x @ c) * np.exp(1j * delta),
                       matrix,
                       atol=1e-2)

    return a, b, c, delta
Exemple #4
0
def _decompose_interaction_into_two_b_gates_ignoring_single_qubit_ops(
        qubits: Sequence['cirq.Qid'],
        kak_interaction_coefficients: Iterable[float]
) -> List['cirq.Operation']:
    """
    References:
        Minimum construction of two-qubit quantum operations
        https://arxiv.org/abs/quant-ph/0312193
    """
    a, b = qubits
    x, y, z = kak_interaction_coefficients
    r = (np.sin(y) * np.cos(z))**2
    r = max(0.0, min(0.5, r))  # Clamp out-of-range floating point error.
    b1 = np.arccos(1 - 4 * r)
    a2 = np.cos(y * 2) * np.cos(z * 2) / (1 - 2 * r)
    a2 = max(0.0, min(1, a2))  # Clamp out-of-range floating point error.
    b2 = np.arcsin(np.sqrt(a2))
    s = 1 if z < 0 else -1
    return [
        _B(a, b),
        ops.ry(s * 2 * x).on(a),
        ops.rz(b2).on(b),
        ops.ry(b1).on(b),
        ops.rz(b2).on(b),
        _B(a, b),
    ]
Exemple #5
0
 def _decompose_(self, qubits):
     q = qubits[0]
     return [
         ops.rz(self.lmda * np.pi).on(q),
         ops.ry(self.theta * np.pi).on(q),
         ops.rz(self.phi * np.pi).on(q),
     ]
Exemple #6
0
def test_from_braket_parameterized_single_qubit_gates(qubit_index):
    braket_circuit = BKCircuit()
    pgates = [
        braket_gates.Rx,
        braket_gates.Ry,
        braket_gates.Rz,
        braket_gates.PhaseShift,
    ]
    angles = np.random.RandomState(11).random(len(pgates))
    instructions = [
        Instruction(rot(a), target=qubit_index)
        for rot, a in zip(pgates, angles)
    ]
    for instr in instructions:
        braket_circuit.add_instruction(instr)
    cirq_circuit = from_braket(braket_circuit)

    for i, op in enumerate(cirq_circuit.all_operations()):
        assert np.allclose(instructions[i].operator.to_matrix(),
                           protocols.unitary(op))

    qubit = LineQubit(qubit_index)
    expected_cirq_circuit = Circuit(
        ops.rx(angles[0]).on(qubit),
        ops.ry(angles[1]).on(qubit),
        ops.rz(angles[2]).on(qubit),
        ops.Z.on(qubit)**(angles[3] / np.pi),
    )
    assert _equal(cirq_circuit,
                  expected_cirq_circuit,
                  require_qubit_equality=True)
Exemple #7
0
def _cphase_symbols_to_sqrt_iswap(a: 'cirq.Qid',
                                  b: 'cirq.Qid',
                                  turns: 'cirq.TParamVal',
                                  use_sqrt_iswap_inv: bool = True):
    """Implements `cirq.CZ(a, b) ** turns` using two √iSWAPs and single qubit rotations.

    Output unitary:
        [[1, 0, 0, 0],
         [0, 1, 0, 0],
         [0, 0, 1, 0],
         [0, 0, 0, g]]
    where:
        g = exp(i·π·t).

    Args:
        a: The first qubit.
        b: The second qubit.
        turns: The rotational angle (t) that specifies the gate, where
            g = exp(i·π·t/2).
        use_sqrt_iswap_inv: If True, `cirq.SQRT_ISWAP_INV` is used instead of `cirq.SQRT_ISWAP`.

    Yields:
        A `cirq.OP_TREE` representing the decomposition.
    """
    theta = sympy.Mod(turns, 2.0) * sympy.pi

    # -1 if theta > pi.  Adds a hacky fudge factor so theta=pi is not 0
    sign = sympy.sign(sympy.pi - theta + 1e-9)

    # For sign = 1: theta. For sign = -1, 2pi-theta
    theta_prime = (sympy.pi - sign * sympy.pi) + sign * theta

    phi = sympy.asin(np.sqrt(2) * sympy.sin(theta_prime / 4))
    xi = sympy.atan(sympy.tan(phi) / np.sqrt(2))

    yield ops.rz(sign * 0.5 * theta_prime).on(a)
    yield ops.rz(sign * 0.5 * theta_prime).on(b)
    yield ops.rx(xi).on(a)
    yield ops.X(b)**(-sign * 0.5)
    yield _sqrt_iswap_inv(a, b, use_sqrt_iswap_inv)
    yield ops.rx(-2 * phi).on(a)
    yield ops.Z(a)
    yield _sqrt_iswap_inv(a, b, use_sqrt_iswap_inv)
    yield ops.Z(a)
    yield ops.rx(xi).on(a)
    yield ops.X(b)**(sign * 0.5)
def cphase(theta: float, q0: ops.Qid, q1: ops.Qid) -> ops.OP_TREE:
    """
    Implement a cphase using the Ising gate generated from 2 Sycamore gates

    A CPHASE gate has the matrix diag([1, 1, 1, exp(1j * theta)]) and can
    be mapped to the Ising gate by prep and post rotations of Z-pi/4.
    We drop the global phase shift of theta/4.

    Args:
        theta: exp(1j * theta )
        q0: First qubit id to operate on
        q1: Second qubit id to operate on
    returns:
        a cirq program implementing cphase
    """
    yield rzz(-theta / 4, q0, q1)
    yield ops.rz(theta / 2).on(q0)
    yield ops.rz(theta / 2).on(q1)
Exemple #9
0
def cphase_to_sqrt_iswap(a, b, turns):
    """Implement a C-Phase gate using two sqrt ISWAP gates and single-qubit
    operations. The circuit is equivalent to cirq.CZPowGate(exponent=turns).

    Output unitary:
    [1   0   0   0],
    [0   1   0   0],
    [0   0   1   0],
    [0   0   0   e^{i turns pi}].

    Args:
        a: the first qubit
        b: the second qubit
        turns: Exponent specifying the evolution time in number of rotations.
    """
    theta = (turns % 2) * np.pi
    if 0 <= theta <= np.pi:
        sign = 1.
        theta_prime = theta
    elif np.pi < theta < 2 * np.pi:
        sign = -1.
        theta_prime = 2 * np.pi - theta

    if np.isclose(theta, np.pi):
        # If we are close to pi, just set values manually to avoid possible
        # numerical errors with arcsin of greater than 1.0 (Ahem, Windows).
        phi = np.pi / 2
        xi = np.pi / 2
    else:
        phi = np.arcsin(np.sqrt(2) * np.sin(theta_prime / 4))
        xi = np.arctan(np.tan(phi) / np.sqrt(2))

    yield ops.rz(sign * 0.5 * theta_prime).on(a)
    yield ops.rz(sign * 0.5 * theta_prime).on(b)
    yield ops.rx(xi).on(a)
    yield ops.X(b)**(-sign * 0.5)
    yield SQRT_ISWAP_INV(a, b)
    yield ops.rx(-2 * phi).on(a)
    yield SQRT_ISWAP(a, b)

    yield ops.rx(xi).on(a)
    yield ops.X(b)**(sign * 0.5)
    # Corrects global phase
    yield ops.GlobalPhaseOperation(np.exp(sign * theta_prime * 0.25j))
Exemple #10
0
    def with_zeta_chi_gamma_compensated(
        self,
        qubits: Tuple[Qid, Qid],
        parameters: PhasedFSimCharacterization,
        *,
        engine_gate: Optional[TwoQubitGate] = None,
    ) -> Tuple[Tuple[Operation, ...], ...]:
        """Creates a composite operation that compensates for zeta, chi and gamma angles of the
        characterization.

        Args:
            qubits: Qubits that the gate should act on.
            parameters: The results of characterization of the engine gate.
            engine_gate: TwoQubitGate that represents the engine gate. When None, the internal
                engine_gate of this instance is used. This argument is useful for testing
                purposes.

        Returns:
            Tuple of tuple of operations that describe the compensated gate. The first index
            iterates over moments of the composed operation.
        """
        assert parameters.zeta is not None, "Zeta value must not be None"
        zeta = parameters.zeta

        assert parameters.gamma is not None, "Gamma value must not be None"
        gamma = parameters.gamma

        assert parameters.chi is not None, "Chi value must not be None"
        chi = parameters.chi + 2 * np.pi * self.phase_exponent

        if engine_gate is None:
            engine_gate = self.engine_gate

        a, b = qubits

        alpha = 0.5 * (zeta + chi)
        beta = 0.5 * (zeta - chi)

        return (
            (rz(0.5 * gamma - alpha).on(a), rz(0.5 * gamma + alpha).on(b)),
            (engine_gate.on(a, b),),
            (rz(0.5 * gamma - beta).on(a), rz(0.5 * gamma + beta).on(b)),
        )
Exemple #11
0
def _decompose_xx_yy_into_two_fsims_ignoring_single_qubit_ops(
    *,
    qubits: Sequence['cirq.Qid'],
    fsim_gate: 'cirq.FSimGate',
    canonical_x_kak_coefficient: float,
    canonical_y_kak_coefficient: float,
    atol: float = 1e-8,
) -> List['cirq.Operation']:
    x = canonical_x_kak_coefficient
    y = canonical_y_kak_coefficient
    assert 0 <= y <= x <= np.pi / 4

    eta = np.sin(x) ** 2 * np.cos(y) ** 2 + np.cos(x) ** 2 * np.sin(y) ** 2
    xi = abs(np.sin(2 * x) * np.sin(2 * y))

    t = fsim_gate.phi / 2
    kappa = np.sin(fsim_gate.theta) ** 2 - np.sin(t) ** 2
    s_sum = (eta - np.sin(t) ** 2) / kappa
    s_dif = 0.5 * xi / kappa

    a_dif = _sticky_0_to_1(s_sum + s_dif, atol=atol)
    a_sum = _sticky_0_to_1(s_sum - s_dif, atol=atol)
    if a_dif is None or a_sum is None:
        raise ValueError(
            f'Failed to synthesize XX^{x/np.pi}·YY^{y/np.pi} from two '
            f'{fsim_gate!r} separated by single qubit operations.'
        )

    x_dif = np.arcsin(np.sqrt(a_dif))
    x_sum = np.arcsin(np.sqrt(a_sum))

    x_a = x_sum + x_dif
    x_b = x_dif - x_sum

    a, b = qubits
    return [
        fsim_gate(a, b),
        ops.rz(t + np.pi).on(a),
        ops.rz(t).on(b),
        ops.rx(x_a).on(a),
        ops.rx(x_b).on(b),
        fsim_gate(a, b),
    ]
Exemple #12
0
    def operation_decomposer(
            self, op: ops.Operation) -> Optional[list[ops.Operation]]:
        # Decomposes CNOT and the CZPowGate family to Valkmusa native gates.
        # All the decompositions below keep track of global phase (required for decomposing controlled gates).

        if isinstance(op.gate, ops.CXPowGate) and op.gate.exponent == 1.0:
            # CNOT is a special case, we decompose it using iSWAPs to be able to commute Z rotations through
            control_qubit = op.qubits[0]
            target_qubit = op.qubits[1]
            s = op.gate.global_shift
            iSWAP = ops.ISwapPowGate(exponent=1, global_shift=(s + 0.25) / 2)
            return [
                Lx.on(target_qubit),
                Lzi.on(control_qubit),
                Lz.on(target_qubit),
                iSWAP.on(*op.qubits),
                Lx.on(control_qubit),
                iSWAP.on(*op.qubits),
                Lz.on(target_qubit),
            ]
        if isinstance(op.gate, ops.CZPowGate):
            # decompose CZPowGate using ZZPowGate
            t = op.gate.exponent
            s = op.gate.global_shift
            L = ops.rz(t / 2 * PI)
            return [
                ops.ZZPowGate(exponent=-0.5 * t,
                              global_shift=-2 * s - 1).on(*op.qubits),
                L.on(op.qubits[0]),
                L.on(op.qubits[1]),
            ]
        if isinstance(op.gate, ops.ZZPowGate):
            # ZZPowGate is decomposed using two applications of the XY interaction
            t = op.gate.exponent
            s = op.gate.global_shift
            XY = ops.ISwapPowGate(exponent=-t, global_shift=-(s + 0.5) / 2)
            return [
                Lyi.on(op.qubits[0]),
                Lyi.on(op.qubits[1]),
                XY.on(*op.qubits),
                ops.XPowGate(exponent=-1, global_shift=-0.5).on(op.qubits[0]),
                XY.on(*op.qubits),
                ops.XPowGate(exponent=1, global_shift=-0.5).on(op.qubits[0]),
                Ly.on(op.qubits[0]),
                Ly.on(op.qubits[1]),
            ]
        if isinstance(op.gate, ops.ZPowGate):
            # Rz using Rx, Ry
            q = op.qubits[0]
            return [
                ops.XPowGate(exponent=-0.5).on(q),
                ops.YPowGate(exponent=op.gate.exponent).on(q),
                ops.XPowGate(exponent=0.5).on(q),
            ]
        return None
Exemple #13
0
def _translate_one_qubit_braket_instruction_to_cirq_operation(
    instr: Instruction, ) -> List["cirq.Operation"]:
    """Converts the one-qubit braket instruction to Cirq.

    Args:
        instr: One-qubit Braket instruction to convert.

    Raises:
        ValueError: If the instruction cannot be converted to Cirq.
    """
    qubits = [LineQubit(int(qubit)) for qubit in instr.target]
    gate = instr.operator

    # One-qubit non-parameterized gates.
    if isinstance(gate, braket_gates.I):
        return [cirq_ops.I.on(*qubits)]
    elif isinstance(gate, braket_gates.X):
        return [cirq_ops.X.on(*qubits)]
    elif isinstance(gate, braket_gates.Y):
        return [cirq_ops.Y.on(*qubits)]
    elif isinstance(gate, braket_gates.Z):
        return [cirq_ops.Z.on(*qubits)]
    elif isinstance(gate, braket_gates.H):
        return [cirq_ops.H.on(*qubits)]
    elif isinstance(gate, braket_gates.S):
        return [cirq_ops.S.on(*qubits)]
    elif isinstance(gate, braket_gates.Si):
        return [cirq_ops.S.on(*qubits)**-1.0]
    elif isinstance(gate, braket_gates.T):
        return [cirq_ops.T.on(*qubits)]
    elif isinstance(gate, braket_gates.Ti):
        return [cirq_ops.T.on(*qubits)**-1.0]
    elif isinstance(gate, braket_gates.V):
        return [cirq_ops.X.on(*qubits)**0.5]
    elif isinstance(gate, braket_gates.Vi):
        return [cirq_ops.X.on(*qubits)**-0.5]

    # One-qubit parameterized gates.
    elif isinstance(gate, braket_gates.Rx):
        return [cirq_ops.rx(gate.angle).on(*qubits)]
    elif isinstance(gate, braket_gates.Ry):
        return [cirq_ops.ry(gate.angle).on(*qubits)]
    elif isinstance(gate, braket_gates.Rz):
        return [cirq_ops.rz(gate.angle).on(*qubits)]
    elif isinstance(gate, braket_gates.PhaseShift):
        return [cirq_ops.Z.on(*qubits)**(gate.angle / np.pi)]

    else:
        raise ValueError(
            f"Unable to convert the instruction {instr} to Cirq. If you think "
            "this is a bug, you can open an issue on the Mitiq GitHub at "
            "https://github.com/unitaryfund/mitiq.")
Exemple #14
0
def _translate_one_qubit_braket_instruction_to_cirq_operation(
    instr: Instruction,
) -> List[cirq_ops.Operation]:
    """Converts the one-qubit braket instruction to Cirq.

    Args:
        instr: One-qubit Braket instruction to convert.

    Raises:
        ValueError: If the instruction cannot be converted to Cirq.
    """
    qubits = [LineQubit(int(qubit)) for qubit in instr.target]
    gate = instr.operator

    # One-qubit non-parameterized gates.
    if isinstance(gate, braket_gates.I):
        return [cirq_ops.I.on(*qubits)]
    elif isinstance(gate, braket_gates.X):
        return [cirq_ops.X.on(*qubits)]
    elif isinstance(gate, braket_gates.Y):
        return [cirq_ops.Y.on(*qubits)]
    elif isinstance(gate, braket_gates.Z):
        return [cirq_ops.Z.on(*qubits)]
    elif isinstance(gate, braket_gates.H):
        return [cirq_ops.H.on(*qubits)]
    elif isinstance(gate, braket_gates.S):
        return [cirq_ops.S.on(*qubits)]
    elif isinstance(gate, braket_gates.Si):
        return [protocols.inverse(cirq_ops.S.on(*qubits))]
    elif isinstance(gate, braket_gates.T):
        return [cirq_ops.T.on(*qubits)]
    elif isinstance(gate, braket_gates.Ti):
        return [protocols.inverse(cirq_ops.T.on(*qubits))]
    elif isinstance(gate, braket_gates.V):
        return [cirq_ops.X.on(*qubits) ** 0.5]
    elif isinstance(gate, braket_gates.Vi):
        return [cirq_ops.X.on(*qubits) ** -0.5]

    # One-qubit parameterized gates.
    elif isinstance(gate, braket_gates.Rx):
        return [cirq_ops.rx(gate.angle).on(*qubits)]
    elif isinstance(gate, braket_gates.Ry):
        return [cirq_ops.ry(gate.angle).on(*qubits)]
    elif isinstance(gate, braket_gates.Rz):
        return [cirq_ops.rz(gate.angle).on(*qubits)]
    elif isinstance(gate, braket_gates.PhaseShift):
        return [cirq_ops.Z.on(*qubits) ** (gate.angle / np.pi)]

    else:
        _raise_braket_to_cirq_error(instr)

    return None  # type: ignore[return-value]  # pragma: no cover
Exemple #15
0
    def from_characterization(
        cls,
        qubits: Tuple[Qid, Qid],
        gate: FSimGate,
        parameters: PhasedFSimCharacterization,
        characterization_index: Optional[int],
    ) -> 'FSimPhaseCorrections':
        """Creates an operation that compensates for zeta, chi and gamma angles of the supplied
        gate and characterization.

        Args:
            qubits: Qubits that the gate should act on.
            gate: Original, imperfect gate that is supposed to run on the hardware.
            parameters: The real parameters of the supplied gate.
            characterization_index: characterization index to use at each moment with gate.
        """
        assert parameters.zeta is not None, "Zeta value must not be None"
        zeta = parameters.zeta

        assert parameters.gamma is not None, "Gamma value must not be None"
        gamma = parameters.gamma

        assert parameters.chi is not None, "Chi value must not be None"
        chi = parameters.chi

        a, b = qubits
        alpha = 0.5 * (zeta + chi)
        beta = 0.5 * (zeta - chi)

        operations = (
            (rz(0.5 * gamma - alpha).on(a), rz(0.5 * gamma + alpha).on(b)),
            (gate.on(a, b),),
            (rz(0.5 * gamma - beta).on(a), rz(0.5 * gamma + beta).on(b)),
        )

        moment_to_calibration = [None, characterization_index, None]

        return cls(operations, moment_to_calibration)
Exemple #16
0
def test_circuit_from_quil():
    q0, q1, q2 = LineQubit.range(3)
    cirq_circuit = Circuit([
        I(q0),
        I(q1),
        I(q2),
        X(q0),
        Y(q1),
        Z(q2),
        H(q0),
        S(q1),
        T(q2),
        Z(q0)**(1 / 8),
        Z(q1)**(1 / 8),
        Z(q2)**(1 / 8),
        rx(np.pi / 2)(q0),
        ry(np.pi / 2)(q1),
        rz(np.pi / 2)(q2),
        CZ(q0, q1),
        CNOT(q1, q2),
        cphase(np.pi / 2)(q0, q1),
        cphase00(np.pi / 2)(q1, q2),
        cphase01(np.pi / 2)(q0, q1),
        cphase10(np.pi / 2)(q1, q2),
        ISWAP(q0, q1),
        pswap(np.pi / 2)(q1, q2),
        SWAP(q0, q1),
        xy(np.pi / 2)(q1, q2),
        CCNOT(q0, q1, q2),
        CSWAP(q0, q1, q2),
        MeasurementGate(1, key="ro[0]")(q0),
        MeasurementGate(1, key="ro[1]")(q1),
        MeasurementGate(1, key="ro[2]")(q2),
    ])
    # build the same Circuit, using Quil
    quil_circuit = circuit_from_quil(QUIL_PROGRAM)
    # test Circuit equivalence
    assert cirq_circuit == quil_circuit

    pyquil_circuit = Program(QUIL_PROGRAM)
    # drop declare and measures, get Program unitary
    pyquil_unitary = program_unitary(pyquil_circuit[1:-3], n_qubits=3)
    # fix qubit order convention
    cirq_circuit_swapped = Circuit(SWAP(q0, q2), cirq_circuit[:-1],
                                   SWAP(q0, q2))
    # get Circuit unitary
    cirq_unitary = cirq_circuit_swapped.unitary()
    # test unitary equivalence
    assert np.isclose(pyquil_unitary, cirq_unitary).all()
Exemple #17
0
"""
from math import pi as PI
from typing import Optional

from cirq import ops

from .iqm_device import IQMDevice

PI_2 = PI / 2

# common gates used in gate decompositions
Lx = ops.rx(PI_2)
Lxi = ops.rx(-PI_2)
Ly = ops.ry(PI_2)
Lyi = ops.ry(-PI_2)
Lz = ops.rz(PI_2)
Lzi = ops.rz(-PI_2)


class Valkmusa(IQMDevice):
    """IQM's two-qubit transmon device.

    The qubits are connected thus::

      QB1 - QB2

    Each qubit can be rotated about any axis in the xy plane by an arbitrary angle.
    The native two qubit-gate is ISwapPowGate.
    The qubits are always measured simultaneously at the end of the computation.
    """
def generate_all_single_qubit_rotation_cell_makers() -> Iterator[CellMaker]:

    # Fixed single qubit rotations.
    yield _gate("H", ops.H)
    yield _gate("X", ops.X)
    yield _gate("Y", ops.Y)
    yield _gate("Z", ops.Z)
    yield _gate("X^½", ops.X**(1 / 2))
    yield _gate("X^⅓", ops.X**(1 / 3))
    yield _gate("X^¼", ops.X**(1 / 4))
    yield _gate("X^⅛", ops.X**(1 / 8))
    yield _gate("X^⅟₁₆", ops.X**(1 / 16))
    yield _gate("X^⅟₃₂", ops.X**(1 / 32))
    yield _gate("X^-½", ops.X**(-1 / 2))
    yield _gate("X^-⅓", ops.X**(-1 / 3))
    yield _gate("X^-¼", ops.X**(-1 / 4))
    yield _gate("X^-⅛", ops.X**(-1 / 8))
    yield _gate("X^-⅟₁₆", ops.X**(-1 / 16))
    yield _gate("X^-⅟₃₂", ops.X**(-1 / 32))
    yield _gate("Y^½", ops.Y**(1 / 2))
    yield _gate("Y^⅓", ops.Y**(1 / 3))
    yield _gate("Y^¼", ops.Y**(1 / 4))
    yield _gate("Y^⅛", ops.Y**(1 / 8))
    yield _gate("Y^⅟₁₆", ops.Y**(1 / 16))
    yield _gate("Y^⅟₃₂", ops.Y**(1 / 32))
    yield _gate("Y^-½", ops.Y**(-1 / 2))
    yield _gate("Y^-⅓", ops.Y**(-1 / 3))
    yield _gate("Y^-¼", ops.Y**(-1 / 4))
    yield _gate("Y^-⅛", ops.Y**(-1 / 8))
    yield _gate("Y^-⅟₁₆", ops.Y**(-1 / 16))
    yield _gate("Y^-⅟₃₂", ops.Y**(-1 / 32))
    yield _gate("Z^½", ops.Z**(1 / 2))
    yield _gate("Z^⅓", ops.Z**(1 / 3))
    yield _gate("Z^¼", ops.Z**(1 / 4))
    yield _gate("Z^⅛", ops.Z**(1 / 8))
    yield _gate("Z^⅟₁₆", ops.Z**(1 / 16))
    yield _gate("Z^⅟₃₂", ops.Z**(1 / 32))
    yield _gate("Z^⅟₆₄", ops.Z**(1 / 64))
    yield _gate("Z^⅟₁₂₈", ops.Z**(1 / 128))
    yield _gate("Z^-½", ops.Z**(-1 / 2))
    yield _gate("Z^-⅓", ops.Z**(-1 / 3))
    yield _gate("Z^-¼", ops.Z**(-1 / 4))
    yield _gate("Z^-⅛", ops.Z**(-1 / 8))
    yield _gate("Z^-⅟₁₆", ops.Z**(-1 / 16))

    # Dynamic single qubit rotations.
    yield _gate("X^t", ops.X**sympy.Symbol('t'))
    yield _gate("Y^t", ops.Y**sympy.Symbol('t'))
    yield _gate("Z^t", ops.Z**sympy.Symbol('t'))
    yield _gate("X^-t", ops.X**-sympy.Symbol('t'))
    yield _gate("Y^-t", ops.Y**-sympy.Symbol('t'))
    yield _gate("Z^-t", ops.Z**-sympy.Symbol('t'))
    yield _gate("e^iXt", ops.rx(2 * sympy.pi * sympy.Symbol('t')))
    yield _gate("e^iYt", ops.ry(2 * sympy.pi * sympy.Symbol('t')))
    yield _gate("e^iZt", ops.rz(2 * sympy.pi * sympy.Symbol('t')))
    yield _gate("e^-iXt", ops.rx(-2 * sympy.pi * sympy.Symbol('t')))
    yield _gate("e^-iYt", ops.ry(-2 * sympy.pi * sympy.Symbol('t')))
    yield _gate("e^-iZt", ops.rz(-2 * sympy.pi * sympy.Symbol('t')))

    # Formulaic single qubit rotations.
    yield _formula_gate("X^ft", "sin(pi*t)", lambda e: ops.X**e)
    yield _formula_gate("Y^ft", "sin(pi*t)", lambda e: ops.Y**e)
    yield _formula_gate("Z^ft", "sin(pi*t)", lambda e: ops.Z**e)
    yield _formula_gate("Rxft", "pi*t*t", ops.rx)
    yield _formula_gate("Ryft", "pi*t*t", ops.ry)
    yield _formula_gate("Rzft", "pi*t*t", ops.rz)
class QasmParser:
    """Parser for QASM strings.

    Example:

        qasm = "OPENQASM 2.0; qreg q1[2]; CX q1[0], q1[1];"
        parsedQasm = QasmParser().parse(qasm)
    """
    def __init__(self):
        self.parser = yacc.yacc(module=self, debug=False, write_tables=False)
        self.circuit = Circuit()
        self.qregs: Dict[str, int] = {}
        self.cregs: Dict[str, int] = {}
        self.qelibinc = False
        self.lexer = QasmLexer()
        self.supported_format = False
        self.parsedQasm: Optional[Qasm] = None
        self.qubits: Dict[str, ops.Qid] = {}
        self.functions = {
            'sin': np.sin,
            'cos': np.cos,
            'tan': np.tan,
            'exp': np.exp,
            'ln': np.log,
            'sqrt': np.sqrt,
            'acos': np.arccos,
            'atan': np.arctan,
            'asin': np.arcsin,
        }

        self.binary_operators = {
            '+': operator.add,
            '-': operator.sub,
            '*': operator.mul,
            '/': operator.truediv,
            '^': operator.pow,
        }

    basic_gates: Dict[str, QasmGateStatement] = {
        'CX':
        QasmGateStatement(qasm_gate='CX',
                          cirq_gate=CX,
                          num_params=0,
                          num_args=2),
        'U':
        QasmGateStatement(
            qasm_gate='U',
            num_params=3,
            num_args=1,
            # QasmUGate expects half turns
            cirq_gate=(lambda params: QasmUGate(*[p / np.pi for p in params])),
        ),
    }

    qelib_gates = {
        'rx':
        QasmGateStatement(qasm_gate='rx',
                          cirq_gate=(lambda params: ops.rx(params[0])),
                          num_params=1,
                          num_args=1),
        'ry':
        QasmGateStatement(qasm_gate='ry',
                          cirq_gate=(lambda params: ops.ry(params[0])),
                          num_params=1,
                          num_args=1),
        'rz':
        QasmGateStatement(qasm_gate='rz',
                          cirq_gate=(lambda params: ops.rz(params[0])),
                          num_params=1,
                          num_args=1),
        'id':
        QasmGateStatement(qasm_gate='id',
                          cirq_gate=ops.IdentityGate(1),
                          num_params=0,
                          num_args=1),
        'u1':
        QasmGateStatement(
            qasm_gate='u1',
            cirq_gate=(lambda params: QasmUGate(0, 0, params[0] / np.pi)),
            num_params=1,
            num_args=1,
        ),
        'u2':
        QasmGateStatement(
            qasm_gate='u2',
            cirq_gate=(lambda params: QasmUGate(0.5, params[0] / np.pi, params[
                1] / np.pi)),
            num_params=2,
            num_args=1,
        ),
        'u3':
        QasmGateStatement(
            qasm_gate='u3',
            num_params=3,
            num_args=1,
            cirq_gate=(lambda params: QasmUGate(*[p / np.pi for p in params])),
        ),
        'x':
        QasmGateStatement(qasm_gate='x',
                          num_params=0,
                          num_args=1,
                          cirq_gate=ops.X),
        'y':
        QasmGateStatement(qasm_gate='y',
                          num_params=0,
                          num_args=1,
                          cirq_gate=ops.Y),
        'z':
        QasmGateStatement(qasm_gate='z',
                          num_params=0,
                          num_args=1,
                          cirq_gate=ops.Z),
        'h':
        QasmGateStatement(qasm_gate='h',
                          num_params=0,
                          num_args=1,
                          cirq_gate=ops.H),
        's':
        QasmGateStatement(qasm_gate='s',
                          num_params=0,
                          num_args=1,
                          cirq_gate=ops.S),
        't':
        QasmGateStatement(qasm_gate='t',
                          num_params=0,
                          num_args=1,
                          cirq_gate=ops.T),
        'cx':
        QasmGateStatement(qasm_gate='cx',
                          cirq_gate=CX,
                          num_params=0,
                          num_args=2),
        'cy':
        QasmGateStatement(qasm_gate='cy',
                          cirq_gate=ops.ControlledGate(ops.Y),
                          num_params=0,
                          num_args=2),
        'cz':
        QasmGateStatement(qasm_gate='cz',
                          cirq_gate=ops.CZ,
                          num_params=0,
                          num_args=2),
        'ch':
        QasmGateStatement(qasm_gate='ch',
                          cirq_gate=ops.ControlledGate(ops.H),
                          num_params=0,
                          num_args=2),
        'swap':
        QasmGateStatement(qasm_gate='swap',
                          cirq_gate=ops.SWAP,
                          num_params=0,
                          num_args=2),
        'cswap':
        QasmGateStatement(qasm_gate='cswap',
                          num_params=0,
                          num_args=3,
                          cirq_gate=ops.CSWAP),
        'ccx':
        QasmGateStatement(qasm_gate='ccx',
                          num_params=0,
                          num_args=3,
                          cirq_gate=ops.CCX),
        'sdg':
        QasmGateStatement(qasm_gate='sdg',
                          num_params=0,
                          num_args=1,
                          cirq_gate=ops.S**-1),
        'tdg':
        QasmGateStatement(qasm_gate='tdg',
                          num_params=0,
                          num_args=1,
                          cirq_gate=ops.T**-1),
    }

    all_gates = {**basic_gates, **qelib_gates}

    tokens = QasmLexer.tokens
    start = 'start'

    precedence = (
        ('left', '+', '-'),
        ('left', '*', '/'),
        ('right', '^'),
    )

    def p_start(self, p):
        """start : qasm"""
        p[0] = p[1]

    def p_qasm_format_only(self, p):
        """qasm : format"""
        self.supported_format = True
        p[0] = Qasm(self.supported_format, self.qelibinc, self.qregs,
                    self.cregs, self.circuit)

    def p_qasm_no_format_specified_error(self, p):
        """qasm : QELIBINC
        | circuit"""
        if self.supported_format is False:
            raise QasmException("Missing 'OPENQASM 2.0;' statement")

    def p_qasm_include(self, p):
        """qasm : qasm QELIBINC"""
        self.qelibinc = True
        p[0] = Qasm(self.supported_format, self.qelibinc, self.qregs,
                    self.cregs, self.circuit)

    def p_qasm_circuit(self, p):
        """qasm : qasm circuit"""
        p[0] = Qasm(self.supported_format, self.qelibinc, self.qregs,
                    self.cregs, p[2])

    def p_format(self, p):
        """format : FORMAT_SPEC"""
        if p[1] != "2.0":
            raise QasmException(
                "Unsupported OpenQASM version: {}, "
                "only 2.0 is supported currently by Cirq".format(p[1]))

    # circuit : new_reg circuit
    #         | gate_op circuit
    #         | measurement circuit
    #         | empty

    def p_circuit_reg(self, p):
        """circuit : new_reg circuit"""
        p[0] = self.circuit

    def p_circuit_gate_or_measurement(self, p):
        """circuit :  circuit gate_op
        |  circuit measurement"""
        self.circuit.append(p[2])
        p[0] = self.circuit

    def p_circuit_empty(self, p):
        """circuit : empty"""
        p[0] = self.circuit

    # qreg and creg

    def p_new_reg(self, p):
        """new_reg : QREG ID '[' NATURAL_NUMBER ']' ';'
        | CREG ID '[' NATURAL_NUMBER ']' ';'"""
        name, length = p[2], p[4]
        if name in self.qregs.keys() or name in self.cregs.keys():
            raise QasmException("{} is already defined at line {}".format(
                name, p.lineno(2)))
        if length == 0:
            raise QasmException(
                "Illegal, zero-length register '{}' at line {}".format(
                    name, p.lineno(4)))
        if p[1] == "qreg":
            self.qregs[name] = length
        else:
            self.cregs[name] = length
        p[0] = (name, length)

    # gate operations
    # gate_op : ID qargs
    #         | ID ( params ) qargs

    def p_gate_op_no_params(self, p):
        """gate_op :  ID qargs"""
        self._resolve_gate_operation(p[2], gate=p[1], p=p, params=[])

    def p_gate_op_with_params(self, p):
        """gate_op :  ID '(' params ')' qargs"""
        self._resolve_gate_operation(args=p[5], gate=p[1], p=p, params=p[3])

    def _resolve_gate_operation(self, args: List[List[ops.Qid]], gate: str,
                                p: Any, params: List[float]):
        gate_set = self.basic_gates if not self.qelibinc else self.all_gates
        if gate not in gate_set.keys():
            msg = 'Unknown gate "{}" at line {}{}'.format(
                gate,
                p.lineno(1),
                ", did you forget to include qelib1.inc?"
                if not self.qelibinc else "",
            )
            raise QasmException(msg)
        p[0] = gate_set[gate].on(args=args, params=params, lineno=p.lineno(1))

    # params : parameter ',' params
    #        | parameter

    def p_params_multiple(self, p):
        """params : expr ',' params"""
        p[3].insert(0, p[1])
        p[0] = p[3]

    def p_params_single(self, p):
        """params : expr """
        p[0] = [p[1]]

    # expr : term
    #            | func '(' expression ')' """
    #            | binary_op
    #            | unary_op

    def p_expr_term(self, p):
        """expr : term"""
        p[0] = p[1]

    def p_expr_parens(self, p):
        """expr : '(' expr ')'"""
        p[0] = p[2]

    def p_expr_function_call(self, p):
        """expr : ID '(' expr ')'"""
        func = p[1]
        if func not in self.functions.keys():
            raise QasmException(
                "Function not recognized: '{}' at line {}".format(
                    func, p.lineno(1)))
        p[0] = self.functions[func](p[3])

    def p_expr_unary(self, p):
        """expr : '-' expr
        | '+' expr"""
        if p[1] == '-':
            p[0] = -p[2]
        else:
            p[0] = p[2]

    def p_expr_binary(self, p):
        """expr : expr '*' expr
        | expr '/' expr
        | expr '+' expr
        | expr '-' expr
        | expr '^' expr
        """
        p[0] = self.binary_operators[p[2]](p[1], p[3])

    def p_term(self, p):
        """term : NUMBER
        | NATURAL_NUMBER
        | PI"""
        p[0] = p[1]

    # qargs : qarg ',' qargs
    #      | qarg ';'

    def p_args_multiple(self, p):
        """qargs : qarg ',' qargs"""
        p[3].insert(0, p[1])
        p[0] = p[3]

    def p_args_single(self, p):
        """qargs : qarg ';'"""
        p[0] = [p[1]]

    # qarg : ID
    #     | ID '[' NATURAL_NUMBER ']'

    def p_quantum_arg_register(self, p):
        """qarg : ID """
        reg = p[1]
        if reg not in self.qregs.keys():
            raise QasmException(
                'Undefined quantum register "{}" at line {}'.format(
                    reg, p.lineno(1)))
        qubits = []
        for idx in range(self.qregs[reg]):
            arg_name = self.make_name(idx, reg)
            if arg_name not in self.qubits.keys():
                self.qubits[arg_name] = NamedQubit(arg_name)
            qubits.append(self.qubits[arg_name])
        p[0] = qubits

    # carg : ID
    #     | ID '[' NATURAL_NUMBER ']'

    def p_classical_arg_register(self, p):
        """carg : ID """
        reg = p[1]
        if reg not in self.cregs.keys():
            raise QasmException(
                'Undefined classical register "{}" at line {}'.format(
                    reg, p.lineno(1)))

        p[0] = [self.make_name(idx, reg) for idx in range(self.cregs[reg])]

    def make_name(self, idx, reg):
        return str(reg) + "_" + str(idx)

    def p_quantum_arg_bit(self, p):
        """qarg : ID '[' NATURAL_NUMBER ']' """
        reg = p[1]
        idx = p[3]
        arg_name = self.make_name(idx, reg)
        if reg not in self.qregs.keys():
            raise QasmException(
                'Undefined quantum register "{}" at line {}'.format(
                    reg, p.lineno(1)))
        size = self.qregs[reg]
        if idx >= size:
            raise QasmException('Out of bounds qubit index {} '
                                'on register {} of size {} '
                                'at line {}'.format(idx, reg, size,
                                                    p.lineno(1)))
        if arg_name not in self.qubits.keys():
            self.qubits[arg_name] = NamedQubit(arg_name)
        p[0] = [self.qubits[arg_name]]

    def p_classical_arg_bit(self, p):
        """carg : ID '[' NATURAL_NUMBER ']' """
        reg = p[1]
        idx = p[3]
        arg_name = self.make_name(idx, reg)
        if reg not in self.cregs.keys():
            raise QasmException(
                'Undefined classical register "{}" at line {}'.format(
                    reg, p.lineno(1)))

        size = self.cregs[reg]
        if idx >= size:
            raise QasmException('Out of bounds bit index {} '
                                'on classical register {} of size {} '
                                'at line {}'.format(idx, reg, size,
                                                    p.lineno(1)))
        p[0] = [arg_name]

    # measurement operations
    # measurement : MEASURE qarg ARROW carg

    def p_measurement(self, p):
        """measurement : MEASURE qarg ARROW carg ';'"""
        qreg = p[2]
        creg = p[4]

        if len(qreg) != len(creg):
            raise QasmException(
                'mismatched register sizes {} -> {} for measurement '
                'at line {}'.format(len(qreg), len(creg), p.lineno(1)))

        p[0] = [
            ops.MeasurementGate(num_qubits=1, key=creg[i]).on(qreg[i])
            for i in range(len(qreg))
        ]

    def p_error(self, p):
        if p is None:
            raise QasmException('Unexpected end of file')

        raise QasmException("""Syntax error: '{}'
{}
at line {}, column {}""".format(p.value, self.debug_context(p), p.lineno,
                                self.find_column(p)))

    def find_column(self, p):
        line_start = self.qasm.rfind('\n', 0, p.lexpos) + 1
        return (p.lexpos - line_start) + 1

    def p_empty(self, p):
        """empty :"""

    def parse(self, qasm: str) -> Qasm:
        if self.parsedQasm is None:
            self.qasm = qasm
            self.lexer.input(self.qasm)
            self.parsedQasm = self.parser.parse(lexer=self.lexer)
        return self.parsedQasm

    def debug_context(self, p):
        debug_start = max(self.qasm.rfind('\n', 0, p.lexpos) + 1, p.lexpos - 5)
        debug_end = min(self.qasm.find('\n', p.lexpos, p.lexpos + 5),
                        p.lexpos + 5)

        return ("..." + self.qasm[debug_start:debug_end] + "\n" +
                (" " * (3 + p.lexpos - debug_start)) + "^")
def decompose_cphase_into_two_fsim(cphase_gate: 'cirq.CZPowGate',
                                   *,
                                   fsim_gate: 'cirq.FSimGate',
                                   qubits: Optional[
                                       Sequence['cirq.Qid']] = None,
                                   atol: float = 1e-8) -> 'cirq.OP_TREE':
    """Decomposes CZPowGate into two FSimGates.

    This function implements the decomposition described in section VII F I
    of https://arxiv.org/abs/1910.11333.

    The decomposition results in exactly two FSimGates and a few single-qubit
    rotations. It is feasible if and only if one of the following conditions
    is met:

        |sin(θ)| <= |sin(δ/4)| <= |sin(φ/2)|
        |sin(φ/2)| <= |sin(δ/4)| <= |sin(θ)|

    where:

         θ = fsim_gate.theta,
         φ = fsim_gate.phi,
         δ = -π * cphase_gate.exponent.

    Note that the gate parametrizations are non-injective. For the
    decomposition to be feasible it is sufficient that one of the
    parameter values which correspond to the provided gate satisfies
    the constraints. This function will find and use the appropriate
    value whenever it exists.

    The constraints above imply that certain FSimGates are not suitable
    for use in this decomposition regardless of the target CZPowGate. We
    reject such gates based on how close |sin(θ)| is to |sin(φ/2)|, see
    atol argument below.

    This implementation accounts for the global phase.

    Args:
        cphase_gate: The CZPowGate to synthesize.
        fsim_gate: The only two qubit gate that is permitted to appear in the
            output.
        qubits: The qubits to apply the resulting operations to. If not set,
            defaults `cirq.LineQubit.range(2)`.
        atol: Tolerance used to determine whether fsim_gate is valid. The gate
            is invalid if the squares of the sines of the theta angle and half
            the phi angle are too close.

    Returns:
        Operations equivalent to cphase_gate and consisting solely of two copies
        of fsim_gate and a few single-qubit rotations.

    Raises:
        ValueError under any of the following circumstances:
         * cphase_gate or fsim_gate is parametrized,
         * cphase_gate and fsim_gate do not satisfy the conditions above,
         * fsim_gate has invalid angles (see atol argument above),
         * incorrect number of qubits are provided.
    """
    if protocols.is_parameterized(cphase_gate):
        raise ValueError('Cannot decompose a parametrized gate.')
    if protocols.is_parameterized(fsim_gate):
        raise ValueError('Cannot decompose into a parametrized gate.')
    if qubits is None:
        qubits = devices.LineQubit.range(2)
    if len(qubits) != 2:
        raise ValueError(f'Expected a pair of qubits, but got {qubits!r}.')
    q0, q1 = qubits

    theta = fsim_gate.theta
    phi = fsim_gate.phi

    sin_half_phi = np.sin(phi / 2)
    cos_half_phi = np.cos(phi / 2)
    sin_theta = np.sin(theta)
    cos_theta = np.cos(theta)

    #
    # Step 1: find alpha
    #
    denominator = (sin_theta - sin_half_phi) * (sin_theta + sin_half_phi)
    if abs(denominator) < atol:
        raise ValueError(
            f'{fsim_gate} cannot be used to decompose CZPowGate because '
            'sin(theta)**2 is too close to sin(phi/2)**2 '
            f'(difference is {denominator}).')

    # Parametrization of CZPowGate by a real angle is a non-injective function
    # with the preimage of cphase_gate infinite. However, it is sufficient to
    # check just two of the angles against the constraints of the decomposition.
    canonical_delta = -np.pi * (cphase_gate.exponent % 2)
    for delta in (canonical_delta, canonical_delta + 2 * np.pi):
        sin_quarter_delta = np.sin(delta / 4)
        numerator = (sin_quarter_delta - sin_half_phi) * (sin_quarter_delta +
                                                          sin_half_phi)
        sin_alpha_squared = numerator / denominator
        if 0 <= sin_alpha_squared <= 1:
            break
    else:
        intervals = compute_cphase_exponents_for_fsim_decomposition(fsim_gate)
        raise ValueError(
            f'{cphase_gate} cannot be decomposed into two {fsim_gate}. Valid '
            f'intervals for canonical exponent of CZPowGate: {intervals}.')
    assert 0 <= sin_alpha_squared <= 1
    alpha = np.arcsin(np.sqrt(sin_alpha_squared))

    #
    # Step 2: find xi and eta
    #
    tan_alpha = np.tan(alpha)
    xi = np.arctan2(tan_alpha * cos_theta, cos_half_phi)
    eta = np.arctan2(tan_alpha * sin_theta, sin_half_phi)
    if delta < 0:
        eta += np.pi

    #
    # Step 3: synthesize output circuit
    #
    return (
        # Local X rotations to convert Γ1⊗I − iZ⊗Γ2 into exp(-i Z⊗Z δ/4)
        ops.rx(xi).on(q0),
        ops.rx(eta).on(q1),

        # Y(θ, φ) := exp(-i X⊗X θ/2) exp(-i Y⊗Y θ/2) exp(-i Z⊗Z φ/4)
        fsim_gate.on(q0, q1),
        ops.rz(phi / 2).on(q0),
        ops.rz(phi / 2).on(q1),
        ops.GlobalPhaseOperation(np.exp(1j * phi / 4)),

        # exp(i X1 α)
        ops.rx(-2 * alpha).on(q0),

        # Y(-θ, φ) := exp(i X⊗X θ/2) exp(i Y⊗Y θ/2) exp(-i Z⊗Z φ/4)
        ops.Z(q0),
        fsim_gate.on(q0, q1),
        ops.rz(phi / 2).on(q0),
        ops.rz(phi / 2).on(q1),
        ops.GlobalPhaseOperation(np.exp(1j * phi / 4)),
        ops.Z(q0),

        # Local X rotations to convert Γ1⊗I − iZ⊗Γ2 into exp(-i Z⊗Z δ/4)
        ops.rx(-eta).on(q1),
        ops.rx(xi).on(q0),

        # Local Z rotations to convert exp(-i Z⊗Z δ/4) into desired CPhase.
        ops.rz(-delta / 2).on(q0),
        ops.rz(-delta / 2).on(q1),
        ops.GlobalPhaseOperation(np.exp(-1j * delta / 4)),
    )
def _decomp_2sqrt_iswap_matrices(
    kak: 'cirq.KakDecomposition',
    atol: float = 1e-8,
) -> Tuple[Sequence[Tuple[np.ndarray, np.ndarray]], complex]:
    """Returns the single-qubit matrices for the 2-SQRT_ISWAP decomposition.

    Assumes canonical x, y, z and x >= y + |z| within tolerance.  For x, y, z
    that violate this inequality, three sqrt-iSWAP gates are required.

    References:
        Towards ultra-high fidelity quantum operations: SQiSW gate as a native
        two-qubit gate
        https://arxiv.org/abs/2105.06074
    """
    # Follows the if-branch of procedure DECOMP(U) in Algorithm 1 of the paper
    x, y, z = kak.interaction_coefficients
    b0, b1 = kak.single_qubit_operations_before
    a0, a1 = kak.single_qubit_operations_after

    # Computed gate parameters: Eq. 4, 6, 7, 8 of the paper
    # range limits added for robustness to numerical error
    def safe_arccos(v):
        return np.arccos(np.clip(v, -1, 1))

    def nonzero_sign(v):
        return -1 if v < 0 else 1

    _c = np.clip(
        np.sin(x + y - z) * np.sin(x - y + z) * np.sin(-x - y - z) * np.sin(-x + y + z), 0, 1
    )
    alpha = safe_arccos(np.cos(2 * x) - np.cos(2 * y) + np.cos(2 * z) + 2 * np.sqrt(_c))
    beta = safe_arccos(np.cos(2 * x) - np.cos(2 * y) + np.cos(2 * z) - 2 * np.sqrt(_c))
    # Don't need to limit this value because it will always be positive and the clip in the
    # following `safe_arccos` handles the cases where this could be slightly greater than 1.
    _4ccs = 4 * (np.cos(x) * np.cos(z) * np.sin(y)) ** 2  # Intermediate value
    gamma = safe_arccos(
        nonzero_sign(z)
        * np.sqrt(_4ccs / (_4ccs + np.clip(np.cos(2 * x) * np.cos(2 * y) * np.cos(2 * z), 0, 1)))
    )

    # Inner single-qubit gates: Fig. 4 of the paper
    # Gate angles here are multiplied by -2 to adjust for non-standard gate definitions in the paper
    c0 = (
        protocols.unitary(ops.rz(-gamma))
        @ protocols.unitary(ops.rx(-alpha))
        @ protocols.unitary(ops.rz(-gamma))
    )
    c1 = protocols.unitary(ops.rx(-beta))

    # Compute KAK on the decomposition to determine outer single-qubit gates
    # There is no known closed form solution for these gates
    u_sqrt_iswap = protocols.unitary(ops.SQRT_ISWAP)
    u = u_sqrt_iswap @ np.kron(c0, c1) @ u_sqrt_iswap  # Unitary of decomposition
    kak_fix = linalg.kak_decomposition(u, atol=atol / 10, rtol=0, check_preconditions=False)
    e0, e1 = kak_fix.single_qubit_operations_before
    d0, d1 = kak_fix.single_qubit_operations_after

    return [  # Pairs of single-qubit unitaries, SQRT_ISWAP between each is implied
        (e0.T.conj() @ b0, e1.T.conj() @ b1),
        (c0, c1),
        (a0 @ d0.T.conj(), a1 @ d1.T.conj()),
    ], kak.global_phase / kak_fix.global_phase