Пример #1
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 (Sequence[int] or int): qubit indices that the template acts on
        rotation (str): Type of rotations used

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

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

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

    wires = _check_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":
        for f, w in zip(features, wires):
            RX(f, wires=w)
    elif rotation == "Y":
        for f, w in zip(features, wires):
            RY(f, wires=w)
    elif rotation == "Z":
        for f, w in zip(features, wires):
            RZ(f, wires=w)
Пример #2
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),
    )

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

    for idx, f in enumerate(features):
        if method == "amplitude":
            Displacement(f, c, wires=wires[idx])
        elif method == "phase":
            Displacement(c, f, wires=wires[idx])
Пример #3
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 (Sequence[int]): sequence of mode indices that the template acts on
        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

    _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))

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

    for idx, f in enumerate(features):
        if method == 'amplitude':
            Squeezing(f, c, wires=wires[idx])
        elif method == 'phase':
            Squeezing(c, f, wires=wires[idx])
Пример #4
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 (Sequence[int]): sequence of qubit indices that the template acts on

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

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

    wires = _check_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))

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

    for wire, state in zip(wires, basis_state):
        if state == 1:
            qml.PauliX(wire)
Пример #5
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 (Sequence[int] or int): qubit indices that the template acts on
        rotation (str): Type of rotations used

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

    #############
    # Input checks
    _check_no_variable([rotation], ['rotation'])
    wires, n_wires = _check_wires(wires)

    msg = "AngleEmbedding cannot process more features than number of qubits {};" \
          "got {}.".format(n_wires, len(features))
    _check_shape(features, (n_wires,), bound='max', msg=msg)
    _check_type(rotation, [str])

    msg = "Rotation strategy {} not recognized.".format(rotation)
    _check_hyperp_is_in_options(rotation, ['X', 'Y', 'Z'], msg=msg)
    ###############

    if rotation == 'X':
        for f, w in zip(features, wires):
            RX(f, wires=w)
    elif rotation == 'Y':
        for f, w in zip(features, wires):
            RY(f, wires=w)
    elif rotation == 'Z':
        for f, w in zip(features, wires):
            RZ(f, wires=w)
Пример #6
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, c], ['method', 'c'])

    wires, n_wires = _check_wires(wires)

    msg = "DisplacementEmbedding cannot process more features than number of wires {};" \
          "got {}.".format(n_wires, len(features))
    _check_shape(features, (n_wires,), bound='max', msg=msg)

    msg = "Did not recognise parameter encoding method {}.".format(method)
    _check_hyperp_is_in_options(method, ['amplitude', 'phase'], msg=msg)
    #############

    for idx, f in enumerate(features):
        if method == 'amplitude':
            Displacement(f, c, wires=wires[idx])
        elif method == 'phase':
            Displacement(c, f, wires=wires[idx])
Пример #7
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 (Sequence[int]): sequence of qubit indices that the template acts on

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

    ######################
    # Input checks
    wires, n_wires = _check_wires(wires)

    msg = "The size of the basis state must match the number of qubits {}; got {}.".format(
        n_wires, len(basis_state))
    _check_shape(basis_state, (n_wires, ), msg=msg)

    # basis_state cannot be trainable
    msg = "Basis state influences circuit architecture and can therefore not be passed as a " \
           "positional argument to the quantum node."
    _check_no_variable([basis_state], ['basisstate'], msg=msg)

    # basis_state is guaranteed to be a list
    if any([b not in [0, 1] for b in basis_state]):
        raise ValueError(
            "Basis state must only consist of 0s and 1s, got {}".format(
                basis_state))
    ######################

    for wire, state in zip(wires, basis_state):
        if state == 1:
            qml.PauliX(wire)
Пример #8
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.


    .. note::

        ``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.

    .. 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 argument 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, rtol=0):
        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)
