Exemple #1
0
    def _unitary_(self) -> Union[np.ndarray, NotImplementedType]:
        qubits = cirq.LineQid.for_gate(self)
        op = self.sub_gate.on(*qubits[self.num_controls() :])
        c_op = cop.ControlledOperation(qubits[: self.num_controls()], op, self.control_values)

        return protocols.unitary(c_op, default=NotImplemented)
Exemple #2
0
 def _unitary_(self) -> Union[np.ndarray, NotImplementedType]:
     return protocols.unitary(self.sub_operation, NotImplemented)
Exemple #3
0
 def _unitary_(self) -> np.ndarray:
     mat = np.eye(2)
     qubit = named_qubit.NamedQubit('arbitrary')
     for op in protocols.decompose_once_with_qubits(self, (qubit, )):
         mat = protocols.unitary(op).dot(mat)
     return mat
Exemple #4
0
def _translate_one_qubit_cirq_operation_to_braket_instruction(
    op: Union[np.ndarray, "cirq.Operation"], target: Optional[int] = None,
) -> List[Instruction]:
    """Translates a one-qubit Cirq operation to a (sequence of) Braket
    instruction(s) according to the following rules:

    1. Attempts to find a "standard translation" from Cirq to Braket.
        - e.g., checks if `op` is Pauli-X and, if so, returns the Braket X.

    2. If (1) is not successful, decomposes the unitary of `op` to
    Rz(theta) Ry(phi) Rz(lambda) and returns the series of rotations as Braket
    instructions.

    Args:
        op: One-qubit Cirq operation to translate.
        target: Qubit index for the op to act on. Must be specified and if only
            if `op` is given as a numpy array.
    """
    # Translate qubit index.
    if not isinstance(op, np.ndarray):
        target = op.qubits[0].x

    if target is None:
        raise ValueError(
            "Arg `target` must be specified when `op` is a matrix."
        )

    # Check common single-qubit gates.
    if isinstance(op, cirq_ops.Operation):
        if isinstance(op.gate, cirq_ops.XPowGate):
            exponent = op.gate.exponent

            if np.isclose(exponent, 1.0) or np.isclose(exponent, -1.0):
                return [Instruction(braket_gates.X(), target)]
            elif np.isclose(exponent, 0.5):
                return [Instruction(braket_gates.V(), target)]
            elif np.isclose(exponent, -0.5):
                return [Instruction(braket_gates.Vi(), target)]

            return [Instruction(braket_gates.Rx(exponent * np.pi), target)]

        elif isinstance(op.gate, cirq_ops.YPowGate):
            exponent = op.gate.exponent

            if np.isclose(exponent, 1.0) or np.isclose(exponent, -1.0):
                return [Instruction(braket_gates.Y(), target)]

            return [Instruction(braket_gates.Ry(exponent * np.pi), target)]

        elif isinstance(op.gate, cirq_ops.ZPowGate):
            exponent = op.gate.exponent

            if np.isclose(exponent, 1.0) or np.isclose(exponent, -1.0):
                return [Instruction(braket_gates.Z(), target)]
            elif np.isclose(exponent, 0.5):
                return [Instruction(braket_gates.S(), target)]
            elif np.isclose(exponent, -0.5):
                return [Instruction(braket_gates.Si(), target)]
            elif np.isclose(exponent, 0.25):
                return [Instruction(braket_gates.T(), target)]
            elif np.isclose(exponent, -0.25):
                return [Instruction(braket_gates.Ti(), target)]

            return [Instruction(braket_gates.Rz(exponent * np.pi), target)]

        elif isinstance(op.gate, cirq_ops.HPowGate) and np.isclose(
            abs(op.gate.exponent), 1.0
        ):
            return [Instruction(braket_gates.H(), target)]

    # Arbitrary single-qubit unitary decomposition.
    # TODO: This does not account for global phase.
    if isinstance(op, cirq_ops.Operation):
        unitary_matrix = protocols.unitary(op)
    else:
        unitary_matrix = op

    a, b, c = deconstruct_single_qubit_matrix_into_angles(unitary_matrix)
    return [
        Instruction(braket_gates.Rz(a), target),
        Instruction(braket_gates.Ry(b), target),
        Instruction(braket_gates.Rz(c), target),
    ]
Exemple #5
0
 def _unitary_(self) -> np.ndarray:
     mat = np.eye(2)
     qubit = raw_types.QubitId()
     for op in op_tree.flatten_op_tree(self.default_decompose((qubit, ))):
         mat = protocols.unitary(op).dot(mat)
     return mat
