Пример #1
0
def two_qubit_matrix_to_operations(q0: ops.QubitId,
                                   q1: ops.QubitId,
                                   mat: np.ndarray,
                                   allow_partial_czs: bool,
                                   tolerance: float = 1e-8
                                   ) -> List[ops.Operation]:
    """Decomposes a two-qubit operation into Z/XY/CZ gates.

    Args:
        q0: The first qubit being operated on.
        q1: The other qubit being operated on.
        mat: Defines the operation to apply to the pair of qubits.
        allow_partial_czs: Enables the use of Partial-CZ gates.
        tolerance: A limit on the amount of error introduced by the
            construction.

    Returns:
        A list of operations implementing the matrix.
    """
    _, (a0, a1), (x, y, z), (b0, b1) = linalg.kak_decomposition(
        mat,
        linalg.Tolerance(atol=tolerance))
    # TODO: Clean up angles before returning
    return _kak_decomposition_to_operations(q0, q1,
                                            a0, a1, x, y, z, b0, b1,
                                            allow_partial_czs, tolerance)
Пример #2
0
def two_qubit_matrix_to_operations(q0: ops.QubitId,
                                   q1: ops.QubitId,
                                   mat: np.ndarray,
                                   allow_partial_czs: bool,
                                   tolerance: float = 1e-8
                                   ) -> List[ops.Operation]:
    """Decomposes a two-qubit operation into Z/XY/CZ gates.

    Args:
        q0: The first qubit being operated on.
        q1: The other qubit being operated on.
        mat: Defines the operation to apply to the pair of qubits.
        allow_partial_czs: Enables the use of Partial-CZ gates.
        tolerance: A limit on the amount of error introduced by the
            construction.

    Returns:
        A list of operations implementing the matrix.
    """
    _, (a0, a1), (x, y, z), (b0, b1) = linalg.kak_decomposition(
        mat,
        linalg.Tolerance(atol=tolerance))
    # TODO: Clean up angles before returning
    return _kak_decomposition_to_operations(q0, q1,
                                            a0, a1, x, y, z, b0, b1,
                                            allow_partial_czs, tolerance)
Пример #3
0
def _fix_single_qubit_gates_around_kak_interaction(
    *,
    desired: 'cirq.KakDecomposition',
    operations: List['cirq.Operation'],
    qubits: Sequence['cirq.Qid'],
) -> Iterator['cirq.Operation']:
    """Adds single qubit operations to complete a desired interaction.

    Args:
        desired: The kak decomposition of the desired operation.
        qubits: The pair of qubits that is being operated on.
        operations: A list of operations that composes into the desired kak
            interaction coefficients, but may not have the desired before/after
            single qubit operations or the desired global phase.

    Returns:
        A list of operations whose kak decomposition approximately equals the
        desired kak decomposition.
    """
    actual = linalg.kak_decomposition(circuits.Circuit(operations))

    def dag(a: np.ndarray) -> np.ndarray:
        return np.transpose(np.conjugate(a))

    for k in range(2):
        g = ops.MatrixGate(
            dag(actual.single_qubit_operations_before[k])
            @ desired.single_qubit_operations_before[k])
        yield g(qubits[k])
    yield from operations
    for k in range(2):
        g = ops.MatrixGate(desired.single_qubit_operations_after[k] @ dag(
            actual.single_qubit_operations_after[k]))
        yield g(qubits[k])
    yield ops.GlobalPhaseOperation(desired.global_phase / actual.global_phase)
Пример #4
0
def two_qubit_matrix_to_operations(
    q0: 'cirq.Qid',
    q1: 'cirq.Qid',
    mat: np.ndarray,
    allow_partial_czs: bool,
    atol: float = 1e-8,
    clean_operations: bool = True,
) -> List[ops.Operation]:
    """Decomposes a two-qubit operation into Z/XY/CZ gates.

    Args:
        q0: The first qubit being operated on.
        q1: The other qubit being operated on.
        mat: Defines the operation to apply to the pair of qubits.
        allow_partial_czs: Enables the use of Partial-CZ gates.
        atol: A limit on the amount of absolute error introduced by the
            construction.
        clean_operations: Enables optimizing resulting operation list by
            merging operations and ejecting phased Paulis and Z operations.

    Returns:
        A list of operations implementing the matrix.
    """
    kak = linalg.kak_decomposition(mat, atol=atol)
    operations = _kak_decomposition_to_operations(q0,
                                                  q1,
                                                  kak,
                                                  allow_partial_czs,
                                                  atol=atol)
    if clean_operations:
        return _cleanup_operations(operations)
    return operations