Пример #9
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.

    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);

    .. note::
        Using the default seed (or any other fixed integer seed) generates one and the same circuit in every
        quantum node. To generate different circuit architectures, either use a different random seed, or use ``seed=None``
        together with the ``cache=False`` option when creating a quantum node.

    .. warning::
        When using a random number generator anywhere inside the quantum function without the ``cache=False`` option,
        a new random circuit architecture will be created every time the quantum node is evaluated.

    Args:
        weights (array[float]): array of weights of shape ``(L, k)``,
        wires (Sequence[int]): sequence of qubit indices that the template acts on
        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

    Raises:
        ValueError: if inputs do not have the correct format
    """
    if seed is not None:
        np.random.seed(seed)

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

    #############
    # Input checks
    hyperparams = [ratio_imprim, imprimitive, rotations, seed]
    hyperparam_names = ['ratio_imprim', 'imprimitive', 'rotations', 'seed']
    _check_no_variable(hyperparams, hyperparam_names)

    wires, _ = _check_wires(wires)

    repeat = _check_number_of_layers([weights])
    n_rots = _get_shape(weights)[1]

    _check_shape(weights, (repeat, n_rots))

    _check_type(ratio_imprim, [float, type(None)])
    _check_type(n_rots, [int, type(None)])
    _check_type(rotations, [list, type(None)])
    _check_type(seed, [int, type(None)])
    ###############

    for l in range(repeat):
        _random_layer(weights=weights[l],
                      wires=wires,
                      ratio_imprim=ratio_imprim,
                      imprimitive=imprimitive,
                      rotations=rotations,
                      seed=seed)
Пример #10
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 ``(:math:`L`, :math:`M`, 3)``
        wires (Sequence[int] or int): qubit indices that the template acts on
        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
    _check_no_variable([ranges, imprimitive], ['ranges', 'imprimitive'])

    wires, n_wires = _check_wires(wires)

    repeat = _check_number_of_layers([weights])

    _check_shape(weights, (repeat, n_wires, 3))

    _check_type(ranges, [list, type(None)])

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

    msg = "StronglyEntanglingLayers expects ``ranges`` to contain a range for each layer; " \
          "got {}.".format(len(ranges))
    _check_shape(ranges, (repeat, ), msg=msg)
    msg = "StronglyEntanglingLayers expects ``ranges`` to be a list of integers; got {}.".format(
        len(ranges))
    _check_type(ranges[0], [int], msg=msg)
    if any((r >= n_wires or r == 0) for r in ranges):
        raise ValueError(
            "The range hyperparameter for all layers needs to be smaller than the number of "
            "qubits; got ranges {}.".format(ranges))
    ###############

    for l in range(repeat):

        _strongly_entangling_layer(weights=weights[l],
                                   wires=wires,
                                   r=ranges[l],
                                   imprimitive=imprimitive)
Пример #11
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]])
Пример #12
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")
Пример #13
0
 def test_check_no_variable(self, arg):
     """Tests that variable check succeeds for valid arguments."""
     _check_no_variable(arg, msg="XXX")
Пример #14
0
def AmplitudeEmbedding(features, wires, pad=None, normalize=False):
    r"""Encodes :math:`2^n` features into the amplitude vector of :math:`n` qubits.

    If the total number of features to embed is less than the :math:`2^n` available amplitudes,
    non-informative constants (zeros) can be padded to ``features``. To enable this, the argument
    ``pad`` should be set to ``True``.

    The L2-norm of ``features`` must be one. By default, ``AmplitudeEmbedding`` expects a normalized
    feature vector. The argument ``normalize`` can be set to ``True`` to automatically normalize it.

    .. 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 argument cannot be computed by PennyLane.

    Args:
        features (array): input array of shape ``(2^n,)``
        wires (Sequence[int] or int): 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
    """

    #############
    # Input checks
    _check_no_variable([pad, normalize], ['pad', 'normalize'])
    wires, n_wires = _check_wires(wires)

    n_ampl = 2**n_wires
    if pad is None:
        msg = "AmplitudeEmbedding must get a feature vector of size 2**len(wires), which is {}. Use 'pad' " \
               "argument for automated padding.".format(n_ampl)
        shp = _check_shape(features, (n_ampl,), msg=msg)
    else:
        msg = "AmplitudeEmbedding must get a feature vector of at least size 2**len(wires) = {}.".format(n_ampl)
        shp = _check_shape(features, (n_ampl,), msg=msg, bound='max')

    _check_type(pad, [float, complex, type(None)])
    _check_type(normalize, [bool])
    ###############

    # Pad
    n_feats = shp[0]
    if pad is not None and n_ampl > n_feats:
        features = np.pad(features, (0, n_ampl-n_feats), 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, rtol=0):
        if normalize or pad:
            features = features/np.sqrt(norm)
        else:
            raise ValueError("Vector of features has to be normalized to 1.0, got {}."
                             "Use 'normalization=True' to automatically normalize.".format(norm))

    features = np.array(features)
    QubitStateVector(features, wires=wires)