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 BasisEmbedding(features, wires): r"""Encodes :math:`n` binary features into a basis state of :math:`n` qubits. For example, for ``features=np.array([0, 1, 0])``, the quantum system will be prepared in state :math:`|010 \rangle`. .. warning:: ``BasisEmbedding`` calls a circuit whose architecture depends on the binary features. The ``features`` argument is therefore not differentiable when using the template, and gradients with respect to the argument cannot be computed by PennyLane. Args: features (array): binary input array of shape ``(n, )`` wires (Sequence[int] or int): 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) _check_shape(features, (n_wires,)) # basis_state is guaranteed to be a list if any([b not in [0, 1] for b in features]): raise ValueError("Basis state must only consist of 0s and 1s, got {}".format(features)) ############### features = np.array(features) BasisState(features, wires=wires)
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 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 MottonenStatePreparation(state_vector, wires): r""" Prepares an arbitrary state on the given wires using a decomposition into gates developed by Möttönen et al. (Quantum Info. Comput., 2005). The state is prepared via a sequence of "uniformly controlled rotations". A uniformly controlled rotation on a target qubit is composed from all possible controlled rotations on said qubit and can be used to address individual elements of the state vector. In the work of Mottonen et al., the inverse of their state preparation is constructed by first equalizing the phases of the state vector via uniformly controlled Z rotations and then rotating the now real state vector into the direction of the state :math:`|0\rangle` via uniformly controlled Y rotations. This code is adapted from code written by Carsten Blank for PennyLane-Qiskit. Args: state_vector (array): Input array of shape ``(2^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. 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 state vector must be of size {}; got {}.".format( 2**n_wires, len(state_vector)) _check_shape(state_vector, (2**n_wires, ), msg=msg) # check if state_vector is normalized if isinstance(state_vector[0], Variable): state_vector_values = [s.val for s in state_vector] norm = np.sum(np.abs(state_vector_values)**2) else: norm = np.sum(np.abs(state_vector)**2) if not np.isclose(norm, 1.0, atol=1e-3): raise ValueError( "State vector probabilities have to sum up to 1.0, got {}".format( norm)) ####################### # Change ordering of indices, original code was for IBM machines state_vector = np.array(state_vector).reshape( [2] * n_wires).T.flatten()[:, np.newaxis] state_vector = sparse.dok_matrix(state_vector) wires = np.array(wires) a = sparse.dok_matrix(state_vector.shape) omega = sparse.dok_matrix(state_vector.shape) for (i, j), v in state_vector.items(): if isinstance(v, Variable): a[i, j] = np.absolute(v.val) omega[i, j] = np.angle(v.val) else: a[i, j] = np.absolute(v) omega[i, j] = np.angle(v) # This code is directly applying the inverse of Carsten Blank's # code to avoid inverting at the end # Apply y rotations for k in range(n_wires, 0, -1): alpha_y_k = _get_alpha_y(a, n_wires, k) # type: sparse.dok_matrix control = wires[k:] target = wires[k - 1] _uniform_rotation_y_dagger(alpha_y_k, control, target) # Apply z rotations for k in range(n_wires, 0, -1): alpha_z_k = _get_alpha_z(omega, n_wires, k) control = wires[k:] target = wires[k - 1] if len(alpha_z_k) > 0: _uniform_rotation_z_dagger(alpha_z_k, control, target)
def QAOAEmbedding(features, weights, wires, local_field='Y'): r""" Encodes :math:`N` features into :math:`n>N` qubits, using a layered, trainable quantum circuit that is inspired by the QAOA ansatz. A single layer applies two circuits or "Hamiltonians": The first encodes the features, and the second is a variational ansatz inspired by a 1-dimensional Ising model. The feature-encoding circuit associates features with the angles of :class:`RX` rotations. The Ising ansatz consists of trainable two-qubit ZZ interactions :math:`e^{-i \alpha \sigma_z \otimes \sigma_z}`, and trainable local fields :math:`e^{-i \frac{\beta}{2} \sigma_{\mu}}`, where :math:`\sigma_{\mu}` can be chosen to be :math:`\sigma_{x}`, :math:`\sigma_{y}` or :math:`\sigma_{z}` (default choice is :math:`\sigma_{y}` or the ``RY`` gate), and :math:`\alpha, \beta` are adjustable gate parameters. The number of features has to be smaller or equal to the number of qubits. If there are fewer features than qubits, the feature-encoding rotation is replaced by a Hadamard gate. The argument ``weights`` contains an array of the :math:`\alpha, \beta` parameters for each layer. The number of layers :math:`L` is derived from the first dimension of ``weights``, which has the following shape: * :math:`(L, )`, if the embedding acts on a single wire, * :math:`(L, 3)`, if the embedding acts on two wires, * :math:`(L, 2n)` else. After the :math:`L` th layer, another set of feature-encoding :class:`RX` gates is applied. This is an example for the full embedding circuit using 2 layers, 3 features, 4 wires, and ``RY`` local fields: | .. figure:: ../../_static/qaoa_layers.png :align: center :width: 60% :target: javascript:void(0); | .. note:: ``QAOAEmbedding`` supports gradient computations with respect to both the ``features`` and the ``weights`` arguments. Note that trainable parameters need to be passed to the quantum node as positional arguments. Args: features (array): array of features to encode weights (array): array of weights wires (Sequence[int] or int): `n` qubit indices that the template acts on local_field (str): type of local field used, one of ``'X'``, ``'Y'``, or ``'Z'`` Raises: ValueError: if inputs do not have the correct format .. UsageDetails:: The QAOA embedding encodes an :math:`n`-dimensional feature vector into at most :math:`n` qubits. The embedding applies layers of a circuit, and each layer is defined by a set of weight parameters. .. code-block:: python import pennylane as qml from pennylane.templates import QAOAEmbedding dev = qml.device('default.qubit', wires=2) @qml.qnode(dev) def circuit(weights, f=None): QAOAEmbedding(features=f, weights=weights, wires=range(2)) return qml.expval(qml.PauliZ(0)) features = [1., 2.] layer1 = [0.1, -0.3, 1.5] layer2 = [3.1, 0.2, -2.8] weights = [layer1, layer2] print(circuit(weights, f=features)) **Using parameter initialization functions** The initial weight parameters can alternatively be generated by utility functions from the ``pennylane.init`` module, for example using the function :func:`~.qaoa_embedding_normal`: .. code-block:: python from pennylane.init import qaoa_embedding_normal weights = qaoa_embedding_normal(n_layers=2, n_wires=2, mean=0, std=0.2) **Training the embedding** The embedding is typically trained with respect to a given cost. For example, one can train it to minimize the PauliZ expectation of the first qubit: .. code-block:: python o = GradientDescentOptimizer() for i in range(10): weights = o.step(lambda w : circuit(w, f=features), weights) print("Step ", i, " weights = ", weights) **Training the features** In principle, also the features are trainable, which means that gradients with respect to feature values can be computed. To train both weights and features, they need to be passed to the qnode as positional arguments. If the built-in optimizer is used, they have to be merged to one input: .. code-block:: python @qml.qnode(dev) def circuit2(pars): weights = pars[0] features = pars[1] QAOAEmbedding(features=features, weights=weights, wires=range(2)) return qml.expval(qml.PauliZ(0)) features = [1., 2.] weights = [[0.1, -0.3, 1.5], [3.1, 0.2, -2.8]] pars = [weights, features] o = GradientDescentOptimizer() for i in range(10): pars = o.step(circuit2, pars) print("Step ", i, " weights = ", pars[0], " features = ", pars[1]) **Local Fields** While by default, ``RY`` gates are used as local fields, one may also choose ``local_field='Z'`` or ``local_field='X'`` as hyperparameters of the embedding. .. code-block:: python @qml.qnode(dev) def circuit(weights, f=None): QAOAEmbedding(features=f, weights=weights, wires=range(2), local_field='Z') return qml.expval(qml.PauliZ(0)) Choosing ``'Z'`` fields implements a QAOAEmbedding where the second Hamiltonian is a 1-dimensional Ising model. """ ############# # Input checks 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((len(wires), ), _get_shape(features))) _check_is_in_options(local_field, ['X', 'Y', 'Z'], msg="did not recognize option {} for 'local_field'" "".format(local_field)) repeat = _check_number_of_layers([weights]) if len(wires) == 1: expected_shape = (repeat, 1) _check_shape(weights, expected_shape, msg="'weights' must be of shape {}; got {}" "".format(expected_shape, _get_shape(features))) elif len(wires) == 2: expected_shape = (repeat, 3) _check_shape(weights, expected_shape, msg="'weights' must be of shape {}; got {}" "".format(expected_shape, _get_shape(features))) else: expected_shape = (repeat, 2 * len(wires)) _check_shape(weights, expected_shape, msg="'weights' must be of shape {}; got {}" "".format(expected_shape, _get_shape(features))) ##################### n_features = _get_shape(features)[0] if local_field == 'Z': local_fields = RZ elif local_field == 'X': local_fields = RX else: local_fields = RY for l in range(repeat): # apply alternating Hamiltonians qaoa_feature_encoding_hamiltonian(features, n_features, wires) qaoa_ising_hamiltonian(weights, wires, local_fields, l) # repeat the feature encoding once more at the end qaoa_feature_encoding_hamiltonian(features, n_features, wires)
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_wires_exception(self, wires): """Tests that wires check fails if ``wires`` is not an integer or iterable.""" with pytest.raises(ValueError, match="wires must be a positive integer"): _check_wires(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 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 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 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 test_check_wires(self, wires, target): """Tests that wires check returns correct wires list and its length.""" res = _check_wires(wires=wires) assert res == target