Пример #5
0
 def from_matrix(mat: np.array, tolerance=1e-8) -> 'QasmTwoQubitGate':
     _, (a1, a0), (x, y, z), (b1, b0) = linalg.kak_decomposition(
         mat, linalg.Tolerance(atol=tolerance))
     before0 = QasmUGate.from_matrix(b0)
     before1 = QasmUGate.from_matrix(b1)
     after0 = QasmUGate.from_matrix(a0)
     after1 = QasmUGate.from_matrix(a1)
     return QasmTwoQubitGate(before0, before1, x, y, z, after0, after1)
Пример #6
0
def _outer_locals_for_unitary(
    target: np.ndarray, base: np.ndarray
) -> Tuple[_SingleQubitGatePair, _SingleQubitGatePair, np.ndarray]:
    """Local unitaries mapping between locally equivalent 2-local unitaries.

    Finds the left and right 1-local unitaries kL, kR such that

    U_target = kL @ U_base @ kR

    Args:
        target: The unitary to which we want to map.
        base: The base unitary which maps to target.

    Returns:
        kR: The right 1-local unitaries in the equation above, expressed as
            2-tuples of (2x2) single qubit unitaries.
        kL: The left 1-local unitaries in the equation above, expressed as
            2-tuples of (2x2) single qubit unitaries.
        actual: The outcome of kL @ base @ kR
    """
    target_decomp = linalg.kak_decomposition(target)
    base_decomp = linalg.kak_decomposition(base)

    # From the KAK decomposition, we have
    # kLt At kRt = kL kLb Ab KRb kR
    # If At=Ab, we can solve for kL and kR as
    # kLt = kL kLb --> kL = kLt kLb^\dagger
    # kRt = kRb kR --> kR = kRb\dagger kRt

    # 0 and 1 are qubit indices.
    kLt0, kLt1 = target_decomp.single_qubit_operations_after
    kLb0, kLb1 = base_decomp.single_qubit_operations_after
    kL = kLt0 @ kLb0.conj().T, kLt1 @ kLb1.conj().T

    kRt0, kRt1 = target_decomp.single_qubit_operations_before
    kRb0, kRb1 = base_decomp.single_qubit_operations_before
    kR = kRb0.conj().T @ kRt0, kRb1.conj().T @ kRt1

    actual = np.kron(*kL) @ base
    actual = actual @ np.kron(*kR)
    actual *= np.conj(target_decomp.global_phase)

    return kR, kL, actual
Пример #7
0
 def from_matrix(mat: np.array, tolerance=1e-8) -> 'QasmTwoQubitGate':
     _, (a1, a0), (x, y, z), (b1, b0) = linalg.kak_decomposition(
         mat,
         linalg.Tolerance(atol=tolerance))
     before0 = QasmUGate.from_matrix(b0)
     before1 = QasmUGate.from_matrix(b1)
     after0 = QasmUGate.from_matrix(a0)
     after1 = QasmUGate.from_matrix(a1)
     return QasmTwoQubitGate(before0, before1,
                             x, y, z,
                             after0, after1)
Пример #8
0
    def from_matrix(mat: np.array, atol=1e-8) -> 'QasmTwoQubitGate':
        """Creates a QasmTwoQubitGate from the given matrix.

        Args:
            mat: The unitary matrix of the two qubit gate.
            atol: Absolute error tolerance when decomposing.

        Returns:
            A QasmTwoQubitGate implementing the matrix.
        """
        kak = linalg.kak_decomposition(mat, atol=atol)
        return QasmTwoQubitGate(kak)
Пример #9
0
def _decompose_two_qubit_interaction_into_two_b_gates(
        interaction: Union['cirq.Operation', 'cirq.Gate', np.ndarray, Any], *,
        qubits: Sequence['cirq.Qid']) -> List['cirq.Operation']:
    kak = linalg.kak_decomposition(interaction)

    result = _decompose_interaction_into_two_b_gates_ignoring_single_qubit_ops(
        qubits, kak.interaction_coefficients)

    return list(
        _fix_single_qubit_gates_around_kak_interaction(desired=kak,
                                                       qubits=qubits,
                                                       operations=result))
