Exemplo n.º 1
0
def unitary_to_rot(tape):
    """Quantum function transform to decomposes all instances of single-qubit :class:`~.QubitUnitary`
    operations to parametrized single-qubit operations.

    Diagonal operations will be converted to a single :class:`.RZ` gate, while non-diagonal
    operations will be converted to a :class:`.Rot` gate that implements the original operation
    up to a global phase.

    Args:
        qfunc (function): a quantum function

    **Example**

    Suppose we would like to apply the following unitary operation:

    .. code-block:: python3

        U = np.array([
            [-0.17111489+0.58564875j, -0.69352236-0.38309524j],
            [ 0.25053735+0.75164238j,  0.60700543-0.06171855j]
        ])

    The ``unitary_to_rot`` transform enables us to decompose
    such numerical operations (as well as unitaries that may be defined by parameters
    within the QNode, and instantiated therein), while preserving differentiability.


    .. code-block:: python3

        def qfunc():
            qml.QubitUnitary(U, wires=0)
            return qml.expval(qml.PauliZ(0))

    The original circuit is:

    >>> dev = qml.device('default.qubit', wires=1)
    >>> qnode = qml.QNode(qfunc, dev)
    >>> print(qml.draw(qnode)())
     0: ──U0──┤ ⟨Z⟩
    U0 =
    [[-0.17111489+0.58564875j -0.69352236-0.38309524j]
     [ 0.25053735+0.75164238j  0.60700543-0.06171855j]]

    We can use the transform to decompose the gate:

    >>> transformed_qfunc = unitary_to_rot(qfunc)
    >>> transformed_qnode = qml.QNode(transformed_qfunc, dev)
    >>> print(qml.draw(transformed_qnode)())
     0: ──Rot(-1.35, 1.83, -0.606)──┤ ⟨Z⟩

    """
    for op in tape.operations + tape.measurements:
        if isinstance(op, qml.QubitUnitary):
            # Only act on single-qubit unitary operations
            if qml.math.shape(op.parameters[0]) != (2, 2):
                continue

            zyz_decomposition(op.parameters[0], op.wires[0])
        else:
            qml.apply(op)
Exemplo n.º 2
0
    def test_zyz_decomposition_jax(self, U, expected_gate, expected_params):
        """Test that a one-qubit operation in JAX is correctly decomposed."""
        jax = pytest.importorskip("jax")

        # Enable float64 support
        from jax.config import config

        remember = config.read("jax_enable_x64")
        config.update("jax_enable_x64", True)

        U = jax.numpy.array(U, dtype=jax.numpy.complex128)

        obtained_gates = zyz_decomposition(U, wire="a")

        assert len(obtained_gates) == 1
        assert isinstance(obtained_gates[0], expected_gate)
        assert obtained_gates[0].wires == Wires("a")

        assert qml.math.allclose(qml.math.unwrap(obtained_gates[0].parameters),
                                 expected_params)

        if obtained_gates[0].num_params == 1:
            obtained_mat = qml.RZ(obtained_gates[0].parameters[0],
                                  wires=0).matrix
        else:
            obtained_mat = qml.Rot(*obtained_gates[0].parameters,
                                   wires=0).matrix

        assert check_matrix_equivalence(obtained_mat, U)
Exemplo n.º 3
0
    def test_zyz_decomposition_tf(self, U, expected_gate, expected_params):
        """Test that a one-qubit operation in Tensorflow is correctly decomposed."""
        tf = pytest.importorskip("tensorflow")

        U = tf.Variable(U, dtype=tf.complex128)

        obtained_gates = zyz_decomposition(U, wire="a")

        assert len(obtained_gates) == 1
        assert isinstance(obtained_gates[0], expected_gate)
        assert obtained_gates[0].wires == Wires("a")
        assert qml.math.allclose(qml.math.unwrap(obtained_gates[0].parameters),
                                 expected_params)

        print(qml.math.unwrap(obtained_gates[0].parameters))
        print(expected_params)

        if obtained_gates[0].num_params == 1:
            # With TF and RZ, need to cast since can't just unwrap
            obtained_mat = qml.RZ(obtained_gates[0].parameters[0].numpy(),
                                  wires=0).matrix
        else:
            obtained_mat = qml.Rot(*qml.math.unwrap(
                obtained_gates[0].parameters),
                                   wires=0).matrix

        assert check_matrix_equivalence(obtained_mat, U)