Exemple #6
0
 def _unitary_(self) -> Union[np.ndarray, NotImplementedType]:
     sub_matrix = protocols.unitary(self.sub_operation, None)
     if sub_matrix is None:
         return NotImplemented
     return self._extend_matrix(sub_matrix)
Exemple #7
0
def scatter_plot_normalized_kak_interaction_coefficients(
    interactions: Iterable[Union[np.ndarray, 'cirq.SupportsUnitary',
                                 'KakDecomposition']],
    *,
    include_frame: bool = True,
    ax: Optional[plt.Axes] = None,
    **kwargs,
):
    r"""Plots the interaction coefficients of many two-qubit operations.

    Plots:
        A point for the (x, y, z) normalized interaction coefficients of
        each interaction from the given interactions. The (x, y, z) coordinates
        are normalized so that the maximum value is at 1 instead of at pi/4.

        If `include_frame` is set to True, then a black wireframe outline of the
        canonicalized normalized KAK coefficient space. The space is defined by
        the following two constraints:

            0 <= abs(z) <= y <= x <= 1
            if x = 1 then z >= 0

        The wireframe includes lines along the surface of the space at z=0.

        The space is a prism with the identity at the origin, a crease along
        y=z=0 leading to the CZ/CNOT at x=1 and a vertical triangular face that
        contains the iswap at x=y=1,z=0 and the swap at x=y=z=1:

                                 (x=1,y=1,z=0)
                             swap___iswap___swap (x=1,y=1,z=+-1)
                               _/\    |    /
                             _/   \   |   /
                           _/      \  |  /
                         _/         \ | /
                       _/            \|/
        (x=0,y=0,z=0) I---------------CZ (x=1,y=0,z=0)

    Args:
        interactions: An iterable of two qubit unitary interactions. Each
            interaction can be specified as a raw 4x4 unitary matrix, or an
            object with a 4x4 unitary matrix according to `cirq.unitary` (
            (e.g. `cirq.CZ` or a `cirq.KakDecomposition` or a `cirq.Circuit`
            over two qubits).
        include_frame: Determines whether or not to draw the kak space
            wireframe. Defaults to `True`.
        ax: A matplotlib 3d axes object to plot into. If not specified, a new
            figure is created, plotted, and shown.

        **kwargs: Arguments forwarded into the call to `scatter` that plots the
            points. Working arguments include color `c='blue'`, scale `s=2`,
            labelling `label="theta=pi/4"`, etc. For reference see the
            `matplotlib.pyplot.scatter` documentation:
            https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.scatter.html

    Returns:
        The matplotlib 3d axes object that was plotted into.

    Examples:
        >>> ax = None
        >>> for y in np.linspace(0, 0.5, 4):
        ...     a, b = cirq.LineQubit.range(2)
        ...     circuits = [
        ...         cirq.Circuit(
        ...             cirq.CZ(a, b)**0.5,
        ...             cirq.X(a)**y, cirq.X(b)**x,
        ...             cirq.CZ(a, b)**0.5,
        ...             cirq.X(a)**x, cirq.X(b)**y,
        ...             cirq.CZ(a, b) ** 0.5,
        ...         )
        ...         for x in np.linspace(0, 1, 25)
        ...     ]
        ...     ax = cirq.scatter_plot_normalized_kak_interaction_coefficients(
        ...         circuits,
        ...         include_frame=ax is None,
        ...         ax=ax,
        ...         s=1,
        ...         label=f'y={y:0.2f}')
        >>> _ = ax.legend()
        >>> import matplotlib.pyplot as plt
        >>> plt.show()
    """
    show_plot = not ax
    if not ax:
        fig = plt.figure()
        ax = fig.add_subplot(1, 1, 1, projection='3d')

    def coord_transform(
        pts: Sequence[Tuple[float, float, float]]
    ) -> Tuple[Iterable[float], Iterable[float], Iterable[float]]:
        if len(pts) == 0:
            return [], [], []
        xs, ys, zs = zip(*pts)
        return xs, zs, ys

    if include_frame:
        envelope = [
            (0, 0, 0),
            (1, 1, 1),
            (1, 1, -1),
            (0, 0, 0),
            (1, 1, 1),
            (1, 0, 0),
            (0, 0, 0),
            (1, 1, -1),
            (1, 0, 0),
            (0, 0, 0),
            (1, 0, 0),
            (1, 1, 0),
            (0, 0, 0),
        ]
        ax.plot(*coord_transform(envelope), c='black', linewidth=1)

    # parse input and extract KAK vector
    if not isinstance(interactions, np.ndarray):
        interactions_extracted: List[np.ndarray] = [
            a if isinstance(a, np.ndarray) else protocols.unitary(a)
            for a in interactions
        ]
    else:
        interactions_extracted = [interactions]

    points = kak_vector(interactions_extracted) * 4 / np.pi

    ax.scatter(*coord_transform(points), **kwargs)
    ax.set_xlim(0, +1)
    ax.set_ylim(-1, +1)
    ax.set_zlim(0, +1)

    if show_plot:
        fig.show()

    return ax