Пример #10
0
    def from_matrix(mat: np.array, tolerance=1e-8) -> 'QasmTwoQubitGate':
        kak = linalg.kak_decomposition(mat, linalg.Tolerance(atol=tolerance))

        a1, a0 = kak.single_qubit_operations_after
        b1, b0 = kak.single_qubit_operations_before
        x, y, z = kak.interaction_coefficients

        before0 = QasmUGate.from_matrix(b0)
        before1 = QasmUGate.from_matrix(b1)
        after0 = QasmUGate.from_matrix(a0)
        after1 = QasmUGate.from_matrix(a1)
        return QasmTwoQubitGate(before0, before1,
                                x, y, z,
                                after0, after1)
Пример #11
0
def _decompose_b_gate_into_two_fsims(
        *, fsim_gate: 'cirq.FSimGate',
        qubits: Sequence['cirq.Qid']) -> List['cirq.Operation']:
    kak = linalg.kak_decomposition(_B)

    result = _decompose_xx_yy_into_two_fsims_ignoring_single_qubit_ops(
        qubits=qubits,
        fsim_gate=fsim_gate,
        canonical_x_kak_coefficient=kak.interaction_coefficients[0],
        canonical_y_kak_coefficient=kak.interaction_coefficients[1])

    return list(
        _fix_single_qubit_gates_around_kak_interaction(desired=kak,
                                                       qubits=qubits,
                                                       operations=result))
Пример #12
0
def two_qubit_matrix_to_ion_operations(
    q0: 'cirq.Qid', q1: 'cirq.Qid', mat: np.ndarray, atol: float = 1e-8
) -> List[ops.Operation]:
    """Decomposes a two-qubit operation into MS/single-qubit rotation gates.

    Args:
        q0: The first qubit being operated on.
        q1: The other qubit being operated on.
        mat: Defines the operation to apply to the pair of qubits.
        atol: A limit on the amount of error introduced by the
            construction.

    Returns:
        A list of operations implementing the matrix.
    """
    kak = linalg.kak_decomposition(mat, atol=atol)
    operations = _kak_decomposition_to_operations(q0, q1, kak, atol)
    return _cleanup_operations(operations)
Пример #13
0
    def _decompose_two_qubit(self, operation: 'cirq.Operation') -> ops.OP_TREE:
        """Decomposes a two qubit gate into XXPow, YYPow, and ZZPow plus single qubit gates."""
        mat = protocols.unitary(operation)
        kak = linalg.kak_decomposition(mat, check_preconditions=False)

        for qubit, mat in zip(operation.qubits,
                              kak.single_qubit_operations_before):
            gates = optimizers.single_qubit_matrix_to_gates(mat, self.atol)
            for gate in gates:
                yield gate(qubit)

        two_qubit_gates = [parity_gates.XX, parity_gates.YY, parity_gates.ZZ]
        for two_qubit_gate, coefficient in zip(two_qubit_gates,
                                               kak.interaction_coefficients):
            yield (two_qubit_gate**(-coefficient * 2 /
                                    np.pi))(*operation.qubits)

        for qubit, mat in zip(operation.qubits,
                              kak.single_qubit_operations_after):
            for gate in optimizers.single_qubit_matrix_to_gates(
                    mat, self.atol):
                yield gate(qubit)
Пример #14
0
def test_kak_decomposition(m):
    g, (a1, a0), (x, y, z), (b1, b0) = linalg.kak_decomposition(m)
    m2 = recompose_kak(g, (a1, a0), (x, y, z), (b1, b0))
    assert np.allclose(m, m2)
