Esempio n. 1
0
def _preprocess(features, wires):
    """Validate and pre-process inputs as follows:

    * Check that the features tensor is one-dimensional.
    * Check that the first dimension of the features tensor
      has length :math:`n`, where :math:`n` is the number of qubits.
    * Check that the entries of the features tensor are zeros and ones.

    Args:
        features (tensor_like): input features to pre-process
        wires (Wires): wires that template acts on

    Returns:
        array: numpy array representation of the features tensor
    """

    if qml.tape_mode_active():

        shape = qml.math.shape(features)

        if len(shape) != 1:
            raise ValueError(
                f"Features must be one-dimensional; got shape {shape}.")

        n_features = shape[0]
        if n_features != len(wires):
            raise ValueError(
                f"Features must be of length {len(wires)}; got length {n_features}."
            )

        features = list(qml.math.toarray(features))

        if set(features) != {0, 1}:
            raise ValueError(
                f"Basis state must only consist of 0s and 1s; got {features}")

        return features

    # non-tape mode
    check_type(
        features,
        [Iterable],
        msg="Features must be iterable; got type {}".format(type(features)),
    )

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

    if any([b not in [0, 1] for b in features]):
        raise ValueError(
            "Basis state must only consist of 0s and 1s; got {}".format(
                features))

    return features
