Esempio n. 1
0
def BasisStatePreparation(basis_state, wires):
    r"""
    Prepares a basis state on the given wires using a sequence of Pauli X gates.

    .. warning::

        ``basis_state`` influences the circuit architecture and is therefore incompatible with
        gradient computations. Ensure that ``basis_state`` is not passed to the qnode by positional
        arguments.

    Args:
        basis_state (array): Input array of shape ``(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 of the device.
        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)

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

    # basis_state cannot be trainable
    check_no_variable(
        basis_state,
        msg="'basis_state' cannot be differentiable; must be passed as a keyword argument "
        "to the quantum node",
    )

    # basis_state is guaranteed to be a list of binary values
    if any([b not in [0, 1] for b in basis_state]):
        raise ValueError(
            "'basis_state' must only contain values of 0 and 1; got {}".format(basis_state)
        )

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

    wires = wires.tolist()  # TODO: remove when operator takes Wires object

    for wire, state in zip(wires, basis_state):
        if state == 1:
            qml.PauliX(wire)
Esempio n. 2
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)

    check_no_variable(ratio_imprim, msg="'ratio_imprim' cannot be differentiable")
    check_no_variable(imprimitive, msg="'imprimitive' cannot be differentiable")
    check_no_variable(rotations, msg="'rotations' cannot be differentiable")
    check_no_variable(seed, msg="'seed' cannot be differentiable")

    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,
        )
Esempio n. 3
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)
Esempio n. 4
0
def Interferometer(theta,
                   phi,
                   varphi,
                   wires,
                   mesh="rectangular",
                   beamsplitter="pennylane"):
    r"""General linear interferometer, an array of beamsplitters and phase shifters.

    For :math:`M` wires, the general interferometer is specified by
    providing :math:`M(M-1)/2` transmittivity angles :math:`\theta` and the same number of
    phase angles :math:`\phi`, as well as :math:`M-1` additional rotation
    parameters :math:`\varphi`.

    By specifying the keyword argument ``mesh``, the scheme used to implement the interferometer
    may be adjusted:

    * ``mesh='rectangular'`` (default): uses the scheme described in
      `Clements et al. <https://dx.doi.org/10.1364/OPTICA.3.001460>`__, resulting in a *rectangular* array of
      :math:`M(M-1)/2` beamsplitters arranged in :math:`M` slices and ordered from left
      to right and top to bottom in each slice. The first beamsplitter acts on
      wires :math:`0` and :math:`1`:

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


    * ``mesh='triangular'``: uses the scheme described in `Reck et al. <https://dx.doi.org/10.1103/PhysRevLett.73.58>`__,
      resulting in a *triangular* array of :math:`M(M-1)/2` beamsplitters arranged in
      :math:`2M-3` slices and ordered from left to right and top to bottom. The
      first and fourth beamsplitters act on wires :math:`M-1` and :math:`M`, the second
      on :math:`M-2` and :math:`M-1`, and the third on :math:`M-3` and :math:`M-2`, and
      so on.

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

    In both schemes, the network of :class:`~pennylane.ops.Beamsplitter` operations is followed by
    :math:`M` local :class:`~pennylane.ops.Rotation` Operations.

    The rectangular decomposition is generally advantageous, as it has a lower
    circuit depth (:math:`M` vs :math:`2M-3`) and optical depth than the triangular
    decomposition, resulting in reduced optical loss.

    This is an example of a 4-mode interferometer with beamsplitters :math:`B` and rotations :math:`R`,
    using ``mesh='rectangular'``:

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

    .. note::

        The decomposition as formulated in `Clements et al. <https://dx.doi.org/10.1364/OPTICA.3.001460>`__ uses a different
        convention for a beamsplitter :math:`T(\theta, \phi)` than PennyLane, namely:

        .. math:: T(\theta, \phi) = BS(\theta, 0) R(\phi)

        For the universality of the decomposition, the used convention is irrelevant, but
        for a given set of angles the resulting interferometers will be different.

        If an interferometer consistent with the convention from `Clements et al. <https://dx.doi.org/10.1364/OPTICA.3.001460>`__
        is needed, the optional keyword argument ``beamsplitter='clements'`` can be specified. This
        will result in each :class:`~pennylane.ops.Beamsplitter` being preceded by a :class:`~pennylane.ops.Rotation` and
        thus increase the number of elementary operations in the circuit.

    Args:
        theta (array): length :math:`M(M-1)/2` array of transmittivity angles :math:`\theta`
        phi (array): length :math:`M(M-1)/2` array of phase angles :math:`\phi`
        varphi (array): length :math:`M` array of rotation angles :math:`\varphi`
        wires (Sequence[int]): wires the interferometer should act on
        mesh (string): the type of mesh to use
        beamsplitter (str): if ``clements``, the beamsplitter convention from
          Clements et al. 2016 (https://dx.doi.org/10.1364/OPTICA.3.001460) is used; if ``pennylane``, the
          beamsplitter is implemented via PennyLane's ``Beamsplitter`` operation.

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

    #############
    # Input checks
    check_no_variable(beamsplitter,
                      msg="'beamsplitter' cannot be differentiable")
    check_no_variable(mesh, msg="'mesh' cannot be differentiable")

    wires = check_wires(wires)

    weights_list = [theta, phi, varphi]
    n_wires = len(wires)
    n_if = n_wires * (n_wires - 1) // 2
    expected_shapes = [(n_if, ), (n_if, ), (n_wires, )]
    check_shapes(weights_list,
                 expected_shapes,
                 msg="wrong shape of weight input(s) detected")

    check_is_in_options(
        beamsplitter,
        ["clements", "pennylane"],
        msg="did not recognize option {} for 'beamsplitter'"
        "".format(beamsplitter),
    )
    check_is_in_options(
        mesh,
        ["triangular", "rectangular"],
        msg="did not recognize option {} for 'mesh'"
        "".format(mesh),
    )
    ###############

    M = len(wires)

    if M == 1:
        # the interferometer is a single rotation
        Rotation(varphi[0], wires=wires[0])
        return

    n = 0  # keep track of free parameters

    if mesh == "rectangular":
        # Apply the Clements beamsplitter array
        # The array depth is N
        for l in range(M):
            for k, (w1, w2) in enumerate(zip(wires[:-1], wires[1:])):
                # skip even or odd pairs depending on layer
                if (l + k) % 2 != 1:
                    if beamsplitter == "clements":
                        Rotation(phi[n], wires=[w1])
                        Beamsplitter(theta[n], 0, wires=[w1, w2])
                    else:
                        Beamsplitter(theta[n], phi[n], wires=[w1, w2])
                    n += 1

    elif mesh == "triangular":
        # apply the Reck beamsplitter array
        # The array depth is 2*N-3
        for l in range(2 * M - 3):
            for k in range(abs(l + 1 - (M - 1)), M - 1, 2):
                if beamsplitter == "clements":
                    Rotation(phi[n], wires=[wires[k]])
                    Beamsplitter(theta[n], 0, wires=[wires[k], wires[k + 1]])
                else:
                    Beamsplitter(theta[n],
                                 phi[n],
                                 wires=[wires[k], wires[k + 1]])
                n += 1

    # apply the final local phase shifts to all modes
    for i, p in enumerate(varphi):
        Rotation(p, wires=[wires[i]])
Esempio n. 5
0
def SqueezingEmbedding(features, wires, method="amplitude", c=0.1):
    r"""Encodes :math:`N` features into the squeezing amplitudes :math:`r \geq 0` or phases :math:`\phi \in [0, 2\pi)`
    of :math:`M` modes, where :math:`N\leq M`.

    The mathematical definition of the squeezing gate is given by the operator

    .. math::

        S(z) = \exp\left(\frac{r}{2}\left(e^{-i\phi}\a^2 -e^{i\phi}{\ad}^{2} \right) \right),

    where :math:`\a` and :math:`\ad` are the bosonic creation and annihilation operators.

    ``features`` has to be an iterable of at most ``len(wires)`` floats. If there are fewer entries in
    ``features`` than wires, the circuit does not apply the remaining squeezing gates.

    Args:
        features (array): Array of features of size (N,)
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
            a Wires object.
        method (str): ``'phase'`` encodes the input into the phase of single-mode squeezing, while
            ``'amplitude'`` uses the amplitude
        c (float): value of the phase of all squeezing gates if ``execution='amplitude'``, or the
            amplitude of all squeezing gates if ``execution='phase'``

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

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

    wires = Wires(wires)

    check_no_variable(method, msg="'method' cannot be differentiable")
    check_no_variable(c, msg="'c' cannot be differentiable")

    check_type(c, [float, int], msg="'c' must be of type float or integer; got {}".format(type(c)))

    expected_shape = (len(wires),)
    check_shape(
        features,
        expected_shape,
        bound="max",
        msg="'features' must be of shape {} or smaller; got {}"
        "".format(expected_shape, get_shape(features)),
    )

    check_is_in_options(
        method,
        ["amplitude", "phase"],
        msg="did not recognize option {} for 'method'".format(method),
    )

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

    constants = [c] * len(features)

    if method == "amplitude":
        broadcast(
            unitary=Squeezing,
            pattern="single",
            wires=wires,
            parameters=list(zip(features, constants)),
        )

    elif method == "phase":
        broadcast(
            unitary=Squeezing,
            pattern="single",
            wires=wires,
            parameters=list(zip(constants, features)),
        )
Esempio n. 6
0
def AmplitudeEmbedding(features, wires, pad=None, normalize=False):
    r"""Encodes :math:`2^n` features into the amplitude vector of :math:`n` qubits.

    By setting ``pad`` to a real or complex number, ``features`` is automatically padded to dimension
    :math:`2^n` where :math:`n` is the number of qubits used in the embedding.

    To represent a valid quantum state vector, the L2-norm of ``features`` must be one.
    The argument ``normalize`` can be set to ``True`` to automatically normalize the features.

    If both automatic padding and normalization are used, padding is executed *before* normalizing.

    .. note::

        On some devices, ``AmplitudeEmbedding`` must be the first operation of a quantum node.


    .. warning::

        ``AmplitudeEmbedding`` calls a circuit that involves non-trivial classical processing of the
        features. The ``features`` argument is therefore **not differentiable** when using the template, and
        gradients with respect to the features cannot be computed by PennyLane.

    Args:
        features (array): input array of shape ``(2^n,)``
        wires (Sequence[int] or int): :math:`n` qubit indices that the template acts on
        pad (float or complex): if not None, the input is padded with this constant to size :math:`2^n`
        normalize (Boolean): controls the activation of automatic normalization

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

    .. UsageDetails::

        Amplitude embedding encodes a normalized :math:`2^n`-dimensional feature vector into the state
        of :math:`n` qubits:

        .. code-block:: python

            import pennylane as qml
            from pennylane.templates import AmplitudeEmbedding

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

            @qml.qnode(dev)
            def circuit(f=None):
                AmplitudeEmbedding(features=f, wires=range(2))
                return qml.expval(qml.PauliZ(0))

            circuit(f=[1/2, 1/2, 1/2, 1/2])

        Checking the final state of the device, we find that it is equivalent to the input passed to the circuit:

        >>> dev._state
        [0.5+0.j 0.5+0.j 0.5+0.j 0.5+0.j]

        **Passing features as positional arguments to a quantum node**

        The ``features`` argument of ``AmplitudeEmbedding`` can in principle also be passed to the quantum node
        as a positional argument:

        .. code-block:: python

            @qml.qnode(dev)
            def circuit(f):
                AmplitudeEmbedding(features=f, wires=range(2))
                return qml.expval(qml.PauliZ(0))

        However, due to non-trivial classical processing to construct the state preparation circuit,
        the features argument is **not differentiable**.

        >>> g = qml.grad(circuit, argnum=0)
        >>> g([1,1,1,1])
        ValueError: Cannot differentiate wrt parameter(s) {0, 1, 2, 3}.


        **Normalization**

        The template will raise an error if the feature input is not normalized.
        One can set ``normalize=True`` to automatically normalize it:

        .. code-block:: python

            @qml.qnode(dev)
            def circuit(f=None):
                AmplitudeEmbedding(features=f, wires=range(2), normalize=True)
                return qml.expval(qml.PauliZ(0))

            circuit(f=[15, 15, 15, 15])

        The re-normalized feature vector is encoded into the quantum state vector:

        >>> dev._state
        [0.5 + 0.j, 0.5 + 0.j, 0.5 + 0.j, 0.5 + 0.j]

        **Padding**

        If the dimension of the feature vector is smaller than the number of amplitudes,
        one can automatically pad it with a constant for the missing dimensions using the ``pad`` option:

        .. code-block:: python

            from math import sqrt

            @qml.qnode(dev)
            def circuit(f=None):
                AmplitudeEmbedding(features=f, wires=range(2), pad=0.)
                return qml.expval(qml.PauliZ(0))

            circuit(f=[1/sqrt(2), 1/sqrt(2)])

        >>> dev._state
        [0.70710678 + 0.j, 0.70710678 + 0.j, 0.0 + 0.j, 0.0 + 0.j]

        **Operations before the embedding**

        On some devices, ``AmplitudeEmbedding`` must be the first operation in the quantum node.
        For example, ``'default.qubit'`` complains when running the following circuit:

        .. code-block:: python

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

            @qml.qnode(dev)
            def circuit(f=None):
                qml.Hadamard(wires=0)
                AmplitudeEmbedding(features=f, wires=range(2))
                return qml.expval(qml.PauliZ(0))


        >>> circuit(f=[1/2, 1/2, 1/2, 1/2])
        pennylane._device.DeviceError: Operation QubitStateVector cannot be used
        after other Operations have already been applied on a default.qubit device.

    """

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

    check_no_variable(pad, msg="'pad' cannot be differentiable")
    check_no_variable(normalize, msg="'normalize' cannot be differentiable")

    wires = check_wires(wires)

    n_amplitudes = 2 ** len(wires)
    expected_shape = (n_amplitudes,)
    if pad is None:
        shape = check_shape(
            features,
            expected_shape,
            msg="'features' must be of shape {}; got {}. Use the 'pad' "
            "argument for automated padding."
            "".format(expected_shape, get_shape(features)),
        )
    else:
        shape = check_shape(
            features,
            expected_shape,
            bound="max",
            msg="'features' must be of shape {} or smaller "
            "to be padded; got {}"
            "".format(expected_shape, get_shape(features)),
        )

    check_type(
        pad,
        [float, complex, type(None)],
        msg="'pad' must be a float or complex; got {}".format(pad),
    )
    check_type(normalize, [bool], msg="'normalize' must be a boolean; got {}".format(normalize))

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

    #############
    # Preprocessing

    # pad
    n_features = shape[0]
    if pad is not None and n_amplitudes > n_features:
        features = np.pad(
            features, (0, n_amplitudes - n_features), mode="constant", constant_values=pad
        )

    # normalize
    if isinstance(features[0], Variable):
        feature_values = [s.val for s in features]
        norm = np.sum(np.abs(feature_values) ** 2)
    else:
        norm = np.sum(np.abs(features) ** 2)

    if not np.isclose(norm, 1.0, atol=TOLERANCE):
        if normalize or pad:
            features = features / np.sqrt(norm)
        else:
            raise ValueError(
                "'features' must be a vector of length 1.0; got length {}."
                "Use 'normalization=True' to automatically normalize.".format(norm)
            )

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

    features = np.array(features)
    QubitStateVector(features, wires=wires)
Esempio n. 7
0
def BasicEntanglerLayers(weights, wires, rotation=None):
    r"""Layers consisting of one-parameter single-qubit rotations on each qubit, followed by a closed chain
    or *ring* of CNOT gates.

    The ring of CNOT gates connects every qubit with its neighbour,
    with the last qubit being considered as a neighbour to the first qubit.

    .. figure:: ../../_static/templates/layers/basic_entangler.png
        :align: center
        :width: 40%
        :target: javascript:void(0);

    The number of layers :math:`L` is determined by the first dimension of the argument ``weights``.
    When using a single wire, the template only applies the single
    qubit gates in each layer.

    .. note::

        This template follows the convention of dropping the entanglement between the last and the first
        qubit when using only two wires, so the entangler is not repeated on the same wires.
        In this case, only one CNOT gate is applied in each layer:

        .. figure:: ../../_static/templates/layers/basic_entangler_2wires.png
            :align: center
            :width: 30%
            :target: javascript:void(0);

    Args:
        weights (array[float]): array of weights with shape ``(L, len(wires))``, each weight is used as a parameter
                                for the rotation
        wires (Sequence[int] or int): qubit indices that the template acts on
        rotation (pennylane.ops.Operation): one-parameter single-qubit gate to use,
                                            if ``None``, :class:`~pennylane.ops.RX` is used as default
    Raises:
        ValueError: if inputs do not have the correct format

    .. UsageDetails::

        The template is used inside a qnode:

        .. code-block:: python

            import pennylane as qml
            from pennylane.templates import BasicEntanglerLayers
            from math import pi

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

            @qml.qnode(dev)
            def circuit(weights):
                BasicEntanglerLayers(weights=weights, wires=range(n_wires))
                return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_wires)]

        >>> circuit([[pi, pi, pi]])
        [1., 1., -1.]

        **Parameter initialization function**

        The :mod:`~pennylane.init` module has two parameter initialization functions, ``basic_entangler_layers_normal``
        and ``basic_entangler_layers_uniform``.

        .. code-block:: python

            from pennylane.init import basic_entangler_layers_normal

            n_layers = 4
            weights = basic_entangler_layers_normal(n_layers=n_layers, n_wires=n_wires)

            circuit(weights)


        **No periodic boundary for two wires**

        When using two wires, the convention is to drop the periodic boundary condition.
        This means that the connection from the second to the first wire is omitted.

        .. code-block:: python

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

            @qml.qnode(dev)
            def circuit(weights):
                BasicEntanglerLayers(weights=weights, wires=range(n_wires))
                return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_wires)]

        >>> circuit([[pi, pi]])
        [-1, 1]


        **Changing the rotation gate**

        Any single-qubit gate can be used as a rotation gate, as long as it only takes a single parameter. The default is the ``RX`` gate.

        .. code-block:: python

            @qml.qnode(dev)
            def circuit(weights):
                BasicEntanglerLayers(weights=weights, wires=range(n_wires), rotation=qml.RZ)
                return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_wires)]

        Accidentally using a gate that expects more parameters throws a
        ``ValueError: Wrong number of parameters``.
    """

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

    if rotation is None:
        rotation = RX

    check_no_variable(rotation, msg="'rotation' cannot be differentiable")

    wires = check_wires(wires)

    repeat = check_number_of_layers([weights])

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

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

    for layer in range(repeat):

        broadcast(unitary=rotation,
                  pattern="single",
                  wires=wires,
                  parameters=weights[layer])
        broadcast(unitary=CNOT, pattern="ring", wires=wires)
Esempio n. 8
0
def IQPEmbedding(features, wires, n_repeats=1, pattern=None):
    r"""
    Encodes :math:`n` features into :math:`n` qubits using diagonal gates of an IQP circuit.

    The embedding has been proposed by `Havlicek et al. (2018) <https://arxiv.org/pdf/1804.11326.pdf>`_.

    The basic IQP circuit can be repeated by specifying ``n_repeats``. Repetitions can make the
    embedding "richer" through interference.

    .. warning::

        ``IQPEmbedding`` calls a circuit that involves non-trivial classical processing of the
        features. The ``features`` argument is therefore **not differentiable** when using the template, and
        gradients with respect to the features cannot be computed by PennyLane.

    An IQP circuit is a quantum circuit of a block of Hadamards, followed by a block of gates that are
    diagonal in the computational basis. Here, the diagonal gates are single-qubit ``RZ`` rotations, applied to each
    qubit and encoding the :math:`n` features, followed by two-qubit ZZ entanglers,
    :math:`e^{-i x_i x_j \sigma_z \otimes \sigma_z}`. The entangler applied to wires ``(wires[i], wires[j])``
    encodes the product of features ``features[i]*features[j]``. The pattern in which the entanglers are
    applied is either the default, or a custom pattern:

    * If ``pattern`` is not specified, the default pattern will be used, in which the entangling gates connect all
      pairs of neighbours:

      |

      .. figure:: ../../_static/templates/embeddings/iqp.png
          :align: center
          :width: 50%
          :target: javascript:void(0);

      |

    * Else, ``pattern`` is a list of wire pairs ``[[a, b], [c, d],...]``, applying the entangler
      on wires ``[a, b]``, ``[c, d]``, etc. For example, ``pattern = [[0, 1], [1, 2]]`` produces
      the following entangler pattern:

      |

      .. figure:: ../../_static/templates/embeddings/iqp_custom.png
          :align: center
          :width: 50%
          :target: javascript:void(0);

      |

      Since diagonal gates commute, the order of the entanglers does not change the result.

    Args:
        features (array): array of features to encode
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
            a Wires object.
        n_repeats (int): number of times the basic embedding is repeated
        pattern (list[int]): specifies the wires and features of the entanglers

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

    .. UsageDetails::

        A typical usage example of the template is the following:

        .. code-block:: python

            import pennylane as qml
            from pennylane.templates import IQPEmbedding

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

            @qml.qnode(dev)
            def circuit(features=None):
                IQPEmbedding(features=features, wires=range(3))
                return [qml.expval(qml.PauliZ(w)) for w in range(3)]

            circuit(features=[1., 2., 3.])

        **Do not pass features as a positional argument to the qnode**

        The ``features`` argument cannot be passed to the quantum node
        as a positional argument. This is due to the fact that the embedding performs non-trivial calculations
        on the features. As a consequence, the following code **will produce an error**:

        .. code-block:: python

            @qml.qnode(dev)
            def circuit(features):
               IQPEmbedding(features=features, wires=range(3), n_repeats=2)
               return [qml.expval(qml.PauliZ(w)) for w in range(3)]

            circuit([1., 2., 3.])

        >>> ValueError: 'features' cannot be differentiable

        **Repeating the embedding**

        The embedding can be repeated by specifying the ``n_repeats`` argument:

        .. code-block:: python

            @qml.qnode(dev)
            def circuit(features=None):
                IQPEmbedding(features=features, wires=range(3), n_repeats=4)
                return [qml.expval(qml.PauliZ(w)) for w in range(3)]

            circuit(features=[1., 2., 3.])

        Every repetition uses exactly the same quantum circuit.

        **Using a custom entangler pattern**

        A custom entangler pattern can be used by specifying the ``pattern`` argument. A pattern has to be
        a nested list of dimension ``(K, 2)``, where ``K`` is the number of entanglers to apply.

        .. code-block:: python

            pattern = [[1, 2], [0, 2], [1, 0]]

            @qml.qnode(dev)
            def circuit(features=None):
                IQPEmbedding(features=features, wires=range(3), pattern=pattern)
                return [qml.expval(qml.PauliZ(w)) for w in range(3)]

            circuit(features=[1., 2., 3.])

        Since diagonal gates commute, the order of the wire pairs has no effect on the result.

        .. code-block:: python

            from pennylane import numpy as np

            pattern1 = [[1, 2], [0, 2], [1, 0]]
            pattern2 = [[1, 0], [0, 2], [1, 2]]  # a reshuffling of pattern1

            @qml.qnode(dev)
            def circuit(features=None, pattern=None):
                IQPEmbedding(features=features, wires=range(3), pattern=pattern, n_repeats=3)
                return [qml.expval(qml.PauliZ(w)) for w in range(3)]

            res1 = circuit(features=[1., 2., 3.], pattern=pattern1)
            res2 = circuit(features=[1., 2., 3.], pattern=pattern2)

            assert np.allclose(res1, res2)

        **Non-consecutive wires**

        In principle, the user can also pass a non-consecutive wire list to the template.
        For single qubit gates, the i'th feature is applied to the i'th wire index (which may not be the i'th wire).
        For the entanglers, the product of i'th and j'th features is applied to the wire indices at the i'th and j'th
        position in ``wires``.

        For example, for ``wires=[2, 0, 1]`` the ``RZ`` block applies the first feature to wire 2,
        the second feature to wire 0, and the third feature to wire 1.

        Likewise, using the default pattern, the entangler block applies the product of the first and second
        feature to the wire pair ``[2, 0]``, the product of the second and third feature to ``[2, 1]``, and so
        forth.

    """
    #############
    # Input checks

    wires = Wires(wires)

    check_no_variable(features, msg="'features' cannot be differentiable")

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

    check_type(n_repeats, [int],
               msg="'n_repeats' must be an integer; got type {}".format(
                   type(n_repeats)))

    if pattern is None:
        # default is an all-to-all pattern
        pattern = [Wires(wire_pair) for wire_pair in combinations(wires, 2)]
    else:
        # do some checks
        check_type(
            pattern,
            [Iterable, type(None)],
            msg="'pattern' must be a list of pairs of wires; got {}".format(
                pattern),
        )
        shape = get_shape(pattern)
        if len(shape) != 2 or shape[1] != 2:
            raise ValueError(
                "'pattern' must be a list of pairs of wires; got {}".format(
                    pattern))

        # convert wire pairs to Wires object
        pattern = [Wires(wire_pair) for wire_pair in pattern]

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

    for i in range(n_repeats):

        # first block of Hadamards
        broadcast(unitary=Hadamard, pattern="single", wires=wires)
        # encode features into block of RZ rotations
        broadcast(unitary=RZ,
                  pattern="single",
                  wires=wires,
                  parameters=features)

        # create new features for entangling block
        products = []
        for wire_pair in pattern:
            # get the position of the wire indices in the array
            idx1, idx2 = wires.indices(wire_pair)
            # create products of parameters
            products.append(features[idx1] * features[idx2])

        broadcast(unitary=MultiRZ,
                  pattern=pattern,
                  wires=wires,
                  parameters=products)
Esempio n. 9
0
def AngleEmbedding(features, wires, rotation="X"):
    r"""
    Encodes :math:`N` features into the rotation angles of :math:`n` qubits, where :math:`N \leq n`.

    The rotations can be chosen as either :class:`~pennylane.ops.RX`, :class:`~pennylane.ops.RY`
    or :class:`~pennylane.ops.RZ` gates, as defined by the ``rotation`` parameter:

    * ``rotation='X'`` uses the features as angles of RX rotations

    * ``rotation='Y'`` uses the features as angles of RY rotations

    * ``rotation='Z'`` uses the features as angles of RZ rotations

    The length of ``features`` has to be smaller or equal to the number of qubits. If there are fewer entries in
    ``features`` than rotations, the circuit does not apply the remaining rotation gates.

    Args:
        features (array): input array of shape ``(N,)``, where N is the number of input features to embed,
            with :math:`N\leq n`
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
            a Wires object.
        rotation (str): Type of rotations used

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

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

    wires = Wires(wires)

    check_no_variable(rotation, msg="'rotation' cannot be differentiable")

    check_shape(
        features,
        (len(wires), ),
        bound="max",
        msg="'features' must be of shape {} or smaller; "
        "got {}.".format((len(wires), ), get_shape(features)),
    )
    check_type(rotation, [str],
               msg="'rotation' must be a string; got {}".format(rotation))

    check_is_in_options(
        rotation,
        ["X", "Y", "Z"],
        msg="did not recognize option {} for 'rotation'.".format(rotation),
    )

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

    if rotation == "X":
        broadcast(unitary=RX,
                  pattern="single",
                  wires=wires,
                  parameters=features)

    elif rotation == "Y":
        broadcast(unitary=RY,
                  pattern="single",
                  wires=wires,
                  parameters=features)

    elif rotation == "Z":
        broadcast(unitary=RZ,
                  pattern="single",
                  wires=wires,
                  parameters=features)
Esempio n. 10
0
def DisplacementEmbedding(features, wires, method="amplitude", c=0.1):
    r"""Encodes :math:`N` features into the displacement amplitudes :math:`r` or phases :math:`\phi` of :math:`M` modes,
     where :math:`N\leq M`.

    The mathematical definition of the displacement gate is given by the operator

    .. math::
            D(\alpha) = \exp(r (e^{i\phi}\ad -e^{-i\phi}\a)),

    where :math:`\a` and :math:`\ad` are the bosonic creation and annihilation operators.

    ``features`` has to be an array of at most ``len(wires)`` floats. If there are fewer entries in
    ``features`` than wires, the circuit does not apply the remaining displacement gates.

    Args:
        features (array): Array of features of size (N,)
        wires (Sequence[int]): sequence of mode indices that the template acts on
        method (str): ``'phase'`` encodes the input into the phase of single-mode displacement, while
            ``'amplitude'`` uses the amplitude
        c (float): value of the phase of all displacement gates if ``execution='amplitude'``, or
            the amplitude of all displacement gates if ``execution='phase'``

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

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

    check_no_variable(method, msg="'method' cannot be differentiable")
    check_no_variable(c, msg="'c' cannot be differentiable")

    wires = check_wires(wires)

    expected_shape = (len(wires),)
    check_shape(
        features,
        expected_shape,
        bound="max",
        msg="'features' must be of shape {} or smaller; got {}."
        "".format(expected_shape, get_shape(features)),
    )

    check_is_in_options(
        method,
        ["amplitude", "phase"],
        msg="did not recognize option {} for 'method'" "".format(method),
    )

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

    constants = [c] * len(features)

    if method == "amplitude":
        broadcast(
            unitary=Displacement,
            pattern="single",
            wires=wires,
            parameters=list(zip(features, constants)),
        )

    elif method == "phase":
        broadcast(
            unitary=Displacement,
            pattern="single",
            wires=wires,
            parameters=list(zip(constants, features)),
        )
Esempio n. 11
0
 def test_check_no_variable_exception(self, arg):
     """Tests that variable check throws error for invalid arguments."""
     with pytest.raises(ValueError, match="XXX"):
         check_no_variable(arg, msg="XXX")
Esempio n. 12
0
 def test_check_no_variable(self, arg):
     """Tests that variable check succeeds for valid arguments."""
     check_no_variable(arg, msg="XXX")