Exemple #8
0
 def _decompose_single_qubit(self,
                             operation: 'cirq.Operation') -> ops.OP_TREE:
     qubit = operation.qubits[0]
     mat = protocols.unitary(operation)
     for gate in optimizers.single_qubit_matrix_to_gates(mat, self.atol):
         yield gate(qubit)
Exemple #9
0
 def _unitary_(self) -> Union[np.ndarray, type(NotImplemented)]:
     return protocols.unitary(self._gate, NotImplemented)
Exemple #10
0
 def _unitary_(self) -> Union[np.ndarray, NotImplementedType]:
     sub_matrix = protocols.unitary(self.sub_operation, None)
     if sub_matrix is None:
         return NotImplemented
     return linalg.block_diag(
         np.eye(pow(2, len(self.qubits)) - sub_matrix.shape[0]), sub_matrix)
Exemple #11
0
def _circuit_as_layers(circuit: circuits.Circuit,
                       grouping: _QubitGrouping) -> List[_TransformsThenCzs]:
    """Transforms a circuit into a series of GroupMatrix+CZ layers.

    Args:
        circuit: The circuit to transform.
        grouping: How the circuit's qubits are combined into groups.

    Returns:
        A list of layers. Each layer has a matrix to apply to each group of
        qubits, and a list of CZs to apply to pairs of qubits crossing
        between groups.
    """
    frontier = {q: 0 for q in circuit.all_qubits()}

    layers = []
    while True:
        # Pull within-group operations into per-group matrices.
        any_group_matrices = False
        group_matrices = []
        for g in grouping.groups:
            # Scan for reachable operations contained within the group qubits.
            start_frontier = {q: frontier[q] for q in g}
            end_frontier = circuit.reachable_frontier_from(start_frontier)
            mergeable_ops = circuit.findall_operations_between(start_frontier,
                                                               end_frontier)

            # Advance frontier.
            for q, v in end_frontier.items():
                frontier[q] = v

            # Fold reachable operations into a single group matrix.
            group_matrix = np.eye(1 << len(g)).reshape((2, 2) * len(g))
            if mergeable_ops:
                any_group_matrices = True
            for _, op in mergeable_ops:
                group_matrix = linalg.targeted_left_multiply(
                    left_matrix=protocols.unitary(op).reshape(
                        (2, 2) * len(op.qubits)),
                    right_target=group_matrix,
                    target_axes=[grouping.loc(q)[1] for q in op.qubits])
            group_matrices.append(np.transpose(group_matrix.reshape(
                1 << len(g), 1 << len(g))))

        # Scan for reachable CZ operations between groups.
        end_frontier = circuit.reachable_frontier_from(
            frontier,
            is_blocker=lambda op: grouping.all_in_same_group(*op.qubits))
        cz_ops = circuit.findall_operations_between(frontier, end_frontier)

        # Advance frontier.
        frontier = end_frontier

        # List out qubit index pairs for each CZ.
        cz_indices = []
        for _, cz in cz_ops:
            a, b = cz.qubits
            assert cz == ops.CZ(a, b)
            cz_indices.append((grouping.ind(a), grouping.ind(b)))

        # Combine group and CZ operations into a simulation layer.
        if not any_group_matrices and not cz_indices:
            break
        layer = _TransformsThenCzs(group_matrices=group_matrices,
                                   cz_indices=cz_indices)
        layers.append(layer)

    # We should have processed the whole circuit.
    assert frontier == {q: len(circuit) for q in circuit.all_qubits()}

    return layers