Пример #15
0
def decompose_two_qubit_interaction_into_four_fsim_gates_via_b(
        interaction: Union['cirq.Operation', 'cirq.Gate', np.ndarray, Any],
        *,
        fsim_gate: 'cirq.FSimGate',
        qubits: Sequence['cirq.Qid'] = None) -> 'cirq.Circuit':
    """Decomposes operations into an FSimGate near theta=pi/2, phi=0.

    This decomposition is guaranteed to use exactly four of the given FSim
    gates. It works by decomposing into two B gates and then decomposing each
    B gate into two of the given FSim gate.

    This decomposition only works for FSim gates with a theta (iswap angle)
    between 3/8π and 5/8π (i.e. within 22.5° of maximum strength) and a
    phi (cphase angle) between -π/4 and +π/4 (i.e. within 45° of minimum
    strength).

    Args:
        interaction: The two qubit operation to synthesize. This can either be
            a cirq object (such as a gate, operation, or circuit) or a raw numpy
            array specifying the 4x4 unitary matrix.
        fsim_gate: The only two qubit gate that is permitted to appear in the
            output. Must satisfy 3/8π < phi < 5/8π and abs(theta) < pi/4.
        qubits: The qubits that the resulting operations should apply the
            desired interaction to. If not set then defaults to either the
            qubits of the given interaction (if it is a `cirq.Operation`) or
            else to `cirq.LineQubit.range(2)`.

    Returns:
        A list of operations implementing the desired two qubit unitary. The
        list will include four operations of the given fsim gate, various single
        qubit operations, and a global phase operation.
    """
    if not 3 / 8 * np.pi <= fsim_gate.theta <= 5 / 8 * np.pi:
        raise ValueError('Must have 3π/8 ≤ fsim_gate.theta ≤ 5π/8')
    if abs(fsim_gate.phi) > np.pi / 4:
        raise ValueError('Must have abs(fsim_gate.phi) ≤ π/4')
    if qubits is None:
        if isinstance(interaction, ops.Operation):
            qubits = interaction.qubits
        else:
            qubits = devices.LineQubit.range(2)
    if len(qubits) != 2:
        raise ValueError(f'Expected a pair of qubits, but got {qubits!r}.')
    kak = linalg.kak_decomposition(interaction)

    result_using_b_gates = _decompose_two_qubit_interaction_into_two_b_gates(
        kak, qubits=qubits)

    b_decomposition = _decompose_b_gate_into_two_fsims(fsim_gate=fsim_gate,
                                                       qubits=qubits)
    result = []
    for op in result_using_b_gates:
        if isinstance(op.gate, _BGate):
            result.extend(b_decomposition)
        else:
            result.append(op)

    circuit = circuits.Circuit(result)
    merge_single_qubit_gates.MergeSingleQubitGates().optimize_circuit(circuit)
    drop_empty_moments.DropEmptyMoments().optimize_circuit(circuit)
    return circuit
Пример #16
0
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
Пример #17
0
def two_qubit_matrix_to_sqrt_iswap_operations(
    q0: 'cirq.Qid',
    q1: 'cirq.Qid',
    mat: np.ndarray,
    *,
    required_sqrt_iswap_count: Optional[int] = None,
    use_sqrt_iswap_inv: bool = False,
    atol: float = 1e-8,
    check_preconditions: bool = True,
    clean_operations: bool = False,
) -> Sequence['cirq.Operation']:
    """Decomposes a two-qubit operation into ZPow/XPow/YPow/sqrt-iSWAP gates.

    This method uses the KAK decomposition of the matrix to determine how many
    sqrt-iSWAP gates are needed and which single-qubit gates to use in between
    each sqrt-iSWAP.

    All operations can be synthesized with exactly three sqrt-iSWAP gates and
    about 79% of operations (randomly chosen under the Haar measure) can also be
    synthesized with two sqrt-iSWAP gates.  Only special cases locally
    equivalent to identity or sqrt-iSWAP can be synthesized with zero or one
    sqrt-iSWAP gates respectively.  Unless ``required_sqrt_iswap_count`` is
    specified, the fewest possible number of sqrt-iSWAP will be used.

    Args:
        q0: The first qubit being operated on.
        q1: The other qubit being operated on.
        mat: Defines the operation to apply to the pair of qubits.
        required_sqrt_iswap_count: When specified, exactly this many sqrt-iSWAP
            gates will be used even if fewer is possible (maximum 3).  Raises
            ``ValueError`` if impossible.
        use_sqrt_iswap_inv: If True, returns a decomposition using
            ``SQRT_ISWAP_INV`` gates instead of ``SQRT_ISWAP``.  This
            decomposition is identical except for the addition of single-qubit
            Z gates.
        atol: A limit on the amount of absolute error introduced by the
            construction.
        check_preconditions: If set, verifies that the input corresponds to a
            4x4 unitary before decomposing.
        clean_operations: Merges runs of single qubit gates to a single `cirq.PhasedXZGate` in
            the resulting operations list.

    Returns:
        A list of operations implementing the matrix including at most three
        ``SQRT_ISWAP`` (sqrt-iSWAP) gates and ZPow, XPow, and YPow single-qubit
        gates.

    Raises:
        ValueError:
            If ``required_sqrt_iswap_count`` is specified, the minimum number of
            sqrt-iSWAP gates needed to decompose the given matrix is greater
            than ``required_sqrt_iswap_count``.

    References:
        Towards ultra-high fidelity quantum operations: SQiSW gate as a native
        two-qubit gate
        https://arxiv.org/abs/2105.06074
    """
    kak = linalg.kak_decomposition(
        mat, atol=atol / 10, rtol=0, check_preconditions=check_preconditions
    )
    operations = _kak_decomposition_to_sqrt_iswap_operations(
        q0, q1, kak, required_sqrt_iswap_count, use_sqrt_iswap_inv, atol=atol
    )
    return (
        [*merge_single_qubit_gates_to_phxz(circuits.Circuit(operations)).all_operations()]
        if clean_operations
        else operations
    )
