def test_apply(self, gaussian_dev, tol):
        """Test the application of gates to a state"""

        # loop through all supported operations
        for gate_name, fn in gaussian_dev._operation_map.items():
            #log.debug("\tTesting %s gate...", gate_name)
            gaussian_dev.reset()

            # start in the displaced squeezed state
            alpha = 0.542 + 0.123j
            a = abs(alpha)
            phi_a = np.angle(alpha)
            r = 0.652
            phi_r = -0.124

            gaussian_dev.apply('DisplacedSqueezedState',
                               wires=Wires([0]),
                               par=[a, phi_a, r, phi_r])
            gaussian_dev.apply('DisplacedSqueezedState',
                               wires=Wires([1]),
                               par=[a, phi_a, r, phi_r])

            # get the equivalent pennylane operation class
            op = qml.ops.__getattribute__(gate_name)
            # the list of wires to apply the operation to
            w = list(range(op.num_wires))

            if op.par_domain == 'A':
                # the parameter is an array
                if gate_name == 'GaussianState':
                    p = [
                        np.array([0.432, 0.123, 0.342, 0.123]),
                        np.diag([0.5234] * 4)
                    ]
                    w = list(range(2))
                    expected_out = p
                elif gate_name == 'Interferometer':
                    w = list(range(2))
                    p = [U]
                    S = fn(*p)
                    expected_out = S @ gaussian_dev._state[
                        0], S @ gaussian_dev._state[1] @ S.T
            else:
                # the parameter is a float
                p = [0.432423, -0.12312, 0.324, 0.751][:op.num_params]

                if gate_name == 'Displacement':
                    alpha = p[0] * np.exp(1j * p[1])
                    state = gaussian_dev._state
                    mu = state[0].copy()
                    mu[w[0]] += alpha.real * np.sqrt(2 * hbar)
                    mu[w[0] + 2] += alpha.imag * np.sqrt(2 * hbar)
                    expected_out = mu, state[1]
                elif 'State' in gate_name:
                    mu, cov = fn(*p, hbar=hbar)
                    expected_out = gaussian_dev._state
                    expected_out[0][[w[0], w[0] + 2]] = mu

                    ind = np.concatenate(
                        [np.array([w[0]]),
                         np.array([w[0]]) + 2])
                    rows = ind.reshape(-1, 1)
                    cols = ind.reshape(1, -1)
                    expected_out[1][rows, cols] = cov
                else:
                    # if the default.gaussian is an operation accepting parameters,
                    # initialise it using the parameters generated above.
                    S = fn(*p)

                    # calculate the expected output
                    if op.num_wires == 1:
                        # reorder from symmetric ordering to xp-ordering
                        S = block_diag(
                            S, np.identity(2))[:, [0, 2, 1, 3]][[0, 2, 1, 3]]

                    expected_out = S @ gaussian_dev._state[
                        0], S @ gaussian_dev._state[1] @ S.T

            gaussian_dev.apply(gate_name, wires=Wires(w), par=p)

            # verify the device is now in the expected state
            assert gaussian_dev._state[0] == pytest.approx(expected_out[0],
                                                           abs=tol)
            assert gaussian_dev._state[1] == pytest.approx(expected_out[1],
                                                           abs=tol)
예제 #2
0
class TestMarginalProb:
    """Test the marginal_prob method"""
    @pytest.mark.parametrize(
        "wires, inactive_wires",
        [
            ([0], [1, 2]),
            ([1], [0, 2]),
            ([2], [0, 1]),
            ([0, 1], [2]),
            ([0, 2], [1]),
            ([1, 2], [0]),
            ([0, 1, 2], []),
            (Wires([0]), [1, 2]),
            (Wires([0, 1]), [2]),
            (Wires([0, 1, 2]), []),
        ],
    )
    def test_correct_arguments_for_marginals(
            self, mock_qubit_device_with_original_statistics, mocker, wires,
            inactive_wires, tol):
        """Test that the correct arguments are passed to the marginal_prob method"""

        # Generate probabilities
        probs = np.array([random() for i in range(2**3)])
        probs /= sum(probs)

        spy = mocker.spy(np, "sum")
        dev = mock_qubit_device_with_original_statistics(wires=3)
        res = dev.marginal_prob(probs, wires=wires)
        array_call = spy.call_args[0][0]
        axis_call = spy.call_args[1]["axis"]

        assert np.allclose(array_call.flatten(), probs, atol=tol, rtol=0)
        assert axis_call == tuple(inactive_wires)

    marginal_test_data = [
        (np.array([0.1, 0.2, 0.3, 0.4]), np.array([0.4, 0.6]), [1]),
        (np.array([0.1, 0.2, 0.3, 0.4]), np.array([0.3, 0.7]), Wires([0])),
        (
            np.array([
                0.17794671,
                0.06184147,
                0.21909549,
                0.04932204,
                0.19595214,
                0.19176834,
                0.08495311,
                0.0191207,
            ]),
            np.array([0.3970422, 0.28090525, 0.11116351, 0.21088904]),
            [2, 0],
        ),
    ]

    @pytest.mark.parametrize("probs, marginals, wires", marginal_test_data)
    def test_correct_marginals_returned(
            self, mock_qubit_device_with_original_statistics, probs, marginals,
            wires, tol):
        """Test that the correct marginals are returned by the marginal_prob method"""
        num_wires = int(np.log2(len(probs)))
        dev = mock_qubit_device_with_original_statistics(num_wires)
        res = dev.marginal_prob(probs, wires=wires)
        assert np.allclose(res, marginals, atol=tol, rtol=0)

    @pytest.mark.parametrize("probs, marginals, wires", marginal_test_data)
    def test_correct_marginals_returned_wires_none(
            self, mock_qubit_device_with_original_statistics, probs, marginals,
            wires, tol):
        """Test that passing wires=None simply returns the original probability."""
        num_wires = int(np.log2(len(probs)))
        dev = mock_qubit_device_with_original_statistics(wires=num_wires)
        dev.num_wires = num_wires

        res = dev.marginal_prob(probs, wires=None)
        assert np.allclose(res, probs, atol=tol, rtol=0)
예제 #3
0
def circuit(ops, obs):
    """A fixture of a circuit generated based on the queue and obs fixtures above."""
    circuit = CircuitGraph(ops, obs, Wires([0, 1, 2]))
    return circuit