Exemple #12
0
 def _decompose_two_qubit_operation(self, op: 'cirq.Operation', _) -> DecomposeResult:
     if protocols.has_unitary(op):
         return transformers.two_qubit_matrix_to_ion_operations(
             op.qubits[0], op.qubits[1], protocols.unitary(op)
         )
     return NotImplemented
 def _mixture_(self) -> Sequence[Tuple[float, np.ndarray]]:
     return ((self._p_ii, protocols.unitary(cirq.IdentityGate(2))),
             (self._p_xi, protocols.unitary(XIGate())),
             (self._p_xx,
              protocols.unitary(XXGate())), (self._p_xy,
                                             protocols.unitary(XYGate())),
             (self._p_xz,
              protocols.unitary(XZGate())), (self._p_yi,
                                             protocols.unitary(YIGate())),
             (self._p_yx,
              protocols.unitary(YXGate())), (self._p_yy,
                                             protocols.unitary(YYGate())),
             (self._p_yz,
              protocols.unitary(YZGate())), (self._p_zi,
                                             protocols.unitary(ZIGate())),
             (self._p_zx,
              protocols.unitary(ZXGate())), (self._p_zy,
                                             protocols.unitary(ZYGate())),
             (self._p_zz,
              protocols.unitary(ZZGate())), (self._p_ix,
                                             protocols.unitary(IXGate())),
             (self._p_iy,
              protocols.unitary(IYGate())), (self._p_iz,
                                             protocols.unitary(IZGate())))
Exemple #14
0
def assert_qasm_is_consistent_with_unitary(val: Any):
    """Uses `val._unitary_` to check `val._qasm_`'s behavior."""

    # Only test if qiskit is installed.
    try:
        import qiskit
    except ImportError:
        # coverage: ignore
        warnings.warn("Skipped assert_qasm_is_consistent_with_unitary because "
                      "qiskit isn't installed to verify against.")
        return

    unitary = protocols.unitary(val, None)
    if unitary is None:
        # Vacuous consistency.
        return

    if isinstance(val, ops.Operation):
        qubits: Sequence[ops.Qid] = val.qubits
        op = val
    elif isinstance(val, ops.Gate):
        qid_shape = protocols.qid_shape(val)
        remaining_shape = list(qid_shape)
        controls = getattr(val, 'control_qubits', None)
        if controls is not None:
            for i, q in zip(reversed(range(len(controls))),
                            reversed(controls)):
                if q is not None:
                    remaining_shape.pop(i)
        qubits = devices.LineQid.for_qid_shape(remaining_shape)
        op = val.on(*qubits)
    else:
        raise NotImplementedError(f"Don't know how to test {val!r}")

    args = protocols.QasmArgs(
        qubit_id_map={q: f'q[{i}]'
                      for i, q in enumerate(qubits)})
    qasm = protocols.qasm(op, args=args, default=None)
    if qasm is None:
        return

    num_qubits = len(qubits)
    header = f"""
OPENQASM 2.0;
include "qelib1.inc";
qreg q[{num_qubits}];
"""
    qasm = header + qasm

    qasm_unitary = None
    try:
        result = qiskit.execute(
            qiskit.QuantumCircuit.from_qasm_str(qasm),
            backend=qiskit.Aer.get_backend('unitary_simulator'),
        )
        qasm_unitary = result.result().get_unitary()
        qasm_unitary = _reorder_indices_of_matrix(
            qasm_unitary, list(reversed(range(num_qubits))))

        lin_alg_utils.assert_allclose_up_to_global_phase(qasm_unitary,
                                                         unitary,
                                                         rtol=1e-8,
                                                         atol=1e-8)
    except Exception as ex:
        p_unitary: Optional[np.ndarray]
        p_qasm_unitary: Optional[np.ndarray]
        if qasm_unitary is not None:
            p_unitary, p_qasm_unitary = linalg.match_global_phase(
                unitary, qasm_unitary)
        else:
            p_unitary = None
            p_qasm_unitary = None
        raise AssertionError(
            'QASM not consistent with cirq.unitary(op) up to global phase.\n\n'
            'op:\n{}\n\n'
            'cirq.unitary(op):\n{}\n\n'
            'Generated QASM:\n\n{}\n\n'
            'Unitary of generated QASM:\n{}\n\n'
            'Phased matched cirq.unitary(op):\n{}\n\n'
            'Phased matched unitary of generated QASM:\n{}\n\n'
            'Underlying error:\n{}'.format(
                _indent(repr(op)),
                _indent(repr(unitary)),
                _indent(qasm),
                _indent(repr(qasm_unitary)),
                _indent(repr(p_unitary)),
                _indent(repr(p_qasm_unitary)),
                _indent(str(ex)),
            ))