Пример #18
0
def decompose_two_qubit_interaction_into_four_fsim_gates(
    interaction: Union['cirq.SupportsUnitary', np.ndarray],
    *,
    fsim_gate: Union['cirq.FSimGate', 'cirq.ISwapPowGate'],
    qubits: Sequence['cirq.Qid'] = None,
) -> 'cirq.Circuit':
    """Decomposes operations into an FSimGate near theta=pi/2, phi=0.

    This decomposition is guaranteed to use exactly four of the given FSim
    gates. It works by decomposing into two B gates and then decomposing each
    B gate into two of the given FSim gate.

    This decomposition only works for FSim gates with a theta (iswap angle)
    between 3/8π and 5/8π (i.e. within 22.5° of maximum strength) and a
    phi (cphase angle) between -π/4 and +π/4 (i.e. within 45° of minimum
    strength).

    Args:
        interaction: The two qubit operation to synthesize. This can either be
            a cirq object (such as a gate, operation, or circuit) or a raw numpy
            array specifying the 4x4 unitary matrix.
        fsim_gate: The only two qubit gate that is permitted to appear in the
            output. Must satisfy 3/8π < phi < 5/8π and abs(theta) < pi/4.
        qubits: The qubits that the resulting operations should apply the
            desired interaction to. If not set then defaults to either the
            qubits of the given interaction (if it is a `cirq.Operation`) or
            else to `cirq.LineQubit.range(2)`.

    Returns:
        A list of operations implementing the desired two qubit unitary. The
        list will include four operations of the given fsim gate, various single
        qubit operations, and a global phase operation.

    Raises:
        ValueError: If the `fsim_gate` has invalid angles or is parameterized, or
            if the supplied target to synthesize acts on more than two qubits.
    """
    if protocols.is_parameterized(fsim_gate):
        raise ValueError(
            "FSimGate must not have parameterized values for angles.")
    if isinstance(fsim_gate, ops.ISwapPowGate):
        mapped_gate = ops.FSimGate(-fsim_gate.exponent * np.pi / 2, 0)
    else:
        mapped_gate = fsim_gate
    if not 3 / 8 * np.pi <= abs(mapped_gate.theta) <= 5 / 8 * np.pi:
        raise ValueError('Must have 3π/8 ≤ |fsim_gate.theta| ≤ 5π/8')
    if abs(mapped_gate.phi) > np.pi / 4:
        raise ValueError('Must have abs(fsim_gate.phi) ≤ π/4')
    if qubits is None:
        if isinstance(interaction, ops.Operation):
            qubits = interaction.qubits
        else:
            qubits = devices.LineQubit.range(2)
    if len(qubits) != 2:
        raise ValueError(f'Expected a pair of qubits, but got {qubits!r}.')
    kak = linalg.kak_decomposition(interaction)

    result_using_b_gates = _decompose_two_qubit_interaction_into_two_b_gates(
        kak, qubits=qubits)

    b_decomposition = _decompose_b_gate_into_two_fsims(fsim_gate=mapped_gate,
                                                       qubits=qubits)
    b_decomposition = [
        fsim_gate(*op.qubits) if op.gate == mapped_gate else op
        for op in b_decomposition
    ]

    result = circuits.Circuit()
    for op in result_using_b_gates:
        if isinstance(op.gate, _BGate):
            result.append(b_decomposition)
        else:
            result.append(op)
    return result