예제 #4
0
파일: uccsd.py 프로젝트: nvitucci/pennylane
def UCCSD(weights, wires, ph=None, pphh=None, init_state=None):
    r"""Implements the Unitary Coupled-Cluster Singles and Doubles (UCCSD) ansatz.

    The UCCSD ansatz calls the
    :func:`~.SingleExcitationUnitary` and :func:`~.DoubleExcitationUnitary`
    templates to exponentiate the coupled-cluster excitation operator. UCCSD is a VQE ansatz
    commonly used to run quantum chemistry simulations.

    The UCCSD unitary, within the first-order Trotter approximation, is given by:

    .. math::

        \hat{U}(\vec{\theta}) =
        \prod_{p > r} \mathrm{exp} \Big\{\theta_{pr}
        (\hat{c}_p^\dagger \hat{c}_r-\mathrm{H.c.}) \Big\}
        \prod_{p > q > r > s} \mathrm{exp} \Big\{\theta_{pqrs}
        (\hat{c}_p^\dagger \hat{c}_q^\dagger \hat{c}_r \hat{c}_s-\mathrm{H.c.}) \Big\}

    where :math:`\hat{c}` and :math:`\hat{c}^\dagger` are the fermionic annihilation and
    creation operators and the indices :math:`r, s` and :math:`p, q` run over the occupied and
    unoccupied molecular orbitals, respectively. Using the `Jordan-Wigner transformation
    <https://arxiv.org/abs/1208.5986>`_ the UCCSD unitary defined above can be written in terms
    of Pauli matrices as follows (for more details see
    `arXiv:1805.04340 <https://arxiv.org/abs/1805.04340>`_):

    .. math::

        \hat{U}(\vec{\theta}) = && \prod_{p > r} \mathrm{exp} \Big\{ \frac{i\theta_{pr}}{2}
        \bigotimes_{a=r+1}^{p-1} \hat{Z}_a (\hat{Y}_r \hat{X}_p - \mathrm{H.c.}) \Big\} \\
        && \times \prod_{p > q > r > s} \mathrm{exp} \Big\{ \frac{i\theta_{pqrs}}{8}
        \bigotimes_{b=s+1}^{r-1} \hat{Z}_b \bigotimes_{a=q+1}^{p-1}
        \hat{Z}_a (\hat{X}_s \hat{X}_r \hat{Y}_q \hat{X}_p +
        \hat{Y}_s \hat{X}_r \hat{Y}_q \hat{Y}_p +
        \hat{X}_s \hat{Y}_r \hat{Y}_q \hat{Y}_p +
        \hat{X}_s \hat{X}_r \hat{X}_q \hat{Y}_p -
        \{\mathrm{H.c.}\}) \Big\}.

    Args:
        weights (array): length ``len(ph)+len(pphh)`` vector containing the parameters
            :math:`\theta_{pr}` and :math:`\theta_{pqrs}` entering the Z rotation in
            :func:`~.SingleExcitationUnitary`
            and
            :func:`~.DoubleExcitationUnitary`. These parameters are precisely the coupled-cluster
            amplitudes that need to be optimized for each configuration generated by the
            excitation operator.
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
            a Wires object.
        ph (Sequence[list]): Sequence of two-element lists containing the indices ``r, p`` that
            define the 1particle-1hole (ph) configuration :math:`\vert \mathrm{ph} \rangle =
            \hat{c}_p^\dagger \hat{c}_r \vert \mathrm{HF} \rangle`,
            where :math:`\vert \mathrm{HF} \rangle` denotes the Hartee-Fock (HF) reference state.
        pphh (Sequence[list]): Sequence of four-elements lists containing the indices
            ``s, r, q, p`` that define the 2particle-2hole configuration (pphh)
            :math:`\vert \mathrm{pphh} \rangle = \hat{c}_p^\dagger \hat{c}_q^\dagger \hat{c}_r
            \hat{c}_s \vert \mathrm{HF} \rangle`.
        init_state (array[int]): Length ``len(wires)`` occupation-number vector representing the
            HF state. ``init_state`` is used to initialize the qubit register.

    Raises:
        ValueError: if inputs do not have the correct format

    .. UsageDetails::

        Notice that:

        #. The number of wires has to be equal to the number of spin-orbitals included in
           the active space.

        #. The ``ph`` and ``pphh`` lists can be generated with the function
           :func:`~.sd_excitations`. See example below.

        #. The vector of parameters ``weights`` is a one-dimensional array of size
           ``len(ph)+len(pphh)``


        An example of how to use this template is shown below:

        .. code-block:: python

            import pennylane as qml
            from pennylane.templates import UCCSD

            # Number of active electrons
            n_active_electrons = 2

            # Number of active spin-orbitals
            n_active_orbitals = 4

            # Set the ``ref_state`` array to encode the HF state
            ref_state = qml.qchem.hf_state(n_active_electrons, n_active_orbitals)

            # Spin-projection selection rule to generate the excitations
            DSz = 0

            # Generate the particle-hole configurations
            ph, pphh = qml.qchem.sd_excitations(n_active_electrons, n_active_orbitals, DS=DSz)

            dev = qml.device('default.qubit', wires=n_active_orbitals)

            @qml.qnode(dev)
            def circuit(weights, wires, ph=ph, pphh=pphh, init_state=ref_state):
                UCCSD(weights, wires, ph=ph, pphh=pphh, init_state=ref_state)
                return qml.expval(qml.PauliZ(0))

            params = np.random.normal(0, np.pi, len(ph) + len(pphh))
            print(circuit(params, wires, ph=ph, pphh=pphh, init_state=ref_state))

    """

    ##############
    # Input checks

    wires = Wires(wires)

    if (not ph) and (not pphh):
        raise ValueError(
            "'ph' and 'pphh' lists can not be both empty; got ph={}, pphh={}".format(ph, pphh)
        )

    check_type(ph, [list], msg="'ph' must be a list; got {}".format(ph))
    expected_shape = (2,)
    for i_ph in ph:
        check_type(i_ph, [list], msg="Each element of 'ph' must be a list; got {}".format(i_ph))
        check_shape(
            i_ph,
            expected_shape,
            msg="Elements of 'ph' must be of shape {}; got {}".format(
                expected_shape, get_shape(i_ph)
            ),
        )
        for i in i_ph:
            check_type(
                i, [int], msg="Each element of 'ph' must be a list of integers; got {}".format(i_ph)
            )

    check_type(pphh, [list], msg="'pphh' must be a list; got {}".format(pphh))
    expected_shape = (4,)
    for i_pphh in pphh:
        check_type(
            i_pphh, [list], msg="Each element of 'pphh' must be a list; got {}".format(i_pphh)
        )
        check_shape(
            i_pphh,
            expected_shape,
            msg="Elements of 'pphh' must be of shape {}; got {}".format(
                expected_shape, get_shape(i_pphh)
            ),
        )
        for i in i_pphh:
            check_type(
                i,
                [int],
                msg="Each element of 'pphh' must be a list of integers; got {}".format(i_pphh),
            )

    check_type(
        init_state,
        [np.ndarray],
        msg="'init_state' must be a Numpy array; got {}".format(init_state),
    )
    for i in init_state:
        check_type(
            i,
            [int, np.int64],
            msg="Elements of 'init_state' must be integers; got {}".format(init_state),
        )

    expected_shape = (len(ph) + len(pphh),)
    check_shape(
        weights,
        expected_shape,
        msg="'weights' must be of shape {}; got {}".format(expected_shape, get_shape(weights)),
    )

    expected_shape = (len(wires),)
    check_shape(
        init_state,
        expected_shape,
        msg="'init_state' must be of shape {}; got {}".format(
            expected_shape, get_shape(init_state)
        ),
    )

    ###############

    wires = wires.tolist()  # TODO: Remove when ops accept wires

    qml.BasisState(np.flip(init_state), wires=wires)

    for d, i_pphh in enumerate(pphh):
        DoubleExcitationUnitary(weights[len(ph) + d], wires=i_pphh)

    for s, i_ph in enumerate(ph):
        SingleExcitationUnitary(weights[s], wires=i_ph)
예제 #5
0
    def marginal_prob(self, prob, wires=None):
        r"""Return the marginal probability of the computational basis
        states by summing the probabiliites on the non-specified wires.

        If no wires are specified, then all the basis states representable by
        the device are considered and no marginalization takes place.

        .. note::

            If the provided wires are not in the order as they appear on the device,
            the returned marginal probabilities take this permutation into account.

            For example, if the addressable wires on this device are ``Wires([0, 1, 2])`` and
            this function gets passed ``wires=[2, 0]``, then the returned marginal
            probability vector will take this 'reversal' of the two wires
            into account:

            .. math::

                \mathbb{P}^{(2, 0)}
                            = \left[
                               |00\rangle, |10\rangle, |01\rangle, |11\rangle
                              \right]

        Args:
            prob: The probabilities to return the marginal probabilities
                for
            wires (Iterable[Number, str], Number, str, Wires): wires to return
                marginal probabilities for. Wires not provided
                are traced out of the system.

        Returns:
            array[float]: array of the resulting marginal probabilities.
        """

        if wires is None:
            # no need to marginalize
            return prob

        wires = Wires(wires)
        # determine which subsystems are to be summed over
        inactive_wires = Wires.unique_wires([self.wires, wires])

        # translate to wire labels used by device
        device_wires = self.map_wires(wires)
        inactive_device_wires = self.map_wires(inactive_wires)

        # reshape the probability so that each axis corresponds to a wire
        prob = self._reshape(prob, [2] * self.num_wires)

        # sum over all inactive wires
        # hotfix to catch when default.qubit uses this method
        # since then device_wires is a list
        if isinstance(inactive_device_wires, Wires):
            prob = self._flatten(
                self._reduce_sum(prob, inactive_device_wires.labels))
        else:
            prob = self._flatten(self._reduce_sum(prob, inactive_device_wires))

        # The wires provided might not be in consecutive order (i.e., wires might be [2, 0]).
        # If this is the case, we must permute the marginalized probability so that
        # it corresponds to the orders of the wires passed.
        num_wires = len(device_wires)
        basis_states = self.generate_basis_states(num_wires)
        basis_states = basis_states[:, np.argsort(np.argsort(device_wires))]

        powers_of_two = 2**np.arange(len(device_wires))[::-1]
        perm = basis_states @ powers_of_two
        return self._gather(prob, perm)
예제 #6
0
    def test_creation_from_single_object(self, wire):
        """Tests that a Wires object can be created from a non-iterable object
        representing a single wire index."""

        wires = Wires(wire)
        assert wires.labels == (wire, )
예제 #7
0
    def test_error_for_repeated_wires(self, iterable):
        """Tests that a Wires object cannot be created from iterables with repeated indices."""

        with pytest.raises(WireError, match="Wires must be unique"):
            Wires(iterable)
예제 #8
0
    def test_length(self, iterable):
        """Tests that a Wires object returns the correct length."""

        wires = Wires(iterable)
        assert len(wires) == len(iterable)
예제 #9
0
 def test_add_two_wires_objects(self):
     """Tests that wires objects add correctly."""
     wires1 = Wires([4, 0, 1])
     wires2 = Wires([1, 2])
     assert wires1 + wires2 == Wires([4, 0, 1, 2])