def assert_qasm_is_consistent_with_unitary(val: Any):
    """Uses `val._unitary_` to check `val._qasm_`'s behavior."""

    # Only test if qiskit is installed.
    try:
        import qiskit
    except ImportError:
        # coverage: ignore
        warnings.warn("Skipped assert_qasm_is_consistent_with_unitary because "
                      "qiskit isn't installed to verify against.")
        return

    unitary = protocols.unitary(val, None)
    if unitary is None:
        # Vacuous consistency.
        return

    controls = getattr(val, 'control_qubits', None)
    if controls is None:
        qubit_count = len(unitary).bit_length() - 1
    else:
        qubit_count = len(unitary).bit_length() - 1 - (len(controls) -
                                                       controls.count(None))
    if isinstance(val, ops.Operation):
        qubits = val.qubits
        op = val
    elif isinstance(val, ops.Gate):
        qubits = tuple(line.LineQubit.range(qubit_count))
        op = val.on(*qubits)
    else:
        raise NotImplementedError("Don't know how to test {!r}".format(val))

    args = protocols.QasmArgs(
        qubit_id_map={q: 'q[{}]'.format(i)
                      for i, q in enumerate(qubits)})
    qasm = protocols.qasm(op, args=args, default=None)
    if qasm is None:
        return
    else:
        header = """
OPENQASM 2.0;
include "qelib1.inc";
qreg q[{}];
""".format(len(qubits))
        qasm = header + qasm

    qasm_unitary = None
    try:
        result = qiskit.execute(
            qiskit.load_qasm_string(qasm),
            backend=qiskit.Aer.get_backend('unitary_simulator'))
        qasm_unitary = result.result().get_unitary()
        qasm_unitary = _reorder_indices_of_matrix(
            qasm_unitary, list(reversed(range(len(qubits)))))

        lin_alg_utils.assert_allclose_up_to_global_phase(qasm_unitary,
                                                         unitary,
                                                         rtol=1e-8,
                                                         atol=1e-8)
    except Exception as ex:
        if qasm_unitary is not None:
            p_unitary, p_qasm_unitary = linalg.match_global_phase(
                unitary, qasm_unitary)
        else:
            p_unitary = None
            p_qasm_unitary = None
        raise AssertionError(
            'QASM be consistent with cirq.unitary(op) up to global phase.\n\n'
            'op:\n{}\n\n'
            'cirq.unitary(op):\n{}\n\n'
            'Generated QASM:\n\n{}\n\n'
            'Unitary of generated QASM:\n{}\n\n'
            'Phased matched cirq.unitary(op):\n{}\n\n'
            'Phased matched unitary of generated QASM:\n{}\n\n'
            'Underlying error:\n{}'.format(_indent(repr(op)),
                                           _indent(repr(unitary)),
                                           _indent(qasm),
                                           _indent(repr(qasm_unitary)),
                                           _indent(repr(p_unitary)),
                                           _indent(repr(p_qasm_unitary)),
                                           _indent(str(ex))))
Exemple #16
0
 def _unitary_(self) -> Union[np.ndarray, NotImplementedType]:
     sub_matrix = protocols.unitary(self.sub_gate, None)
     if sub_matrix is None:
         return NotImplemented
     return linalg.block_diag(np.eye(sub_matrix.shape[0]), sub_matrix)
Exemple #17
0
 def _unitary_(self) -> Union[np.ndarray, type(NotImplemented)]:
     if isinstance(self.half_turns, value.Symbol):
         return NotImplemented
     return protocols.unitary(ops.Rot11Gate(half_turns=self.half_turns))
Exemple #18
0
    def apply_unitary(self, op: 'cirq.Operation'):
        """Applies a unitary operation, mutating the object to represent the new state.

        op:
            The operation that mutates the object. Note that currently, only 1-
            and 2- qubit operations are currently supported.
        """

        old_inds = tuple(
            [self.i_str(self.qubit_map[qubit]) for qubit in op.qubits])
        new_inds = tuple(['new_' + old_ind for old_ind in old_inds])

        U = protocols.unitary(op).reshape(
            [qubit.dimension for qubit in op.qubits] * 2)
        U = qtn.Tensor(U, inds=(new_inds + old_inds))

        # TODO(tonybruguier): Explore using the Quimb's tensor network natively.

        if len(op.qubits) == 1:
            n = self.grouping[op.qubits[0]]

            self.M[n] = (U @ self.M[n]).reindex({new_inds[0]: old_inds[0]})
        elif len(op.qubits) == 2:
            n, p = [self.grouping[qubit] for qubit in op.qubits]

            if n == p:
                self.M[n] = (U @ self.M[n]).reindex({
                    new_inds[0]: old_inds[0],
                    new_inds[1]: old_inds[1]
                })
            else:
                self.num_svd_splits += 1
                # This is the index on which we do the contraction. We need to add it iff it's
                # the first time that we do the joining for that specific pair.
                mu_ind = self.mu_str(n, p)
                if mu_ind not in self.M[n].inds:
                    self.M[n].new_ind(mu_ind)
                if mu_ind not in self.M[p].inds:
                    self.M[p].new_ind(mu_ind)

                T = U @ self.M[n] @ self.M[p]

                left_inds = tuple(set(T.inds)
                                  & set(self.M[n].inds)) + (new_inds[0], )
                X, Y = T.split(
                    left_inds,
                    cutoff=self.rsum2_cutoff,
                    cutoff_mode='rsum2',
                    get='tensors',
                    absorb='both',
                    bond_ind=mu_ind,
                )

                self.M[n] = X.reindex({new_inds[0]: old_inds[0]})
                self.M[p] = Y.reindex({new_inds[1]: old_inds[1]})
        else:
            # NOTE(tonybruguier): There could be a way to handle higher orders. I think this could
            # involve HOSVDs:
            # https://en.wikipedia.org/wiki/Higher-order_singular_value_decomposition
            #
            # TODO(tonybruguier): Evaluate whether it's even useful to implement and learn more
            # about HOSVDs.
            raise ValueError('Can only handle 1 and 2 qubit operations')
