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_num_layers_exception(self, inpt, repeat): """Tests that layer check throws exception for invalid arguments.""" with pytest.raises(ValueError, match="the first dimension of the weight parameters"): _check_number_of_layers(inpt)
def test_check_num_layers(self, inpt, repeat): """Tests that layer check returns correct number of layers.""" n_layers = _check_number_of_layers(inpt) assert n_layers == repeat
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)