예제 #10
0
def broadcast(unitary, wires, pattern, parameters=None, kwargs=None):
    r"""Applies a unitary multiple times to a specific pattern of wires.

    The unitary, defined by the argument ``unitary``, is either a quantum operation
    (such as :meth:`~.pennylane.ops.RX`), or a
    user-supplied template. Depending on the chosen pattern, ``unitary`` is applied to a wire or a subset of wires:

    * ``pattern="single"`` applies a single-wire unitary to each one of the :math:`M` wires:

      .. figure:: ../../_static/templates/broadcast_single.png
            :align: center
            :width: 20%
            :target: javascript:void(0);

    * ``pattern="double"`` applies a two-wire unitary to :math:`\lfloor \frac{M}{2} \rfloor`
      subsequent pairs of wires:

      .. figure:: ../../_static/templates/broadcast_double.png
          :align: center
          :width: 20%
          :target: javascript:void(0);

    * ``pattern="double_odd"`` applies a two-wire unitary to :math:`\lfloor \frac{M-1}{2} \rfloor`
      subsequent pairs of wires, starting with the second wire:

      .. figure:: ../../_static/templates/broadcast_double_odd.png
          :align: center
          :width: 20%
          :target: javascript:void(0);

    * ``pattern="chain"`` applies a two-wire unitary to all :math:`M-1` neighbouring pairs of wires:

      .. figure:: ../../_static/templates/broadcast_chain.png
          :align: center
          :width: 20%
          :target: javascript:void(0);

    * ``pattern="ring"`` applies a two-wire unitary to all :math:`M` neighbouring pairs of wires,
      where the last wire is considered to be a neighbour to the first one:

      .. figure:: ../../_static/templates/broadcast_ring.png
          :align: center
          :width: 20%
          :target: javascript:void(0);

      .. note:: For 2 wires, the ring pattern is automatically replaced by ``pattern = 'chain'`` to avoid
                a mere repetition of the unitary.

    * ``pattern="pyramid"`` applies a two-wire unitary to wire pairs shaped in a pyramid declining to the right:

      .. figure:: ../../_static/templates/broadcast_pyramid.png
          :align: center
          :width: 20%
          :target: javascript:void(0);

    * ``pattern="all_to_all"`` applies a two-wire unitary to wire pairs that connect all wires to each other:

      .. figure:: ../../_static/templates/broadcast_alltoall.png
          :align: center
          :width: 20%
          :target: javascript:void(0);

    * A custom pattern can be passed by providing a list of wire lists to ``pattern``. The ``unitary`` is applied
      to each set of wires specified in the list.

      .. figure:: ../../_static/templates/broadcast_custom.png
          :align: center
          :width: 20%
          :target: javascript:void(0);

    Each ``unitary`` may depend on a different set of parameters. These are passed as a list by the ``parameters``
    argument.

    For more details, see *Usage Details* below.

    Args:
        unitary (func): quantum gate or template
        pattern (str): specifies the wire pattern of the broadcast
        parameters (list): sequence of parameters for each gate applied
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
            a Wires object.
        kwargs (dict): dictionary of auxilliary parameters for ``unitary``

    Raises:
        ValueError: if inputs do not have the correct format

    .. UsageDetails::

        **Broadcasting single gates**

        In the simplest case the unitary is typically an :meth:`~.pennylane.operation.Operation` object
        implementing a quantum gate.

        .. code-block:: python

            import pennylane as qml
            from pennylane import broadcast

            dev = qml.device('default.qubit', wires=3)

            @qml.qnode(dev)
            def circuit(pars):
                broadcast(unitary=qml.RX, pattern="single", wires=[0,1,2], parameters=pars)
                return qml.expval(qml.PauliZ(0))

            circuit([1, 1, 2])

        This is equivalent to the following circuit:

        .. code-block:: python

            @qml.qnode(dev)
            def circuit(pars):
                qml.RX(pars[0], wires=[0])
                qml.RX(pars[1], wires=[1])
                qml.RX(pars[2], wires=[2])
                return qml.expval(qml.PauliZ(0))

            circuit([1, 1, 2])

        **Broadcasting templates**

        Alternatively, one can broadcast a built-in or user-defined template:

        .. code-block:: python

            from pennylane.templates import template

            @template
            def mytemplate(pars, wires):
                qml.Hadamard(wires=wires)
                qml.RY(pars, wires=wires)

            dev = qml.device('default.qubit', wires=3)

            @qml.qnode(dev)
            def circuit(pars):
                broadcast(unitary=mytemplate, pattern="single", wires=[0,1,2], parameters=pars)
                return qml.expval(qml.PauliZ(0))

            print(circuit([1, 1, 0.1]))

        **Constant unitaries**

        If the ``unitary`` argument does not take parameters, no ``parameters`` argument is passed to
        :func:`~.pennylane.broadcast`:

        .. code-block:: python

            dev = qml.device('default.qubit', wires=3)

            @qml.qnode(dev)
            def circuit():
                broadcast(unitary=qml.Hadamard, pattern="single", wires=[0,1,2])
                return qml.expval(qml.PauliZ(0))

            circuit()

        **Multiple parameters in unitary**

        The unitary, whether it is a single gate or a user-defined template,
        can take multiple parameters. For example:

        .. code-block:: python

            from pennylane.templates import template

            @template
            def mytemplate(pars1, pars2, wires):
                qml.Hadamard(wires=wires)
                qml.RY(pars1, wires=wires)
                qml.RX(pars2, wires=wires)

            @qml.qnode(dev)
            def circuit(pars):
                broadcast(unitary=mytemplate, pattern="single", wires=[0,1,2], parameters=pars)
                return qml.expval(qml.PauliZ(0))

            circuit([[1, 1], [2, 1], [0.1, 1]])

        In general, the unitary takes D parameters and **must** have the following signature:

        .. code-block:: python

            unitary(parameter1, parameter2, ... parameterD, wires, **kwargs)

        If ``unitary`` does not depend on parameters (:math:`D=0`), the signature is

        .. code-block:: python

            unitary(wires, **kwargs)

        As a result, ``parameters`` must be a list or array of length-:math:`D` lists or arrays.

        If :math:`D` becomes large, the signature can be simplified by wrapping each entry in ``parameters``:

        .. code-block:: python

            @template
            def mytemplate(pars, wires):
                qml.Hadamard(wires=wires)
                qml.RY(pars[0], wires=wires)
                qml.RX(pars[1], wires=wires)

            @qml.qnode(dev)
            def circuit(pars):
                broadcast(unitary=mytemplate, pattern="single", wires=[0,1,2], parameters=pars)
                return qml.expval(qml.PauliZ(0))

            print(circuit([[[1, 1]], [[2, 1]], [[0.1, 1]]]))

        If the number of parameters for each wire does not match the unitary, an error gets thrown:

        .. code-block:: python

            @template
            def mytemplate(pars1, pars2, wires):
                qml.Hadamard(wires=wires)
                qml.RY(pars1, wires=wires)
                qml.RX(pars2, wires=wires)

            @qml.qnode(dev)
            def circuit(pars):
                broadcast(unitary=mytemplate, pattern="single", wires=[0, 1, 2], parameters=pars)
                return qml.expval(qml.PauliZ(0))

        >>> circuit([1, 2, 3]))
        TypeError: mytemplate() missing 1 required positional argument: 'pars2'

        **Keyword arguments**

        The unitary can be a template that takes additional keyword arguments.

        .. code-block:: python

            @template
            def mytemplate(wires, h=True):
                if h:
                    qml.Hadamard(wires=wires)
                qml.T(wires=wires)

            @qml.qnode(dev)
            def circuit(hadamard=None):
                broadcast(unitary=mytemplate, pattern="single", wires=[0, 1, 2], kwargs={'h': hadamard})
                return qml.expval(qml.PauliZ(0))

            circuit(hadamard=False)

        **Different patterns**

        The basic usage of the different patterns works as follows:

        * Double pattern

          .. code-block:: python

              dev = qml.device('default.qubit', wires=4)

              @qml.qnode(dev)
              def circuit(pars):
                  broadcast(unitary=qml.CRot, pattern='double',
                            wires=[0,1,2,3], parameters=pars)
                  return qml.expval(qml.PauliZ(0))

              pars1 = [-1, 2.5, 3]
              pars2 = [-1, 4, 2]

              circuit([pars1, pars2])

        * Double-odd pattern

          .. code-block:: python

              dev = qml.device('default.qubit', wires=4)

              @qml.qnode(dev)
              def circuit(pars):
                  broadcast(unitary=qml.CRot, pattern='double_odd',
                            wires=[0,1,2,3], parameters=pars)
                  return qml.expval(qml.PauliZ(0))

              pars1 = [-5.3, 2.3, 3]

              circuit([pars1])

        * Chain pattern

          .. code-block:: python

              dev = qml.device('default.qubit', wires=4)

              @qml.qnode(dev)
              def circuit(pars):
                  broadcast(unitary=qml.CRot, pattern='chain',
                            wires=[0,1,2,3], parameters=pars)
                  return qml.expval(qml.PauliZ(0))

              pars1 = [1.8, 2, 3]
              pars2 = [-1, 3, 1]
              pars3 = [2, -1.2, 4]

              circuit([pars1, pars2, pars3])

        * Ring pattern

          In general, the number of parameter sequences has to match
          the number of wires:

          .. code-block:: python

              dev = qml.device('default.qubit', wires=3)

              @qml.qnode(dev)
              def circuit(pars):
                  broadcast(unitary=qml.CRot, pattern='ring',
                            wires=[0,1,2], parameters=pars)
                  return qml.expval(qml.PauliZ(0))

              pars1 = [1, -2.2, 3]
              pars2 = [-1, 3, 1]
              pars3 = [2.6, 1, 4]

              circuit([pars1, pars2, pars3])

          However, there is an exception for 2 wires, where only one set of parameters is needed.
          This avoids repeating a gate over the
          same wires twice:

          .. code-block:: python

              dev = qml.device('default.qubit', wires=2)

              @qml.qnode(dev)
              def circuit(pars):
                  broadcast(unitary=qml.CRot, pattern='ring',
                            wires=[0,1], parameters=pars)
                  return qml.expval(qml.PauliZ(0))

              pars1 = [-3.2, 2, 1.2]

              circuit([pars1])

        * Pyramid pattern

          .. code-block:: python

              dev = qml.device('default.qubit', wires=4)

              @qml.qnode(dev)
              def circuit(pars):
                  broadcast(unitary=qml.CRot, pattern='pyramid',
                            wires=[0,1,2,3], parameters=pars)
                  return qml.expval(qml.PauliZ(0))

              pars1 = [1.1, 2, 3]
              pars2 = [-1, 3, 1]
              pars3 = [2, 1, 4.2]

              circuit([pars1, pars2, pars3])

        * All-to-all pattern

          .. code-block:: python

              dev = qml.device('default.qubit', wires=4)

              @qml.qnode(dev)
              def circuit(pars):
                  broadcast(unitary=qml.CRot, pattern='ring',
                            wires=[0,1,2,3], parameters=pars)
                  return qml.expval(qml.PauliZ(0))

              pars1 = [1, 2, 3]
              pars2 = [-1, 3, 1]
              pars3 = [2, 1, 4]
              pars4 = [-1, -2, -3]
              pars5 = [2, 1, 4]
              pars6 = [3, -2, -3]

              circuit([pars1, pars2, pars3, pars4, pars5, pars6])

        * Custom pattern

          For a custom pattern, the wire lists for each application of the unitary is
          passed to ``pattern``:

          .. code-block:: python

              dev = qml.device('default.qubit', wires=5)

              pattern = [[0, 1], [3, 4]]

              @qml.qnode(dev)
              def circuit():
                  broadcast(unitary=qml.CNOT, pattern=pattern,
                            wires=range(5))
                  return qml.expval(qml.PauliZ(0))

              circuit()

          When using a parametrized unitary, make sure that the number of wire lists in ``pattern`` corresponds to the
          number of parameters in ``parameters``.

          .. code-block:: python

                pattern = [[0, 1], [3, 4]]

                @qml.qnode(dev)
                def circuit(pars):
                    broadcast(unitary=qml.CRot, pattern=pattern,
                              wires=range(5), parameters=pars)
                    return qml.expval(qml.PauliZ(0))

                pars1 = [1, 2, 3]
                pars2 = [-1, 3, 1]
                pars = [pars1, pars2]

                assert len(pars) == len(pattern)

                circuit(pars)
    """

    OPTIONS = [
        "single", "double", "double_odd", "chain", "ring", "pyramid",
        "all_to_all", "custom"
    ]

    #########
    # Input checks

    wires = Wires(wires)

    check_type(
        parameters,
        [Iterable, type(None)],
        msg="'parameters' must be either of type None or "
        "Iterable; got {}".format(type(parameters)),
    )

    if kwargs is None:
        kwargs = {}

    check_type(
        kwargs,
        [dict],
        msg="'kwargs' must be a dictionary; got {}".format(type(kwargs)),
    )

    custom_pattern = None

    if isinstance(pattern, str):
        check_is_in_options(
            pattern,
            OPTIONS,
            msg="did not recognize option {} for 'pattern'".format(pattern),
        )
    else:
        # turn custom pattern into list of Wires objects
        custom_pattern = [Wires(w) for w in pattern]
        # set "pattern" to "custom", indicating that custom settings have to be used
        pattern = "custom"

    n_parameters = {
        "single":
        len(wires),
        "double":
        0 if len(wires) in [0, 1] else len(wires) // 2,
        "double_odd":
        0 if len(wires) in [0, 1] else (len(wires) - 1) // 2,
        "chain":
        0 if len(wires) in [0, 1] else len(wires) - 1,
        "ring":
        0 if len(wires) in [0, 1] else (1 if len(wires) == 2 else len(wires)),
        "pyramid":
        0 if len(wires) in [0, 1] else sum(i + 1
                                           for i in range(len(wires) // 2)),
        "all_to_all":
        0 if len(wires) in [0, 1] else len(wires) * (len(wires) - 1) // 2,
        "custom":
        len(custom_pattern) if custom_pattern is not None else None,
    }

    # check that there are enough parameters for pattern
    if parameters is not None:
        shape = get_shape(parameters)

        # specific error message for ring edge case of 2 wires
        if (pattern == "ring") and (len(wires) == 2) and (shape[0] != 1):
            raise ValueError(
                "the ring pattern with 2 wires is an exception and only applies one unitary"
            )

        if shape[0] != n_parameters[pattern]:
            raise ValueError(
                "'parameters' must contain entries for {} unitaries; got {} entries"
                .format(n_parameters[pattern], shape[0]))

        # repackage for consistent unpacking
        if len(shape) == 1:
            parameters = [[p] for p in parameters]
    else:
        parameters = [[] for _ in range(n_parameters[pattern])]

    #########

    # define wire sequences for patterns
    wire_sequence = {
        "single": [wires[i] for i in range(len(wires))],
        "double":
        [wires.subset([i, i + 1]) for i in range(0,
                                                 len(wires) - 1, 2)],
        "double_odd":
        [wires.subset([i, i + 1]) for i in range(1,
                                                 len(wires) - 1, 2)],
        "chain": [wires.subset([i, i + 1]) for i in range(len(wires) - 1)],
        "ring":
        wires_ring(wires),
        "pyramid":
        wires_pyramid(wires),
        "all_to_all":
        wires_all_to_all(wires),
        "custom":
        custom_pattern,
    }

    # broadcast the unitary
    for wires, pars in zip(wire_sequence[pattern], parameters):
        wires = wires.tolist(
        )  # TODO: Delete once operator takes Wires objects
        unitary(*pars, wires=wires, **kwargs)
예제 #11
0
def RandomLayers(weights,
                 wires,
                 ratio_imprim=0.3,
                 imprimitive=CNOT,
                 rotations=None,
                 seed=42):
    r"""Layers of randomly chosen single qubit rotations and 2-qubit entangling gates, acting
    on randomly chosen qubits.

    .. warning::
        This template uses random number generation inside qnodes. Find more
        details about how to invoke the desired random behaviour in the "Usage Details" section below.

    The argument ``weights`` contains the weights for each layer. The number of layers :math:`L` is therefore derived
    from the first dimension of ``weights``.

    The two-qubit gates of type ``imprimitive`` and the rotations are distributed randomly in the circuit.
    The number of random rotations is derived from the second dimension of ``weights``. The number of
    two-qubit gates is determined by ``ratio_imprim``. For example, a ratio of ``0.3`` with ``30`` rotations
    will lead to the use of ``10`` two-qubit gates.

    .. note::
        If applied to one qubit only, this template will use no imprimitive gates.

    This is an example of two 4-qubit random layers with four Pauli-Y/Pauli-Z rotations :math:`R_y, R_z`,
    controlled-Z gates as imprimitives, as well as ``ratio_imprim=0.3``:

    .. figure:: ../../_static/layer_rnd.png
        :align: center
        :width: 60%
        :target: javascript:void(0);

    Args:
        weights (array[float]): array of weights of shape ``(L, k)``,
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
            a Wires object.
        ratio_imprim (float): value between 0 and 1 that determines the ratio of imprimitive to rotation gates
        imprimitive (pennylane.ops.Operation): two-qubit gate to use, defaults to :class:`~pennylane.ops.CNOT`
        rotations (list[pennylane.ops.Operation]): List of Pauli-X, Pauli-Y and/or Pauli-Z gates. The frequency
            determines how often a particular rotation type is used. Defaults to the use of all three
            rotations with equal frequency.
        seed (int): seed to generate random architecture, defaults to 42

    Raises:
        ValueError: if inputs do not have the correct format

    .. UsageDetails::

        **Default seed**

        ``RandomLayers`` always uses a seed to initialize the construction of a random circuit. This means
        that the template creates the same circuit every time it is called. If no seed is provided, the default
        seed of ``42`` is used.

        .. code-block:: python

            import pennylane as qml
            import numpy as np
            from pennylane.templates.layers import RandomLayers

            dev = qml.device("default.qubit", wires=2)
            weights = [[0.1, -2.1, 1.4]]

            @qml.qnode(dev)
            def circuit1(weights):
                RandomLayers(weights=weights, wires=range(2))
                return qml.expval(qml.PauliZ(0))

            @qml.qnode(dev)
            def circuit2(weights):
                RandomLayers(weights=weights, wires=range(2))
                return qml.expval(qml.PauliZ(0))

        >>> np.allclose(circuit1(weights), circuit2(weights))
        >>> True

        You can verify this by drawing the circuits.

            >>> print(circuit1.draw())
            >>>  0: ──RX(0.1)──RX(-2.1)──╭X──╭X───────────┤ ⟨Z⟩
            ...  1: ─────────────────────╰C──╰C──RZ(1.4)──┤

            >>> print(circuit2.draw())
            >>>  0: ──RX(0.1)──RX(-2.1)──╭X──╭X───────────┤ ⟨Z⟩
            ...  1: ─────────────────────╰C──╰C──RZ(1.4)──┤

        **Changing the seed**

        To change the randomly generated circuit architecture, you have to change the seed passed to the template.
        For example, these two calls of ``RandomLayers`` *do not* create the same circuit:

        .. code-block:: python

            @qml.qnode(dev)
            def circuit_9(weights):
                RandomLayers(weights=weights, wires=range(2), seed=9)
                return qml.expval(qml.PauliZ(0))

            @qml.qnode(dev)
            def circuit_12(weights):
                RandomLayers(weights=weights, wires=range(2), seed=12)
                return qml.expval(qml.PauliZ(0))

        >>> np.allclose(circuit_9(weights), circuit_12(weights))
        >>> False

        >>> print(circuit_9.draw())
        >>>  0: ──╭X──RY(-2.1)──RX(1.4)──┤ ⟨Z⟩
        ...  1: ──╰C──RX(0.1)────────────┤

        >>> print(circuit_12.draw())
        >>>  0: ──╭X──RX(-2.1)──╭C──╭X──RZ(1.4)──┤ ⟨Z⟩
        ...  1: ──╰C──RZ(0.1)───╰X──╰C───────────┤


        **Automatically creating random circuits**

        To automate the process of creating different circuits with ``RandomLayers``,
        you can set ``seed=None`` to avoid specifying a seed. However, in this case care needs
        to be taken. In the default setting, a quantum node is **mutable**, which means that the quantum function is
        re-evaluated every time it is called. This means that the circuit is re-constructed from scratch
        each time you call the qnode:

        .. code-block:: python

            @qml.qnode(dev)
            def circuit_rnd(weights):
                RandomLayers(weights=weights, wires=range(2), seed=None)
                return qml.expval(qml.PauliZ(0))

            first_call = circuit_rnd(weights)
            second_call = circuit_rnd(weights)

        >>> np.allclose(first_call, second_call)
        >>> False

        This can be rectified by making the quantum node **immutable**.

        .. code-block:: python

            @qml.qnode(dev, mutable=False)
            def circuit_rnd(weights):
                RandomLayers(weights=weights, wires=range(2), seed=None)
                return qml.expval(qml.PauliZ(0))

            first_call = circuit_rnd(weights)
            second_call = circuit_rnd(weights)

        >>> np.allclose(first_call, second_call)
        >>> True
    """
    if seed is not None:
        np.random.seed(seed)

    if rotations is None:
        rotations = [RX, RY, RZ]

    #############
    # Input checks

    wires = Wires(wires)

    repeat = check_number_of_layers([weights])
    n_rots = get_shape(weights)[1]

    expected_shape = (repeat, n_rots)
    check_shape(
        weights,
        expected_shape,
        msg="'weights' must be of shape {}; got {}"
        "".format(expected_shape, get_shape(weights)),
    )

    check_type(
        ratio_imprim,
        [float, type(None)],
        msg="'ratio_imprim' must be a float; got {}".format(ratio_imprim),
    )
    check_type(n_rots, [int, type(None)],
               msg="'n_rots' must be an integer; got {}".format(n_rots))
    # TODO: Check that 'rotations' contains operations
    check_type(
        rotations,
        [list, type(None)],
        msg="'rotations' must be a list of PennyLane operations; got {}"
        "".format(rotations),
    )
    check_type(seed, [int, type(None)],
               msg="'seed' must be an integer; got {}.".format(seed))

    ###############

    for l in range(repeat):
        random_layer(
            weights=weights[l],
            wires=wires,
            ratio_imprim=ratio_imprim,
            imprimitive=imprimitive,
            rotations=rotations,
            seed=seed,
        )
예제 #12
0
def CVNeuralNetLayers(theta_1, phi_1, varphi_1, r, phi_r, theta_2, phi_2,
                      varphi_2, a, phi_a, k, wires):
    r"""A sequence of layers of a continuous-variable quantum neural network,
    as specified in `arXiv:1806.06871 <https://arxiv.org/abs/1806.06871>`_.

    The layer consists
    of interferometers, displacement and squeezing gates mimicking the linear transformation of
    a neural network in the x-basis of the quantum system, and uses a Kerr gate
    to introduce a 'quantum' nonlinearity.

    The layers act on the :math:`M` modes given in ``wires``,
    and include interferometers of :math:`K=M(M-1)/2` beamsplitters. The different weight parameters
    contain the weights for each layer. The number of layers :math:`L` is therefore derived
    from the first dimension of ``weights``.

    This example shows a 4-mode CVNeuralNet layer with squeezing gates :math:`S`, displacement gates :math:`D` and
    Kerr gates :math:`K`. The two big blocks are interferometers of type
    :mod:`pennylane.templates.layers.Interferometer`:

    .. figure:: ../../_static/layer_cvqnn.png
        :align: center
        :width: 60%
        :target: javascript:void(0);

    .. note::
       The CV neural network architecture includes :class:`~pennylane.ops.Kerr` operations.
       Make sure to use a suitable device, such as the :code:`strawberryfields.fock`
       device of the `PennyLane-SF <https://github.com/XanaduAI/pennylane-sf>`_ plugin.

    Args:
        theta_1 (tensor_like): shape :math:`(L, K)` tensor of transmittivity angles for first interferometer
        phi_1 (tensor_like): shape :math:`(L, K)` tensor of phase angles for first interferometer
        varphi_1 (tensor_like): shape :math:`(L, M)` tensor of rotation angles to apply after first interferometer
        r (tensor_like): shape :math:`(L, M)` tensor of squeezing amounts for :class:`~pennylane.ops.Squeezing` operations
        phi_r (tensor_like): shape :math:`(L, M)` tensor of squeezing angles for :class:`~pennylane.ops.Squeezing` operations
        theta_2 (tensor_like): shape :math:`(L, K)` tensor of transmittivity angles for second interferometer
        phi_2 (tensor_like): shape :math:`(L, K)` tensor of phase angles for second interferometer
        varphi_2 (tensor_like): shape :math:`(L, M)` tensor of rotation angles to apply after second interferometer
        a (tensor_like): shape :math:`(L, M)` tensor of displacement magnitudes for :class:`~pennylane.ops.Displacement` operations
        phi_a (tensor_like): shape :math:`(L, M)` tensor of displacement angles for :class:`~pennylane.ops.Displacement` operations
        k (tensor_like): shape :math:`(L, M)` tensor of kerr parameters for :class:`~pennylane.ops.Kerr` operations
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
            a Wires object.
    Raises:
        ValueError: if inputs do not have the correct format
    """

    #############
    # Input checks

    wires = Wires(wires)
    repeat = _preprocess(theta_1, phi_1, varphi_1, r, phi_r, theta_2, phi_2,
                         varphi_2, a, phi_a, k, wires)

    ###############

    for l in range(repeat):
        cv_neural_net_layer(
            theta_1=theta_1[l],
            phi_1=phi_1[l],
            varphi_1=varphi_1[l],
            r=r[l],
            phi_r=phi_r[l],
            theta_2=theta_2[l],
            phi_2=phi_2[l],
            varphi_2=varphi_2[l],
            a=a[l],
            phi_a=phi_a[l],
            k=k[l],
            wires=wires,
        )
예제 #13
0
    def test_heisenberg(self, test_class, tol):
        "Heisenberg picture adjoint actions of CV Operations."

        ww = list(range(test_class.num_wires))

        # fixed parameter values
        if test_class.par_domain == "A":
            if test_class.__name__ == "Interferometer":
                ww = list(range(2))
                par = [
                    np.array(
                        [
                            [0.83645892 - 0.40533293j, -0.20215326 + 0.30850569j],
                            [-0.23889780 - 0.28101519j, -0.88031770 - 0.29832709j],
                        ]
                    )
                ]
            else:
                par = [np.array([[-1.82624687]])] * test_class.num_params
        else:
            par = [-0.069125, 0.51778, 0.91133, 0.95904][: test_class.num_params]

        op = test_class(*par, wires=ww)

        if issubclass(test_class, qml.operation.Observable):
            Q = op.heisenberg_obs(Wires(ww))
            # ev_order equals the number of dimensions of the H-rep array
            assert Q.ndim == test_class.ev_order
            return

        # not an Expectation

        U = op.heisenberg_tr(Wires(ww))
        I = np.eye(*U.shape)
        # first row is always (1,0,0...)
        assert np.all(U[0, :] == I[:, 0])

        # check the inverse transform
        V = op.heisenberg_tr(Wires(ww), inverse=True)
        assert np.linalg.norm(U @ V - I) == pytest.approx(0, abs=tol)
        assert np.linalg.norm(V @ U - I) == pytest.approx(0, abs=tol)

        if op.grad_recipe is not None:
            # compare gradient recipe to numerical gradient
            h = 1e-7
            U = op.heisenberg_tr(Wires(ww))
            for k in range(test_class.num_params):
                D = op.heisenberg_pd(k)  # using the recipe
                # using finite difference
                op.data[k] += h
                Up = op.heisenberg_tr(Wires(ww))
                op.data = par
                G = (Up - U) / h
                assert D == pytest.approx(G, abs=tol)

        # make sure that `heisenberg_expand` method receives enough wires to actually expand
        # so only check multimode ops
        if len(op.wires) > 1:
            with pytest.raises(ValueError, match="do not exist on this device with wires"):
                op.heisenberg_expand(U, Wires([0]))

        # validate size of input for `heisenberg_expand` method
        with pytest.raises(ValueError, match="Heisenberg matrix is the wrong size"):
            U_wrong_size = U[1:, 1:]
            op.heisenberg_expand(U_wrong_size, Wires(ww))

        # ensure that `heisenberg_expand` raises exception if it receives an array with order > 2
        with pytest.raises(ValueError, match="Only order-1 and order-2 arrays supported"):
            U_high_order = np.array([U] * 3)
            op.heisenberg_expand(U_high_order, Wires(ww))
예제 #14
0
def MottonenStatePreparation(state_vector, wires):
    r"""
    Prepares an arbitrary state on the given wires using a decomposition into gates developed
    by Möttönen et al. (Quantum Info. Comput., 2005).

    The state is prepared via a sequence
    of "uniformly controlled rotations". A uniformly controlled rotation on a target qubit is
    composed from all possible controlled rotations on said qubit and can be used to address individual
    elements of the state vector. In the work of Mottonen et al., the inverse of their state preparation
    is constructed by first equalizing the phases of the state vector via uniformly controlled Z rotations
    and then rotating the now real state vector into the direction of the state :math:`|0\rangle` via
    uniformly controlled Y rotations.

    This code is adapted from code written by Carsten Blank for PennyLane-Qiskit.

    Args:
        state_vector (array): Input array of shape ``(2^N,)``, where N is the number of wires
            the state preparation acts on. ``N`` must be smaller or equal to the total
            number of wires.
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
            a Wires object.

    Raises:
        ValueError: if inputs do not have the correct format
    """

    ###############
    # Input checks

    wires = Wires(wires)

    n_wires = len(wires)
    expected_shape = (2 ** n_wires,)
    check_shape(
        state_vector,
        expected_shape,
        msg="'state_vector' must be of shape {}; got {}."
        "".format(expected_shape, get_shape(state_vector)),
    )

    # check if state_vector is normalized
    if isinstance(state_vector[0], Variable):
        state_vector_values = [s.val for s in state_vector]
        norm = np.sum(np.abs(state_vector_values) ** 2)
    else:
        norm = np.sum(np.abs(state_vector) ** 2)
    if not np.isclose(norm, 1.0, atol=1e-3):
        raise ValueError("'state_vector' has to be of length 1.0, got {}".format(norm))

    #######################

    # Change ordering of indices, original code was for IBM machines
    state_vector = np.array(state_vector).reshape([2] * n_wires).T.flatten()[:, np.newaxis]
    state_vector = sparse.dok_matrix(state_vector)

    a = sparse.dok_matrix(state_vector.shape)
    omega = sparse.dok_matrix(state_vector.shape)

    for (i, j), v in state_vector.items():
        if isinstance(v, Variable):
            a[i, j] = np.absolute(v.val)
            omega[i, j] = np.angle(v.val)
        else:
            a[i, j] = np.absolute(v)
            omega[i, j] = np.angle(v)
    # This code is directly applying the inverse of Carsten Blank's
    # code to avoid inverting at the end

    # Apply y rotations
    for k in range(n_wires, 0, -1):  # Todo: use actual wire ordering!
        alpha_y_k = _get_alpha_y(a, n_wires, k)  # type: sparse.dok_matrix
        control = wires[k:]
        target = wires[k - 1]
        control = control.tolist()  # TODO: remove when operators accept Wires object
        target = target.tolist()[0]  # TODO: remove when operators accept Wires object
        _uniform_rotation_y_dagger(alpha_y_k, control, target)

    # Apply z rotations
    for k in range(n_wires, 0, -1):  # Todo: use actual wire ordering!
        alpha_z_k = _get_alpha_z(omega, n_wires, k)
        control = wires[k:]
        target = wires[k - 1]
        control = control.tolist()  # TODO: remove when operators accept Wires object
        target = target.tolist()[0]  # TODO: remove when operators accept Wires object
        if len(alpha_z_k) > 0:
            _uniform_rotation_z_dagger(alpha_z_k, control, target)
예제 #15
0
    def test_creation_from_wires_lists(self):
        """Tests that a Wires object can be created from a list of Wires."""

        wires = Wires([Wires([0]), Wires([1]), Wires([2])])
        assert wires.labels == (Wires([0]), Wires([1]), Wires([2]))
예제 #16
0
 def test_add_wires_object_with_iterable(self):
     """Tests that wires objects add correctly."""
     wires1 = [4, 0, 1]
     wires2 = Wires([1, 2])
     assert wires1 + wires2 == Wires([4, 0, 1, 2])
     assert wires2 + wires1 == Wires([1, 2, 4, 0])
예제 #17
0
    def test_creation_from_different_wire_types(self, iterable):
        """Tests that a Wires object can be created from iterables of different
        objects representing a single wire index."""

        wires = Wires(iterable)
        assert wires.labels == tuple(iterable)
예제 #18
0
 def test_add_wires_with_inbuilt_sum(self):
     """Tests that wires objects add correctly using sum()."""
     wires1 = [4, 0, 1]
     wires2 = Wires([1, 2])
     assert sum([wires1, wires2], Wires([]))
예제 #19
0
    def test_error_for_incorrect_wire_types(self, iterable):
        """Tests that a Wires object cannot be created from wire types that are not allowed."""

        with pytest.raises(WireError, match="Wires must be represented"):
            Wires(iterable)
예제 #20
0
class TestWires:
    """Tests for the ``Wires`` class."""
    @pytest.mark.parametrize(
        "iterable",
        [np.array([0, 1, 2]), [0, 1, 2],
         (0, 1, 2), range(3)])
    def test_creation_from_common_iterables(self, iterable):
        """Tests that a Wires object can be created from standard iterable inputs."""

        wires = Wires(iterable)
        assert wires.labels == (0, 1, 2)

    @pytest.mark.parametrize(
        "iterable", [[qml.RX, qml.RY], [qml.PauliX], (None, qml.expval),
                     (
                         qml.device('default.qubit', wires=range(3)),
                         qml.device('default.gaussian', wires=[qml.RX, 3]),
                     )])
    def test_creation_from_iterables_of_exotic_elements(self, iterable):
        """Tests that a Wires object can be created from standard iterable inputs."""

        wires = Wires(iterable)
        assert wires.labels == tuple(iterable)

    def test_creation_from_wires_object(self):
        """Tests that a Wires object can be created from another Wires object."""

        wires = Wires(Wires([0, 1, 2]))
        assert wires.labels == (0, 1, 2)

    def test_creation_from_wires_lists(self):
        """Tests that a Wires object can be created from a list of Wires."""

        wires = Wires([Wires([0]), Wires([1]), Wires([2])])
        assert wires.labels == (Wires([0]), Wires([1]), Wires([2]))

    @pytest.mark.parametrize(
        "iterable",
        [[1, 0, 4], ['a', 'b', 'c'], [0, 1, None], ['a', 1, "ancilla"]])
    def test_creation_from_different_wire_types(self, iterable):
        """Tests that a Wires object can be created from iterables of different
        objects representing a single wire index."""

        wires = Wires(iterable)
        assert wires.labels == tuple(iterable)

    @pytest.mark.parametrize("wire", [1, -2, 'a', 'q1', -1.4])
    def test_creation_from_single_object(self, wire):
        """Tests that a Wires object can be created from a non-iterable object
        representing a single wire index."""

        wires = Wires(wire)
        assert wires.labels == (wire, )

    @pytest.mark.parametrize("iterable", [None, qml.RX])
    def test_error_for_incorrect_wire_types(self, iterable):
        """Tests that a Wires object cannot be created from wire types that are not allowed."""

        with pytest.raises(WireError, match="Wires must be represented"):
            Wires(iterable)

    @pytest.mark.parametrize(
        "iterable",
        [np.array([4, 1, 1, 3]), [4, 1, 1, 3], (4, 1, 1, 3), ['a', 'a', 'b']])
    def test_error_for_repeated_wires(self, iterable):
        """Tests that a Wires object cannot be created from iterables with repeated indices."""

        with pytest.raises(WireError, match="Wires must be unique"):
            Wires(iterable)

    @pytest.mark.parametrize("iterable", [[4, 1, 0, 3], ['a', 'b', 'c']])
    def test_indexing_and_slicing(self, iterable):
        """Tests the indexing and slicing of Wires objects."""

        wires = Wires(iterable)

        # check single index
        for i in range(len(iterable)):
            assert wires[i] == iterable[i]
        # check slicing
        assert wires[:2] == Wires(iterable[:2])

    def test_equality(self):
        """Tests that we can compare Wires objects with the '==' and '!=' operators."""

        wires1 = Wires([1, 2, 3])
        wires2 = Wires([3, 2, 1])
        wires3 = Wires([1, 2, 3])
        assert wires1 != wires2
        assert wires1 == wires3

    @pytest.mark.parametrize("iterable", [[4, 1, 0, 3], ['a', 'b', 'c']])
    def test_length(self, iterable):
        """Tests that a Wires object returns the correct length."""

        wires = Wires(iterable)
        assert len(wires) == len(iterable)

    def test_contains(self, ):
        """Tests the __contains__() method."""

        wires = Wires([0, 1, 2, 3, Wires([4, 5]), None])

        assert 0 in wires
        assert Wires([4, 5]) in wires
        assert None in wires
        assert not Wires([1]) in wires
        assert not Wires([0, 3]) in wires
        assert not Wires([0, 4]) in wires

        assert not [0, 4] in wires
        assert not [4] in wires

    def test_contains_wires(self, ):
        """Tests the dedicated contains_wires() method."""

        wires = Wires([0, 1, 2, 3, Wires([4, 5]), None])

        assert wires.contains_wires(Wires([0, 3]))
        assert wires.contains_wires(Wires([1, 2, None]))
        assert wires.contains_wires(Wires([Wires(
            [4, 5])]))  # Wires([4, 5]) is just a label!

        assert not wires.contains_wires(0)  # wrong type
        assert not wires.contains_wires([0, 1])  # wrong type
        assert not wires.contains_wires(Wires(
            [4, 5]))  # looks up 4 and 5 in wires, which are not present

    def test_add_two_wires_objects(self):
        """Tests that wires objects add correctly."""
        wires1 = Wires([4, 0, 1])
        wires2 = Wires([1, 2])
        assert wires1 + wires2 == Wires([4, 0, 1, 2])

    def test_add_wires_object_with_iterable(self):
        """Tests that wires objects add correctly."""
        wires1 = [4, 0, 1]
        wires2 = Wires([1, 2])
        assert wires1 + wires2 == Wires([4, 0, 1, 2])
        assert wires2 + wires1 == Wires([1, 2, 4, 0])

    def test_add_wires_with_inbuilt_sum(self):
        """Tests that wires objects add correctly using sum()."""
        wires1 = [4, 0, 1]
        wires2 = Wires([1, 2])
        assert sum([wires1, wires2], Wires([]))

    def test_representation_and_string(self):
        """Tests the string representation via both __str__ and __repr__."""

        wires_str = str(Wires([1, 2, 3]))
        wires_repr = repr(Wires([1, 2, 3]))
        assert wires_str == "<Wires = [1, 2, 3]>"
        assert wires_repr == "<Wires = [1, 2, 3]>"

    def test_array_representation(self):
        """Tests that Wires object has an array representation."""

        wires = Wires([4, 0, 1])
        array = np.array(wires)
        assert isinstance(array, np.ndarray)
        assert array.shape == (3, )
        for w1, w2 in zip(array, np.array([4, 0, 1])):
            assert w1 == w2

    def test_set_of_wires(self):
        """Tests that a set() of wires is formed correctly."""

        wires = Wires([0, 1, 2])
        list_of_wires = [Wires([1]), Wires([1]), Wires([1, 2, 3]), Wires([4])]

        assert set(wires) == {0, 1, 2}
        assert set(list_of_wires) == {Wires([1]), Wires([1, 2, 3]), Wires([4])}

    def test_label_property(self):
        """Tests the get_label() method."""

        labels = [0, 'q1', 16]
        wires = Wires(labels)

        assert wires.labels == tuple(labels)
        assert wires.labels[1] == 'q1'
        assert wires.labels[2] == 16

    def test_convert_to_numpy_array(self):
        """Tests that Wires object can be converted to a numpy array."""

        wires = Wires([4, 0, 1])
        array = wires.toarray()
        assert isinstance(array, np.ndarray)
        assert array.shape == (3, )
        for w1, w2 in zip(array, np.array([4, 0, 1])):
            assert w1 == w2

    def test_convert_to_list(self):
        """Tests that Wires object can be converted to a list."""

        wires = Wires([4, 0, 1])
        list_ = wires.tolist()
        assert isinstance(list_, list)
        assert list_ == [4, 0, 1]

    @pytest.mark.parametrize("iterable", [[4, 1, 0, 3], ['a', 'b', 'c']])
    def test_index_method(self, iterable):
        """Tests the ``index()`` method."""

        wires = Wires(iterable)
        element = iterable[1]
        # check for non-Wires inputs
        assert wires.index(element) == 1
        # check for Wires inputs
        assert wires.index(Wires([element])) == 1
        # check that Wires of length >1 produce an error
        with pytest.raises(WireError, match="Can only retrieve index"):
            wires.index(Wires([1, 2]))
        # check that error raised when wire does not exist
        with pytest.raises(WireError, match="Wire with label d not found"):
            wires.index(Wires(['d']))

    def test_indices_method(self):
        """Tests the ``indices()`` method."""

        wires = Wires([4, 0, 1])
        # for Wires inputs
        assert wires.indices(Wires([1, 4])) == [2, 0]
        # for non-Wires inputs
        assert wires.indices([1, 4]) == [2, 0]
        # for integer
        assert wires.indices(1) == [2]

    @pytest.mark.parametrize("wires, wire_map, expected", [
        (Wires(['a', 'b']), {
            'a': 0,
            'b': 1
        }, Wires([0, 1])),
        (Wires([-1, 1]), {
            1: 'c',
            -1: 1,
            'd': 'e'
        }, Wires([1, 'c'])),
    ])
    def test_map_method(self, wires, wire_map, expected):
        """Tests the ``map()`` method."""

        assert wires.map(wire_map) == expected

        # error when labels not in wire_map dictionary
        with pytest.raises(WireError, match="No mapping for wire label"):
            wires.map({-1: Wires(4)}) == expected

        # error for non-unique wire labels
        with pytest.raises(WireError, match="Failed to implement wire map"):
            wires = Wires([0, 1])
            wires.map({0: 'a', 1: 'a'})

    def test_select_random_method(self):
        """Tests the ``select_random()`` method."""

        wires = Wires([4, 0, 1, 5, 6])

        assert len(wires.select_random(2)) == 2
        # check that seed makes call deterministic
        assert wires.select_random(4, seed=1) == wires.select_random(4, seed=1)

        with pytest.raises(WireError, match="Cannot sample"):
            wires.select_random(6)

    def test_subset_method(self):
        """Tests the ``subset()`` method."""

        wires = Wires([4, 0, 1, 5, 6])

        assert wires.subset([2, 3, 0]) == Wires([1, 5, 4])
        assert wires.subset(1) == Wires([0])
        assert wires.subset([4, 5, 7],
                            periodic_boundary=True) == Wires([6, 4, 1])
        # if index does not exist
        with pytest.raises(WireError, match="Cannot subset wire at index"):
            wires.subset([10])

    def test_all_wires_method(self):
        """Tests the ``all_wires()`` method."""

        wires1 = Wires([2, 1, 3])
        wires2 = Wires([1, 4, 5, 2])
        wires3 = Wires([6, 5])

        new_wires = Wires.all_wires([wires1, wires2, wires3])
        assert new_wires.labels == (2, 1, 3, 4, 5, 6)

        new_wires = Wires.all_wires([wires1, wires2, wires3], sort=True)
        assert new_wires.labels == (1, 2, 3, 4, 5, 6)

        with pytest.raises(WireError, match="Expected a Wires object"):
            Wires.all_wires([[3, 4], [8, 5]])

    def test_shared_wires_method(self):
        """Tests the ``shared_wires()`` method."""

        wires1 = Wires([4, 0, 1])
        wires2 = Wires([3, 0, 4])
        wires3 = Wires([4, 0])
        res = Wires.shared_wires([wires1, wires2, wires3])
        assert res == Wires([4, 0])

        res = Wires.shared_wires([wires2, wires1, wires3])
        assert res == Wires([0, 4])

        with pytest.raises(WireError, match="Expected a Wires object"):
            Wires.shared_wires([[3, 4], [8, 5]])

    def test_unique_wires_method(self):
        """Tests the ``unique_wires()`` method."""

        wires1 = Wires([4, 0, 1])
        wires2 = Wires([3, 0, 4])
        wires3 = Wires([4, 0])
        res = Wires.unique_wires([wires1, wires2, wires3])
        assert res == Wires([1, 3])

        res = Wires.unique_wires([wires2, wires1, wires3])
        assert res == Wires([3, 1])

        with pytest.raises(WireError, match="Expected a Wires object"):
            Wires.unique_wires([[2, 1], [8, 5]])

    def test_equal_to_tuple(self):
        assert Wires([1, 2, 3]) == (1, 2, 3)
        assert Wires([1, 2, 3]) != (1, 5, 3)
        assert (1, 5, 3) != Wires([1, 2, 3])
예제 #21
0
파일: qpe.py 프로젝트: varunrishi/pennylane
def QuantumPhaseEstimation(unitary, target_wires, estimation_wires):
    r"""Performs the
    `quantum phase estimation <https://en.wikipedia.org/wiki/Quantum_phase_estimation_algorithm>`__
    circuit.

    Given a unitary matrix :math:`U`, this template applies the circuit for quantum phase
    estimation. The unitary is applied to the qubits specified by ``target_wires`` and :math:`n`
    qubits are used for phase estimation as specified by ``estimation_wires``.

    .. figure:: ../../_static/templates/subroutines/qpe.svg
        :align: center
        :width: 60%
        :target: javascript:void(0);

    This circuit can be used to perform the standard quantum phase estimation algorithm, consisting
    of the following steps:

    #. Prepare ``target_wires`` in a given state. If ``target_wires`` are prepared in an eigenstate
       of :math:`U` that has corresponding eigenvalue :math:`e^{2 \pi i \theta}` with phase
       :math:`\theta \in [0, 1)`, this algorithm will measure :math:`\theta`. Other input states can
       be prepared more generally.
    #. Apply the ``QuantumPhaseEstimation`` circuit.
    #. Measure ``estimation_wires`` using :func:`~.probs`, giving a probability distribution over
       measurement outcomes in the computational basis.
    #. Find the index of the largest value in the probability distribution and divide that number by
       :math:`2^{n}`. This number will be an estimate of :math:`\theta` with an error that decreases
       exponentially with the number of qubits :math:`n`.

    Note that if :math:`\theta \in (-1, 0]`, we can estimate the phase by again finding the index
    :math:`i` found in step 4 and calculating :math:`\theta \approx \frac{1 - i}{2^{n}}`. The
    usage details below give an example of this case.

    Args:
        unitary (array): the phase estimation unitary, specified as a matrix
        target_wires (Union[Wires, Sequence[int], or int]): the target wires to apply the unitary
        estimation_wires (Union[Wires, Sequence[int], or int]): the wires to be used for phase
            estimation

    Raises:
        QuantumFunctionError: if the ``target_wires`` and ``estimation_wires`` share a common
            element

    .. UsageDetails::

        Consider the matrix corresponding to a rotation from an :class:`~.RX` gate:

        .. code-block:: python

            import pennylane as qml
            from pennylane.templates import QuantumPhaseEstimation
            from pennylane import numpy as np

            phase = 5
            target_wires = [0]
            unitary = qml.RX(phase, wires=0).matrix

        The ``phase`` parameter can be estimated using ``QuantumPhaseEstimation``. An example is
        shown below using a register of five phase-estimation qubits:

        .. code-block:: python

            n_estimation_wires = 5
            estimation_wires = range(1, n_estimation_wires + 1)

            dev = qml.device("default.qubit", wires=n_estimation_wires + 1)

            @qml.qnode(dev)
            def circuit():
                # Start in the |+> eigenstate of the unitary
                qml.Hadamard(wires=target_wires)

                QuantumPhaseEstimation(
                    unitary,
                    target_wires=target_wires,
                    estimation_wires=estimation_wires,
                )

                return qml.probs(estimation_wires)

            phase_estimated = np.argmax(circuit()) / 2 ** n_estimation_wires

            # Need to rescale phase due to convention of RX gate
            phase_estimated = 4 * np.pi * (1 - phase_estimated)
    """

    target_wires = Wires(target_wires)
    estimation_wires = Wires(estimation_wires)

    if len(Wires.shared_wires([target_wires, estimation_wires])) != 0:
        raise qml.QuantumFunctionError(
            "The target wires and estimation wires must be different")

    unitary_powers = [unitary]

    for _ in range(len(estimation_wires) - 1):
        new_power = unitary_powers[-1] @ unitary_powers[-1]
        unitary_powers.append(new_power)

    for wire in estimation_wires:
        qml.Hadamard(wire)
        qml.ControlledQubitUnitary(unitary_powers.pop(),
                                   control_wires=wire,
                                   wires=target_wires)

    qml.QFT(wires=estimation_wires).inv()
예제 #22
0
    def test_creation_from_common_iterables(self, iterable):
        """Tests that a Wires object can be created from standard iterable inputs."""

        wires = Wires(iterable)
        assert wires.labels == (0, 1, 2)
예제 #23
0
def StronglyEntanglingLayers(weights, wires, ranges=None, imprimitive=CNOT):
    r"""Layers consisting of single qubit rotations and entanglers, inspired by the circuit-centric classifier design
    `arXiv:1804.00633 <https://arxiv.org/abs/1804.00633>`_.

    The argument ``weights`` contains the weights for each layer. The number of layers :math:`L` is therefore derived
    from the first dimension of ``weights``.

    The 2-qubit gates, whose type is specified by the ``imprimitive`` argument,
    act chronologically on the :math:`M` wires, :math:`i = 1,...,M`. The second qubit of each gate is given by
    :math:`(i+r)\mod M`, where :math:`r` is a  hyperparameter called the *range*, and :math:`0 < r < M`.
    If applied to one qubit only, this template will use no imprimitive gates.

    This is an example of two 4-qubit strongly entangling layers (ranges :math:`r=1` and :math:`r=2`, respectively) with
    rotations :math:`R` and CNOTs as imprimitives:

    .. figure:: ../../_static/layer_sec.png
        :align: center
        :width: 60%
        :target: javascript:void(0);

    Args:

        weights (array[float]): array of weights of shape ``(L, M, 3)``
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
            a Wires object.
        ranges (Sequence[int]): sequence determining the range hyperparameter for each subsequent layer; if None
                                using :math:`r=l \mod M` for the :math:`l`th layer and :math:`M` wires.
        imprimitive (pennylane.ops.Operation): two-qubit gate to use, defaults to :class:`~pennylane.ops.CNOT`

    Raises:
        ValueError: if inputs do not have the correct format
    """

    #############
    # Input checks

    wires = Wires(wires)

    check_no_variable(ranges, msg="'ranges' cannot be differentiable")
    check_no_variable(imprimitive,
                      msg="'imprimitive' cannot be differentiable")

    repeat = check_number_of_layers([weights])

    expected_shape = (repeat, len(wires), 3)
    check_shape(
        weights,
        expected_shape,
        msg="'weights' must be of shape {}; got {}"
        "".format(expected_shape, get_shape(weights)),
    )

    if len(wires) > 1:
        if ranges is None:
            # tile ranges with iterations of range(1, n_wires)
            ranges = [(l % (len(wires) - 1)) + 1 for l in range(repeat)]

        expected_shape = (repeat, )
        check_shape(
            ranges,
            expected_shape,
            msg="'ranges' must be of shape {}; got {}"
            "".format(expected_shape, get_shape(weights)),
        )

        check_type(ranges, [list],
                   msg="'ranges' must be a list; got {}"
                   "".format(ranges))
        for r in ranges:
            check_type(r, [int],
                       msg="'ranges' must be a list of integers; got {}"
                       "".format(ranges))
        if any((r >= len(wires) or r == 0) for r in ranges):
            raise ValueError(
                "the range for all layers needs to be smaller than the number of "
                "qubits; got ranges {}.".format(ranges))
    else:
        ranges = [0] * repeat

    ###############

    for l in range(repeat):

        strongly_entangling_layer(weights=weights[l],
                                  wires=wires,
                                  r=ranges[l],
                                  imprimitive=imprimitive)
예제 #24
0
 def test_equal_to_tuple(self):
     assert Wires([1, 2, 3]) == (1, 2, 3)
     assert Wires([1, 2, 3]) != (1, 5, 3)
     assert (1, 5, 3) != Wires([1, 2, 3])
예제 #25
0
 def _controlled(self, wires):
     ControlledOperation(tape=self.subtape, control_wires=Wires(wires) + self.control_wires)
예제 #26
0
    def test_creation_from_iterables_of_exotic_elements(self, iterable):
        """Tests that a Wires object can be created from standard iterable inputs."""

        wires = Wires(iterable)
        assert wires.labels == tuple(iterable)
예제 #27
0
def _process_wires(wires, n_wires=None):
    r"""
    Checks and consolidates custom user wire mapping into a consistent, direction-free, `Wires`
    format. Used for converting between OpenFermion qubit numbering and Pennylane wire labels.

    Since OpenFermion's qubit numbering is always consecutive int, simple iterable types such as
    list, tuple, or Wires can be used to specify the 2-way `qubit index` <-> `wire label` mapping
    with indices representing qubits. Dict can also be used as a mapping, but does not provide any
    advantage over lists other than the ability to do partial mapping/permutation in the
    `qubit index` -> `wire label` direction.

    It is recommended to pass Wires/list/tuple `wires` since it's direction-free, i.e. the same
    `wires` argument can be used to convert both ways between OpenFermion and Pennylane. Only use
    dict for partial or unordered mapping.

    Args:
        wires (Wires, list, tuple, dict): User wire labels or mapping for Pennylane ansatz.
            For types Wires, list, or tuple, each item in the iterable represents a wire label
            corresponding to the qubit number equal to its index.
            For type dict, only int-keyed dict (for qubit-to-wire conversion) or
            consecutive-int-valued dict (for wire-to-qubit conversion) is accepted.
            If None, will be set to consecutive int based on ``n_wires``.
        n_wires (int): Number of wires used if known. If None, will be inferred from ``wires``; if
            ``wires`` is not available, will be set to 1.

    Returns:
        Wires: Cleaned wire mapping with indices corresponding to qubits and values
            corresponding to wire labels.

    **Example**

    >>> # consec int wires if no wires mapping provided, ie. identity map: 0<->0, 1<->1, 2<->2
    >>> _process_wires(None, 3)
    <Wires = [0, 1, 2]>

    >>> # List as mapping, qubit indices with wire label values: 0<->w0, 1<->w1, 2<->w2
    >>> _process_wires(['w0','w1','w2'])
    <Wires = ['w0', 'w1', 'w2']>

    >>> # Wires as mapping, qubit indices with wire label values: 0<->w0, 1<->w1, 2<->w2
    >>> _process_wires(Wires(['w0', 'w1', 'w2']))
    <Wires = ['w0', 'w1', 'w2']>

    >>> # Dict as partial mapping, int qubits keys to wire label values: 0->w0, 1 unchanged, 2->w2
    >>> _process_wires({0:'w0',2:'w2'})
    <Wires = ['w0', 1, 'w2']>

    >>> # Dict as mapping, wires label keys to consec int qubit values: w2->2, w0->0, w1->1
    >>> _process_wires({'w2':2, 'w0':0, 'w1':1})
    <Wires = ['w0', 'w1', 'w2']>
    """

    # infer from wires, or assume 1 if wires is not of accepted types.
    if n_wires is None:
        n_wires = len(wires) if isinstance(wires, (Wires, list, tuple, dict)) else 1

    # defaults to no mapping.
    if wires is None:
        return Wires(range(n_wires))

    if isinstance(wires, (Wires, list, tuple)):
        # does not care about the tail if more wires are provided than n_wires.
        wires = Wires(wires[:n_wires])

    elif isinstance(wires, dict):

        if all([isinstance(w, int) for w in wires.keys()]):
            # Assuming keys are taken from consecutive int wires. Allows for partial mapping.
            n_wires = max(wires) + 1
            labels = list(range(n_wires))  # used for completing potential partial mapping.
            for k, v in wires.items():
                if k < n_wires:
                    labels[k] = v
            wires = Wires(labels)
        elif set(range(n_wires)).issubset(set(wires.values())):
            # Assuming values are consecutive int wires (up to n_wires, ignores the rest).
            # Does NOT allow for partial mapping.
            wires = {v: k for k, v in wires.items()}  # flip for easy indexing
            wires = Wires([wires[i] for i in range(n_wires)])
        else:
            raise ValueError("Expected only int-keyed or consecutive int-valued dict for `wires`")

    else:
        raise ValueError(
            "Expected type Wires, list, tuple, or dict for `wires`, got {}".format(type(wires))
        )

    if len(wires) != n_wires:
        # check length consistency when all checking and cleaning are done.
        raise ValueError(
            "Length of `wires` ({}) does not match `n_wires` ({})".format(len(wires), n_wires)
        )

    return wires
예제 #28
0
    def test_creation_from_wires_object(self):
        """Tests that a Wires object can be created from another Wires object."""

        wires = Wires(Wires([0, 1, 2]))
        assert wires.labels == (0, 1, 2)
    qml.CNOT(wires=[0, 1])
    qml.PauliY(wires=1)
    qml.CRY(theta[0], wires=[2, 1])
    qml.PhaseShift(theta[1], wires=0)
    qml.T(wires=0)
    qml.Toffoli(wires=[0, 1, 2])
    return qml.expval(qml.PauliZ(0))


transformed_qfunc = commute_controlled()(qfunc)

expected_op_list = [
    "PauliX", "CNOT", "CRY", "PauliY", "Toffoli", "S", "PhaseShift", "T"
]
expected_wires_list = [
    Wires(2),
    Wires([0, 1]),
    Wires([2, 1]),
    Wires(1),
    Wires([0, 1, 2]),
    Wires(0),
    Wires(0),
    Wires(0),
]


class TestCommuteControlledInterfaces:
    """Test that single-qubit gates can be pushed through controlled gates in all interfaces."""
    def test_commute_controlled_autograd(self):
        """Test QNode and gradient in autograd interface."""
예제 #30
0
def binary_to_pauli(binary_vector, wire_map=None):  # pylint: disable=too-many-branches
    """Converts a binary vector of even dimension to an Observable instance.

    This functions follows the convention that the first half of binary vector components specify
    PauliX placements while the last half specify PauliZ placements.

    Args:
        binary_vector (Union[list, tuple, array]): binary vector of even dimension representing a
            unique Pauli word
        wire_map (dict): dictionary containing all wire labels used in the Pauli word as keys, and
            unique integer labels as their values

    Returns:
        Tensor: The Pauli word corresponding to the input binary vector. Note
        that if a zero vector is input, then the resulting Pauli word will be
        an :class:`~.Identity` instance.

    Raises:
        TypeError: if length of binary vector is not even, or if vector does not have strictly
            binary components

    **Example**

    If ``wire_map`` is unspecified, the Pauli operations follow the same enumerations as the vector
    components, i.e., the ``i`` and ``N+i`` components specify the Pauli operation on wire ``i``,

    >>> binary_to_pauli([0,1,1,0,1,0])
    Tensor(PauliY(wires=[1]), PauliX(wires=[2]))

    An arbitrary labelling can be assigned by using ``wire_map``:

    >>> wire_map = {Wires('a'): 0, Wires('b'): 1, Wires('c'): 2}
    >>> binary_to_pauli([0,1,1,0,1,0], wire_map=wire_map)
    Tensor(PauliY(wires=['b']), PauliX(wires=['c']))

    Note that the values of ``wire_map``, if specified, must be ``0,1,..., N``,
    where ``N`` is the dimension of the vector divided by two, i.e.,
    ``list(wire_map.values())`` must be ``list(range(len(binary_vector)/2))``.
    """

    if isinstance(binary_vector, (list, tuple)):
        binary_vector = np.asarray(binary_vector)

    if len(binary_vector) % 2 != 0:
        raise ValueError(
            f"Length of binary_vector must be even, instead got vector of shape {np.shape(binary_vector)}."
        )

    if not np.array_equal(binary_vector, binary_vector.astype(bool)):
        raise ValueError(
            f"Input vector must have strictly binary components, instead got {binary_vector}."
        )

    n_qubits = len(binary_vector) // 2

    if wire_map is not None:
        if set(wire_map.values()) != set(range(n_qubits)):
            raise ValueError(
                f"The values of wire_map must be integers 0 to N, for 2N-dimensional binary vector."
                f" Instead got wire_map values: {wire_map.values()}")
        label_map = {
            explicit_index: wire_label
            for wire_label, explicit_index in wire_map.items()
        }
    else:
        label_map = {i: Wires(i) for i in range(n_qubits)}

    pauli_word = None
    for i in range(n_qubits):
        operation = None
        if binary_vector[i] == 1 and binary_vector[n_qubits + i] == 0:
            operation = PauliX(wires=label_map[i])

        elif binary_vector[i] == 1 and binary_vector[n_qubits + i] == 1:
            operation = PauliY(wires=label_map[i])

        elif binary_vector[i] == 0 and binary_vector[n_qubits + i] == 1:
            operation = PauliZ(wires=label_map[i])

        if operation is not None:
            if pauli_word is None:
                pauli_word = operation
            else:
                pauli_word @= operation

    if pauli_word is None:
        return Identity(wires=list(label_map.values())[0])

    return pauli_word