Esempio n. 2
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_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. 3
0
def BasisEmbedding(features, wires):
    r"""Encodes :math:`n` binary features into a basis state of :math:`n` qubits.

    For example, for ``features=np.array([0, 1, 0])``, the quantum system will be
    prepared in state :math:`|010 \rangle`.

    .. warning::

        ``BasisEmbedding`` calls a circuit whose architecture depends on the binary features.
        The ``features`` argument is therefore not differentiable when using the template, and
        gradients with respect to the argument cannot be computed by PennyLane.

    Args:
        features (array): binary input array of shape ``(n, )``
        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)

    check_type(features, [Iterable],
               msg="'features' must be iterable; got type {}".format(
                   type(features)))

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

    if any([b not in [0, 1] for b in features]):
        raise ValueError(
            "'basis_state' must only consist of 0s and 1s; got {}".format(
                features))

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

    wires = wires.tolist()  # TODO: Remove when operators take Wires objects

    for wire, bit in zip(wires, features):
        if bit == 1:
            qml.PauliX(wire)
Esempio n. 4
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. 5
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. 6
0
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)
Esempio n. 7
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)
Esempio n. 8
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. 9
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. 10
0
def _preprocess(features, wires, pattern, n_repeats):
    """Validate and pre-process inputs as follows:

    * Check that the features tensor is one-dimensional.
    * Check that the first dimension of the features tensor
      has length :math:`n`, where :math:`n` is the number of qubits.
    * If pattern is None, create default pattern. Else cast wires in user-provided pattern to Wires objects.

    Args:
        features (tensor_like): input features to pre-process
        wires (Wires): wires that template acts on
        pattern (list[int]): specifies the wires and features of the entanglers
        n_repeats (int): number of times the basic embedding is repeated

    Returns:
        list[Wires]: preprocessed pattern
    """

    if qml.tape_mode_active():

        shape = qml.math.shape(features)

        if len(shape) != 1:
            raise ValueError(
                f"Features must be a one-dimensional tensor; got shape {shape}."
            )

        n_features = shape[0]
        if n_features != len(wires):
            raise ValueError(
                f"Features must be of length {len(wires)}; got length {n_features}."
            )

        if pattern is None:
            # default is an all-to-all pattern
            pattern = [
                Wires(wire_pair) for wire_pair in combinations(wires, 2)
            ]
        else:
            # convert wire pairs to Wires object
            pattern = [Wires(wire_pair) for wire_pair in pattern]

    else:
        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]

    return pattern
Esempio n. 11
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. 12
0
def _preprocess(features, wires, pad_with, normalize):
    """Validate and pre-process inputs as follows:

    * Check that the features tensor is one-dimensional.
    * If pad_with is None, check that the first dimension of the features tensor
      has length :math:`2^n` where :math:`n` is the number of qubits. Else check that the
      first dimension of the features tensor is not larger than :math:`2^n` and pad features with value if necessary.
    * If normalize is false, check that first dimension of features is normalised to one. Else, normalise the
      features tensor.

    Args:
        features (tensor_like): input features to pre-process
        wires (Wires): wires that template acts on
        pad_with (float): constant used to pad the features tensor to required dimension
        normalize (bool): whether or not to normalize the features vector

    Returns:
        tensor: pre-processed features
    """

    if qml.tape_mode_active():

        shape = qml.math.shape(features)

        # check shape
        if len(shape) != 1:
            raise ValueError(
                f"Features must be a one-dimensional tensor; got shape {shape}."
            )

        n_features = shape[0]
        if pad_with is None and n_features != 2**len(wires):
            raise ValueError(
                f"Features must be of length {2 ** len(wires)}; got length {n_features}. "
                f"Use the 'pad' argument for automated padding.")

        if pad_with is not None and n_features > 2**len(wires):
            raise ValueError(
                f"Features must be of length {2 ** len(wires)} or "
                f"smaller to be padded; got length {n_features}.")

        # pad
        if pad_with is not None and n_features < 2**len(wires):
            padding = [pad_with] * (2**len(wires) - n_features)
            features = qml.math.concatenate([features, padding], axis=0)

        # normalize
        norm = qml.math.sum(qml.math.abs(features)**2)

        if not qml.math.allclose(norm, 1.0, atol=TOLERANCE):
            if normalize or pad_with:
                features = features / np.sqrt(norm)
            else:
                raise ValueError(
                    f"Features must be a vector of length 1.0; got length {norm}. "
                    "Use 'normalize=True' to automatically normalize.")

    # todo: delete if tape is only core
    else:
        n_amplitudes = 2**len(wires)
        expected_shape = (n_amplitudes, )

        if len(get_shape(features)) > 1:
            raise ValueError(
                f"Features must be a one-dimensional vector; got shape {get_shape(features)}."
            )

        if pad_with is None:
            shape = check_shape(
                features,
                expected_shape,
                msg="Features must be of length {}; 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 length {} or smaller "
                "to be padded; got {}"
                "".format(expected_shape, get_shape(features)),
            )

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

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

        # 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_with:
                features = features / np.sqrt(norm)
            else:
                raise ValueError(
                    "Features must be a vector of length 1.0; got length {}."
                    "Use 'normalize=True' to automatically normalize.".format(
                        norm))

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

        features = np.array(features)

    return features
Esempio n. 13
0
 def test_check_type_exception(self, hp, typ, alt):
     """Tests that type check throws error for invalid arguments."""
     with pytest.raises(ValueError, match="XXX"):
         check_type(hp, [typ, alt], msg="XXX")
Esempio n. 14
0
 def test_check_type(self, hp, typ, alt):
     """Tests that type check succeeds for valid arguments."""
     check_type(hp, [typ, alt], msg="XXX")
Esempio n. 15
0
def UCCSD(weights, wires, s_wires=None, d_wires=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(s_wires) + len(d_wires)`` vector containing the parameters
            :math:`\theta_{pr}` and :math:`\theta_{pqrs}` entering the Z rotation in
            :func:`~.SingleExcitationUnitary`
            and
            :func:`~.DoubleExcitationUnitary`. These parameters are the coupled-cluster
            amplitudes that need to be optimized for each single and double excitation generated
            with the :func:`~.excitations` function.
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers
            or strings, or a Wires object.
        s_wires (Sequence[Sequence]): Sequence of lists containing the wires ``[r,...,p]``
            resulting from the single excitation
            :math:`\vert r, p \rangle = \hat{c}_p^\dagger \hat{c}_r \vert \mathrm{HF} \rangle`,
            where :math:`\vert \mathrm{HF} \rangle` denotes the Hartee-Fock reference state.
            The first (last) entry ``r`` (``p``) is considered the wire representing the
            occupied (unoccupied) orbital where the electron is annihilated (created).
        d_wires (Sequence[Sequence[Sequence]]): Sequence of lists, each containing two lists that
            specify the indices ``[s, ...,r]`` and ``[q,..., p]`` defining the double excitation
            :math:`\vert s, r, q, p \rangle = \hat{c}_p^\dagger \hat{c}_q^\dagger \hat{c}_r
            \hat{c}_s \vert \mathrm{HF} \rangle`. The entries ``s`` and ``r`` are wires
            representing two occupied orbitals where the two electrons are annihilated
            while the entries ``q`` and ``p`` correspond to the wires representing two unoccupied
            orbitals where the electrons are created. Wires in-between represent the occupied
            and unoccupied orbitals in the intervals ``[s, r]`` and ``[q, p]``, respectively.
        init_state (array[int]): Length ``len(wires)`` occupation-number vector representing the
            HF state. ``init_state`` is used to initialize the wires.

    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 single and double excitations can be generated be generated with the function
           :func:`~.excitations`. See example below.

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


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

        .. code-block:: python

            import pennylane as qml
            from pennylane import qchem
            from pennylane.templates import UCCSD

            from functools import partial

            # Build the electronic Hamiltonian
            name = "h2"
            geo_file = "h2.xyz"
            h, qubits = qchem.molecular_hamiltonian(name, geo_file)

            # Number of electrons
            electrons = 2

            # Define the HF state
            ref_state = qchem.hf_state(electrons, qubits)

            # Generate single and double excitations
            singles, doubles = qchem.excitations(electrons, qubits)

            # Map excitations to the wires the UCCSD circuit will act on
            s_wires, d_wires = qchem.excitations_to_wires(singles, doubles)

            # Define the device
            dev = qml.device('default.qubit', wires=qubits)

            # Define the UCCSD ansatz
            ansatz = partial(UCCSD, init_state=ref_state, s_wires=s_wires, d_wires=d_wires)

            # Define the cost function
            cost_fn = qml.ExpvalCost(ansatz, h, dev)

            # Compute the expectation value of 'h' for given set of parameters 'params'
            params = np.random.normal(0, np.pi, len(singles) + len(doubles))
            print(cost_fn(params))

    """

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

    wires = Wires(wires)

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

    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, np.ndarray],
            msg="Elements of 'init_state' must be integers; got {}".format(
                init_state),
        )

    expected_shape = (len(s_wires) + len(d_wires), )
    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)),
    )

    for d_wires_ in d_wires:
        if len(d_wires_) != 2:
            raise ValueError(
                "expected entries of d_wires to be of size 2; got {} of length {}"
                .format(d_wires_, len(d_wires_)))

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

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

    # turn wire arguments into Wires objects
    s_wires = [Wires(w) for w in s_wires]
    d_wires = [[Wires(w1), Wires(w2)] for w1, w2 in d_wires]

    for i, (w1, w2) in enumerate(d_wires):
        DoubleExcitationUnitary(weights[len(s_wires) + i],
                                wires1=w1,
                                wires2=w2)

    for j, s_wires_ in enumerate(s_wires):
        SingleExcitationUnitary(weights[j], wires=s_wires_)