Exemple #19
0
 def _unitary_(self):
     return protocols.unitary(self.kak)
Exemple #20
0
 def _mixture_(self) -> Sequence[Tuple[float, np.ndarray]]:
     return ((self._p_i, protocols.unitary(common_gates.I)),
             (self._p_x, protocols.unitary(pauli_gates.X)),
             (self._p_y, protocols.unitary(pauli_gates.Y)),
             (self._p_z, protocols.unitary(pauli_gates.Z)))
Exemple #21
0
def kak_decomposition(
    unitary_object: Union[np.ndarray, 'cirq.SupportsUnitary', 'cirq.Gate',
                          'cirq.Operation', KakDecomposition],
    *,
    rtol: float = 1e-5,
    atol: float = 1e-8,
    check_preconditions: bool = True,
) -> KakDecomposition:
    """Decomposes a 2-qubit unitary into 1-qubit ops and XX/YY/ZZ interactions.

    Args:
        unitary_object: The value to decompose. Can either be a 4x4 unitary
            matrix, or an object that has a 4x4 unitary matrix (via the
            `cirq.SupportsUnitary` protocol).
        rtol: Per-matrix-entry relative tolerance on equality.
        atol: Per-matrix-entry absolute tolerance on equality.
        check_preconditions: If set, verifies that the input corresponds to a
            4x4 unitary before decomposing.

    Returns:
        A `cirq.KakDecomposition` canonicalized such that the interaction
        coefficients x, y, z satisfy:

            0 ≤ abs(z2) ≤ y2 ≤ x2 ≤ π/4
            if x2 = π/4, z2 >= 0

    Raises:
        ValueError: Bad matrix.
        ArithmeticError: Failed to perform the decomposition.

    References:
        'An Introduction to Cartan's KAK Decomposition for QC Programmers'
        https://arxiv.org/abs/quant-ph/0507171
    """
    if isinstance(unitary_object, KakDecomposition):
        return unitary_object
    if isinstance(unitary_object, np.ndarray):
        mat = unitary_object
    else:
        mat = protocols.unitary(unitary_object)
    if check_preconditions and (
            mat.shape !=
        (4, 4) or not predicates.is_unitary(mat, rtol=rtol, atol=atol)):
        raise ValueError(
            f'Input must correspond to a 4x4 unitary matrix. Received matrix:\n{mat}'
        )

    # Diagonalize in magic basis.
    left, d, right = diagonalize.bidiagonalize_unitary_with_special_orthogonals(
        KAK_MAGIC_DAG @ mat @ KAK_MAGIC,
        atol=atol,
        rtol=rtol,
        check_preconditions=False)

    # Recover pieces.
    a1, a0 = so4_to_magic_su2s(left.T,
                               atol=atol,
                               rtol=rtol,
                               check_preconditions=False)
    b1, b0 = so4_to_magic_su2s(right.T,
                               atol=atol,
                               rtol=rtol,
                               check_preconditions=False)
    w, x, y, z = (KAK_GAMMA @ np.vstack(np.angle(d))).flatten()
    g = np.exp(1j * w)

    # Canonicalize.
    inner_cannon = kak_canonicalize_vector(x, y, z)

    b1 = np.dot(inner_cannon.single_qubit_operations_before[0], b1)
    b0 = np.dot(inner_cannon.single_qubit_operations_before[1], b0)
    a1 = np.dot(a1, inner_cannon.single_qubit_operations_after[0])
    a0 = np.dot(a0, inner_cannon.single_qubit_operations_after[1])
    return KakDecomposition(
        interaction_coefficients=inner_cannon.interaction_coefficients,
        global_phase=g * inner_cannon.global_phase,
        single_qubit_operations_before=(b1, b0),
        single_qubit_operations_after=(a1, a0),
    )
