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 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 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 CVNeuralNetLayers(theta_1, phi_1, varphi_1, r, phi_r, theta_2, phi_2, varphi_2, a, phi_a, k, wires): r"""A sequence of layers of a continuous-variable quantum neural network, as specified in `arXiv:1806.06871 <https://arxiv.org/abs/1806.06871>`_. The layer consists of interferometers, displacement and squeezing gates mimicking the linear transformation of a neural network in the x-basis of the quantum system, and uses a Kerr gate to introduce a 'quantum' nonlinearity. The layers act on the :math:`M` modes given in ``wires``, and include interferometers of :math:`K=M(M-1)/2` beamsplitters. The different weight parameters contain the weights for each layer. The number of layers :math:`L` is therefore derived from the first dimension of ``weights``. This example shows a 4-mode CVNeuralNet layer with squeezing gates :math:`S`, displacement gates :math:`D` and Kerr gates :math:`K`. The two big blocks are interferometers of type :mod:`pennylane.templates.layers.Interferometer`: .. figure:: ../../_static/layer_cvqnn.png :align: center :width: 60% :target: javascript:void(0); .. note:: The CV neural network architecture includes :class:`~pennylane.ops.Kerr` operations. Make sure to use a suitable device, such as the :code:`strawberryfields.fock` device of the `PennyLane-SF <https://github.com/XanaduAI/pennylane-sf>`_ plugin. Args: theta_1 (array[float]): length :math:`(L, K)` array of transmittivity angles for first interferometer phi_1 (array[float]): length :math:`(L, K)` array of phase angles for first interferometer varphi_1 (array[float]): length :math:`(L, M)` array of rotation angles to apply after first interferometer r (array[float]): length :math:`(L, M)` array of squeezing amounts for :class:`~pennylane.ops.Squeezing` operations phi_r (array[float]): length :math:`(L, M)` array of squeezing angles for :class:`~pennylane.ops.Squeezing` operations theta_2 (array[float]): length :math:`(L, K)` array of transmittivity angles for second interferometer phi_2 (array[float]): length :math:`(L, K)` array of phase angles for second interferometer varphi_2 (array[float]): length :math:`(L, M)` array of rotation angles to apply after second interferometer a (array[float]): length :math:`(L, M)` array of displacement magnitudes for :class:`~pennylane.ops.Displacement` operations phi_a (array[float]): length :math:`(L, M)` array of displacement angles for :class:`~pennylane.ops.Displacement` operations k (array[float]): length :math:`(L, M)` array of kerr parameters for :class:`~pennylane.ops.Kerr` operations wires (Sequence[int]): sequence of mode indices that the template acts on Raises: ValueError: if inputs do not have the correct format """ ############# # Input checks wires, n_wires = _check_wires(wires) n_if = n_wires * (n_wires - 1) // 2 weights_list = [ theta_1, phi_1, varphi_1, r, phi_r, theta_2, phi_2, varphi_2, a, phi_a, k ] repeat = _check_number_of_layers(weights_list) shapes_list = [(repeat, n_if), (repeat, n_if), (repeat, n_wires), (repeat, n_wires), (repeat, n_wires), (repeat, n_if), (repeat, n_if), (repeat, n_wires), (repeat, n_wires), (repeat, n_wires), (repeat, n_wires)] _check_shapes(weights_list, shapes_list) _check_type(repeat, [int]) ############### for l in range(repeat): _cv_neural_net_layer(theta_1=theta_1[l], phi_1=phi_1[l], varphi_1=varphi_1[l], r=r[l], phi_r=phi_r[l], theta_2=theta_2[l], phi_2=phi_2[l], varphi_2=varphi_2[l], a=a[l], phi_a=phi_a[l], k=k[l], wires=wires)
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 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")
def test_check_type(self, hp, typ, alt): """Tests that type check succeeds for valid arguments.""" _check_type(hp, [typ, alt], 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)
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); 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 (Sequence[int] or int): wire indices that the unitaries act upon 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 @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 @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]) """ OPTIONS = [ "single", "double", "double_odd", "chain", "ring", "pyramid", "all_to_all" ] ######### # Input checks wires = _check_wires(wires) _check_type( parameters, [Iterable, type(None)], msg="'parameters' must be either of type None or " "Iterable; got {}".format(type(parameters)), ) _check_type( pattern, [str], msg="'pattern' must be a string; got {}".format(type(pattern)), ) if kwargs is None: kwargs = {} _check_type( kwargs, [dict], msg="'kwargs' must be a dictionary; got {}".format(type(kwargs)), ) _check_is_in_options( pattern, OPTIONS, msg="did not recognize option {} for 'pattern'".format(pattern), ) 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, } # check that 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 sequence for patterns wire_sequence = { "single": wires, "double": [[wires[i], wires[i + 1]] for i in range(0, len(wires) - 1, 2)], "double_odd": [[wires[i], wires[i + 1]] for i in range(1, len(wires) - 1, 2)], "chain": [[wires[i], wires[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), } # broadcast the unitary for w, p in zip(wire_sequence[pattern], parameters): unitary(*p, wires=w, **kwargs)
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") _check_type(c, [float, int], msg="'c' must be of type float or integer; got {}".format( type(c))) wires = _check_wires(wires) expected_shape = (len(wires), ) _check_shape( features, expected_shape, bound="max", msg="'features' must be of shape {} or smaller; got {}" "".format(expected_shape, _get_shape(features)), ) _check_is_in_options( method, ["amplitude", "phase"], msg="did not recognize option {} for 'method'".format(method), ) ############## constants = [c] * len(features) if method == "amplitude": broadcast( unitary=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)), )