Exemplo n.º 4
0
    def test_zyz_decomposition(self, U, expected_gate, expected_params):
        """Test that a one-qubit matrix in isolation is correctly decomposed."""
        obtained_gates = zyz_decomposition(U, Wires("a"))

        assert len(obtained_gates) == 1

        assert isinstance(obtained_gates[0], expected_gate)
        assert obtained_gates[0].wires == Wires("a")
        assert qml.math.allclose(obtained_gates[0].parameters, expected_params)
Exemplo n.º 5
0
    def test_zyz_decomposition_tf(self, U, expected_gate, expected_params):
        """Test that a one-qubit operation in Tensorflow is correctly decomposed."""
        tf = pytest.importorskip("tensorflow")

        U = tf.Variable(U, dtype=tf.complex64)

        obtained_gates = zyz_decomposition(U, wire="a")

        assert len(obtained_gates) == 1
        assert isinstance(obtained_gates[0], expected_gate)
        assert obtained_gates[0].wires == Wires("a")
        assert qml.math.allclose(
            [x.numpy() for x in obtained_gates[0].parameters], expected_params)
Exemplo n.º 6
0
    def test_zyz_decomposition_jax(self, U, expected_gate, expected_params):
        """Test that a one-qubit operation in JAX is correctly decomposed."""
        jax = pytest.importorskip("jax")

        U = jax.numpy.array(U, dtype=jax.numpy.complex64)

        obtained_gates = zyz_decomposition(U, wire="a")

        assert len(obtained_gates) == 1
        assert isinstance(obtained_gates[0], expected_gate)
        assert obtained_gates[0].wires == Wires("a")
        assert qml.math.allclose(
            [jax.numpy.asarray(x) for x in obtained_gates[0].parameters],
            expected_params)
Exemplo n.º 7
0
    def test_zyz_decomposition_torch(self, U, expected_gate, expected_params):
        """Test that a one-qubit operation in Torch is correctly decomposed."""
        torch = pytest.importorskip("torch")

        U = torch.tensor(U, dtype=torch.complex64)

        obtained_gates = zyz_decomposition(U, wire="a")

        assert len(obtained_gates) == 1
        assert isinstance(obtained_gates[0], expected_gate)
        assert obtained_gates[0].wires == Wires("a")
        assert qml.math.allclose(
            [x.detach() for x in obtained_gates[0].parameters],
            expected_params)
Exemplo n.º 8
0
    def test_zyz_decomposition(self, U, expected_gate, expected_params):
        """Test that a one-qubit matrix in isolation is correctly decomposed."""
        obtained_gates = zyz_decomposition(U, Wires("a"))

        assert len(obtained_gates) == 1
        assert isinstance(obtained_gates[0], expected_gate)
        assert obtained_gates[0].wires == Wires("a")
        assert qml.math.allclose(obtained_gates[0].parameters, expected_params)

        if obtained_gates[0].num_params == 1:
            obtained_mat = qml.RZ(obtained_gates[0].parameters[0],
                                  wires=0).matrix
        else:
            obtained_mat = qml.Rot(*obtained_gates[0].parameters,
                                   wires=0).matrix

        assert check_matrix_equivalence(obtained_mat, U)
Exemplo n.º 9
0
    def test_zyz_decomposition_torch(self, U, expected_gate, expected_params):
        """Test that a one-qubit operation in Torch is correctly decomposed."""
        torch = pytest.importorskip("torch")

        U = torch.tensor(U, dtype=torch.complex128)

        obtained_gates = zyz_decomposition(U, wire="a")

        assert len(obtained_gates) == 1
        assert isinstance(obtained_gates[0], expected_gate)
        assert obtained_gates[0].wires == Wires("a")
        assert qml.math.allclose(qml.math.unwrap(obtained_gates[0].parameters),
                                 expected_params)

        if obtained_gates[0].num_params == 1:
            obtained_mat = qml.RZ(obtained_gates[0].parameters[0],
                                  wires=0).matrix
        else:
            obtained_mat = qml.Rot(*obtained_gates[0].parameters,
                                   wires=0).matrix

        assert check_matrix_equivalence(obtained_mat, qml.math.unwrap(U))
Exemplo n.º 10
0
 def test_zyz_decomposition_invalid_input(self):
     """Test that non-unitary operations throw errors when we try to decompose."""
     with pytest.raises(ValueError, match="Operator must be unitary"):
         zyz_decomposition(I + H, Wires("a"))