Esempio n. 16
0
def SimplifiedTwoDesign(initial_layer_weights, weights, wires):
    r"""
    Layers consisting of a simplified 2-design architecture of Pauli-Y rotations and controlled-Z entanglers
    proposed in `Cerezo et al. (2020) <https://arxiv.org/abs/2001.00550>`_.

    A 2-design is an ensemble of unitaries whose statistical properties are the same as sampling random unitaries
    with respect to the Haar measure up to the first 2 moments.

    The template is not a strict 2-design, since
    it does not consist of universal 2-qubit gates as building blocks, but has been shown in
    `Cerezo et al. (2020) <https://arxiv.org/abs/2001.00550>`_ to exhibit important properties to study "barren plateaus"
    in quantum optimization landscapes.

    The template starts with an initial layer of single qubit Pauli-Y rotations, before the main
    :math:`L` layers are applied. The basic building block of the main layers are controlled-Z entanglers
    followed by a pair of Pauli-Y rotation gates (one for each wire).
    Each layer consists of an "even" part whose entanglers start with the first qubit,
    and an "odd" part that starts with the second qubit.

    This is an example of two layers, including the initial layer:

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

    |

    The argument ``initial_layer_weights`` contains the rotation angles of the initial layer of Pauli-Y rotations,
    while ``weights`` contains the pairs of Pauli-Y rotation angles of the respective layers. Each layer takes
    :math:`\lfloor M/2 \rfloor + \lfloor (M-1)/2 \rfloor = M-1` pairs of angles, where :math:`M` is the number of wires.
    The number of layers :math:`L` is derived from the first dimension of ``weights``.

    Args:
        initial_layer_weights (array[float]): array of weights for the initial rotation block, shape ``(M,)``
        weights (array[float]): array of rotation angles for the layers, shape ``(L, M-1, 2)``
        wires (Sequence[int] or int): qubit indices that the template acts on

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

    .. UsageDetails::

        template - here shown for two layers - is used inside a :class:`~.QNode`:

        .. code-block:: python

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

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

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

            init_weights = [pi, pi, pi]
            weights_layer1 = [[0., pi],
                              [0., pi]]
            weights_layer2 = [[pi, 0.],
                              [pi, 0.]]
            weights = [weights_layer1, weights_layer2]

            >>> circuit(init_weights, weights)
            [1., -1., 1.]

        **Parameter initialization function**

        The :mod:`~pennylane.init` module contains four parameter initialization functions:

        * ``simplified_two_design_initial_layer_normal``
        * ``simplified_two_design_initial_layer_uniform``
        * ``simplified_two_design_weights_normal``.
        * ``simplified_two_design_weights_uniform``.

        They can be used as follows:

        .. code-block:: python

            from pennylane.init import (simplified_two_design_initial_layer_normal,
                                        simplified_two_design_weights_normal)

            n_layers = 4
            init_weights = simplified_two_design_initial_layer_normal(n_wires)
            weights = simplified_two_design_weights_normal(n_layers, n_wires)

            >>> circuit(initial_layer_weights, weights)

    """

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

    wires = check_wires(wires)

    repeat = check_number_of_layers([weights])

    check_type(
        initial_layer_weights,
        [list, np.ndarray],
        msg="'initial_layer_weights' must be of type list or np.ndarray; got type {}".format(
            type(initial_layer_weights)
        ),
    )
    check_type(
        weights,
        [list, np.ndarray],
        msg="'weights' must be of type list or np.ndarray; got type {}".format(type(weights)),
    )

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

    if len(wires) in [0, 1]:
        expected_shape_weights = (0,)
    else:
        expected_shape_weights = (repeat, len(wires) - 1, 2)

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

    ###############
    # initial rotations
    broadcast(unitary=RY, pattern="single", wires=wires, parameters=initial_layer_weights)

    # alternate layers
    for layer in range(repeat):

        # even layer
        weights_even = weights[layer][: len(wires) // 2]
        broadcast(unitary=entangler, pattern="double", wires=wires, parameters=weights_even)

        # odd layer
        weights_odd = weights[layer][len(wires) // 2 :]
        broadcast(unitary=entangler, pattern="double_odd", wires=wires, parameters=weights_odd)