Exemple #22
0
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Iterator, List, Optional, TYPE_CHECKING

import math
import numpy as np
import scipy.linalg
from cirq import circuits, google, linalg, ops, optimizers, protocols
from cirq.google.ops import SycamoreGate
from cirq.google.optimizers.two_qubit_gates.gate_compilation import GateTabulation

if TYPE_CHECKING:
    import cirq

UNITARY_ZZ = np.kron(protocols.unitary(ops.Z), protocols.unitary(ops.Z))
PAULI_OPS = [
    np.eye(2),
    protocols.unitary(ops.X),
    protocols.unitary(ops.Y),
    protocols.unitary(ops.Z),
]


class ConvertToSycamoreGates(circuits.PointOptimizer):
    """Attempts to convert non-native gates into SycamoreGates.

    First, checks if the given operation is already a native sycamore operation.

    Second, checks if the operation has a known unitary. If so, and the gate
        is a 1-qubit or 2-qubit gate, then performs circuit synthesis of the
Exemple #23
0
def _translate_two_qubit_cirq_operation_to_braket_instruction(
    op: "cirq.Operation",
) -> List[Instruction]:
    """Translates a two-qubit Cirq operation to a (sequence of) Braket
    instruction(s) according to the following rules:

    1. Attempts to find a "standard translation" from Cirq to Braket.
        - e.g., checks if `op` is a CNOT and, if so, returns the Braket CNOT.

    2. If (1) is not successful, performs a KAK decomposition of the unitary of
    `op` to obtain a circuit

        ──A1──X^0.5───@───X^a───X──────────────────@───B1───
                      │         │                  │
        ──A2──────────X───Y^b───@───X^-0.5───Z^c───X───B2────

    where A1, A2, B1, and B2 are arbitrary single-qubit unitaries and a, b, c
    are floats.

    Args:
        op: Two-qubit Cirq operation to translate.
    """
    # Translate qubit indices.
    q1, q2 = [qubit.x for qubit in op.qubits]

    # Check common two-qubit gates.
    if isinstance(op.gate, cirq_ops.CNotPowGate) and np.isclose(
        abs(op.gate.exponent), 1.0
    ):
        return [Instruction(braket_gates.CNot(), [q1, q2])]
    elif isinstance(op.gate, cirq_ops.CZPowGate) and np.isclose(
        abs(op.gate.exponent), 1.0
    ):
        return [Instruction(braket_gates.CZ(), [q1, q2])]
    elif isinstance(op.gate, cirq_ops.ISwapPowGate) and np.isclose(
        op.gate.exponent, 1.0
    ):
        return [Instruction(braket_gates.ISwap(), [q1, q2])]
    elif isinstance(op.gate, cirq_ops.XXPowGate):
        return [
            Instruction(braket_gates.XX(op.gate.exponent * np.pi), [q1, q2])
        ]
    elif isinstance(op.gate, cirq_ops.YYPowGate):
        return [
            Instruction(braket_gates.YY(op.gate.exponent * np.pi), [q1, q2])
        ]
    elif isinstance(op.gate, cirq_ops.ZZPowGate):
        return [
            Instruction(braket_gates.ZZ(op.gate.exponent * np.pi), [q1, q2])
        ]

    # Arbitrary two-qubit unitary decomposition.
    kak = kak_decomposition(protocols.unitary(op))
    A1, A2 = kak.single_qubit_operations_before

    x, y, z = kak.interaction_coefficients
    a = x * -2 / np.pi + 0.5
    b = y * -2 / np.pi + 0.5
    c = z * -2 / np.pi + 0.5

    B1, B2 = kak.single_qubit_operations_after

    return [
        *_translate_one_qubit_cirq_operation_to_braket_instruction(A1, q1),
        *_translate_one_qubit_cirq_operation_to_braket_instruction(A2, q2),
        Instruction(braket_gates.Rx(0.5 * np.pi), q1),
        Instruction(braket_gates.CNot(), [q1, q2]),
        Instruction(braket_gates.Rx(a * np.pi), q1),
        Instruction(braket_gates.Ry(b * np.pi), q2),
        Instruction(braket_gates.CNot(), [q2, q1]),
        Instruction(braket_gates.Rx(-0.5 * np.pi), q2),
        Instruction(braket_gates.Rz(c * np.pi), q2),
        Instruction(braket_gates.CNot(), [q1, q2]),
        *_translate_one_qubit_cirq_operation_to_braket_instruction(B1, q1),
        *_translate_one_qubit_cirq_operation_to_braket_instruction(B2, q2),
    ]
Exemple #24
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
Exemple #25
0
    def apply_op(self, op: 'cirq.Operation', prng: np.random.RandomState):
        """Applies a unitary operation, mutating the object to represent the new state.

        op:
            The operation that mutates the object. Note that currently, only 1-
            and 2- qubit operations are currently supported.
        """

        old_inds = tuple(
            [self.i_str(self.qubit_map[qubit]) for qubit in op.qubits])
        new_inds = tuple(['new_' + old_ind for old_ind in old_inds])

        if protocols.has_unitary(op):
            U = protocols.unitary(op)
        else:
            mixtures = protocols.mixture(op)
            mixture_idx = int(
                prng.choice(len(mixtures),
                            p=[mixture[0] for mixture in mixtures]))
            U = mixtures[mixture_idx][1]
        U = qtn.Tensor(U.reshape([qubit.dimension for qubit in op.qubits] * 2),
                       inds=(new_inds + old_inds))

        # TODO(tonybruguier): Explore using the Quimb's tensor network natively.

        if len(op.qubits) == 1:
            n = self.grouping[op.qubits[0]]

            self.M[n] = (U @ self.M[n]).reindex({new_inds[0]: old_inds[0]})
        elif len(op.qubits) == 2:
            n, p = [self.grouping[qubit] for qubit in op.qubits]

            if n == p:
                self.M[n] = (U @ self.M[n]).reindex({
                    new_inds[0]: old_inds[0],
                    new_inds[1]: old_inds[1]
                })
            else:
                # This is the index on which we do the contraction. We need to add it iff it's
                # the first time that we do the joining for that specific pair.
                mu_ind = self.mu_str(n, p)
                if mu_ind not in self.M[n].inds:
                    self.M[n].new_ind(mu_ind)
                if mu_ind not in self.M[p].inds:
                    self.M[p].new_ind(mu_ind)

                T = U @ self.M[n] @ self.M[p]

                left_inds = tuple(set(T.inds)
                                  & set(self.M[n].inds)) + (new_inds[0], )
                X, Y = T.split(
                    left_inds,
                    method=self.simulation_options.method,
                    max_bond=self.simulation_options.max_bond,
                    cutoff=self.simulation_options.cutoff,
                    cutoff_mode=self.simulation_options.cutoff_mode,
                    get='tensors',
                    absorb='both',
                    bond_ind=mu_ind,
                )

                # Equations (13), (14), and (15):
                # TODO(tonybruguier): When Quimb 2.0.0 is released, the split()
                # function should have a 'renorm' that, when set to None, will
                # allow to compute e_n exactly as:
                # np.sum(abs((X @ Y).data) ** 2).real / np.sum(abs(T) ** 2).real
                #
                # The renormalization would then have to be done manually.
                #
                # However, for now, e_n are just the estimated value.
                e_n = self.simulation_options.cutoff
                self.estimated_gate_error_list.append(e_n)

                self.M[n] = X.reindex({new_inds[0]: old_inds[0]})
                self.M[p] = Y.reindex({new_inds[1]: old_inds[1]})
        else:
            # NOTE(tonybruguier): There could be a way to handle higher orders. I think this could
            # involve HOSVDs:
            # https://en.wikipedia.org/wiki/Higher-order_singular_value_decomposition
            #
            # TODO(tonybruguier): Evaluate whether it's even useful to implement and learn more
            # about HOSVDs.
            raise ValueError('Can only handle 1 and 2 qubit operations')
        return True
def _gate_seq_to_mats(gate_seq: Sequence[ops.Gate]) -> np.ndarray:
    mat_rep = protocols.unitary(gate_seq[0])
    for gate in gate_seq[1:]:
        mat_rep = np.dot(protocols.unitary(gate), mat_rep)
    return mat_rep
Exemple #27
0
 def _unitary_(self) -> Optional[np.ndarray]:
     if not self._has_unitary_():
         return None
     return linalg.kron(self.coefficient,
                        *[protocols.unitary(self[q]) for q in self.qubits])
 def _unitary_(self):
     if not self._has_unitary_():
         return NotImplemented
     return self.coefficient * linalg.kron(
         *[protocols.unitary(PAULI_GATES[p]) for p in self.pauli_mask])
Exemple #29
0
 def _unitary_(self) -> Union[np.ndarray, NotImplementedType]:
     return protocols.unitary(self.gate, default=None)
Exemple #30
0
 def _unitary_(self):
     return protocols.unitary(self.to_circuit())