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)
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])
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])
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)
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)
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])
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)
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)
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)
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)
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]])
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")
def test_check_no_variable(self, arg): """Tests that variable check succeeds for valid arguments.""" _check_no_variable(arg, msg="XXX")
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)