Exemplo n.º 11
0
def unitary_to_rot(tape):
    r"""Quantum function transform to decomposes all instances of single-qubit and
    select instances of two-qubit :class:`~.QubitUnitary` operations to
    parametrized single-qubit operations.

    For single-qubit gates, diagonal operations will be converted to a single
    :class:`.RZ` gate, while non-diagonal operations will be converted to a
    :class:`.Rot` gate that implements the original operation up to a global
    phase. Two-qubit gates will be decomposed according to the
    :func:`pennylane.transforms.two_qubit_decomposition` function.

    .. warning::

        This transform is not fully differentiable for 2-qubit ``QubitUnitary``
        operations. See usage details below.

    Args:
        qfunc (function): a quantum function

    **Example**

    Suppose we would like to apply the following unitary operation:

    .. code-block:: python3

        U = np.array([
            [-0.17111489+0.58564875j, -0.69352236-0.38309524j],
            [ 0.25053735+0.75164238j,  0.60700543-0.06171855j]
        ])

    The ``unitary_to_rot`` transform enables us to decompose such numerical
    operations while preserving differentiability.

    .. code-block:: python3

        def qfunc():
            qml.QubitUnitary(U, wires=0)
            return qml.expval(qml.PauliZ(0))

    The original circuit is:

    >>> dev = qml.device('default.qubit', wires=1)
    >>> qnode = qml.QNode(qfunc, dev)
    >>> print(qml.draw(qnode)())
     0: ──U0──┤ ⟨Z⟩
    U0 =
    [[-0.17111489+0.58564875j -0.69352236-0.38309524j]
     [ 0.25053735+0.75164238j  0.60700543-0.06171855j]]

    We can use the transform to decompose the gate:

    >>> transformed_qfunc = unitary_to_rot(qfunc)
    >>> transformed_qnode = qml.QNode(transformed_qfunc, dev)
    >>> print(qml.draw(transformed_qnode)())
     0: ──Rot(-1.35, 1.83, -0.606)──┤ ⟨Z⟩


    .. UsageDetails::

        This decomposition is not fully differentiable. We **can** differentiate
        with respect to input QNode parameters when they are not used to
        explicitly construct a :math:`4 \times 4` unitary matrix being
        decomposed. So for example, the following will work:

        .. code-block:: python3

            U = scipy.stats.unitary_group.rvs(4)

            def circuit(angles):
                qml.QubitUnitary(U, wires=["a", "b"])
                qml.RX(angles[0], wires="a")
                qml.RY(angles[1], wires="b")
                qml.CNOT(wires=["b", "a"])
                return qml.expval(qml.PauliZ(wires="a"))

            dev = qml.device('default.qubit', wires=["a", "b"])
            transformed_qfunc = qml.transforms.unitary_to_rot(circuit)
            transformed_qnode = qml.QNode(transformed_qfunc, dev)

        >>> g = qml.grad(transformed_qnode)
        >>> params = np.array([0.2, 0.3], requires_grad=True)
        >>> g(params)
        array([ 0.00296633, -0.29392145])

        However, the following example will **not** be differentiable:

        .. code-block:: python3

            def circuit(angles):
                z = angles[0]
                x = angles[1]

                Z_mat = np.array([[np.exp(-1j * z / 2), 0.0], [0.0, np.exp(1j * z / 2)]])

                c = np.cos(x / 2)
                s = np.sin(x / 2) * 1j
                X_mat = np.array([[c, -s], [-s, c]])

                U = np.kron(Z_mat, X_mat)

                qml.Hadamard(wires="a")

                # U depends on the input parameters
                qml.QubitUnitary(U, wires=["a", "b"])

                qml.CNOT(wires=["b", "a"])
                return qml.expval(qml.PauliX(wires="a"))
    """

    for op in tape.operations + tape.measurements:
        if isinstance(op, qml.QubitUnitary):
            # Single-qubit unitary operations
            if qml.math.shape(op.parameters[0]) == (2, 2):
                zyz_decomposition(op.parameters[0], op.wires[0])
            # Two-qubit unitary operations
            elif qml.math.shape(op.parameters[0]) == (4, 4):
                two_qubit_decomposition(op.parameters[0], op.wires)
            else:
                qml.apply(op)
        else:
            qml.apply(op)