def test_check_shape_list_of_inputs_exception(self, inpt, target_shape, bound): """Tests that list version of shape check succeeds for valid arguments.""" with pytest.raises(ValueError, match="XXX"): check_shapes(inpt, target_shape, bounds=[bound] * len(inpt), msg="XXX")
def CVNeuralNetLayersHomeMade( theta_1, phi_1, varphi_1, r, phi_r, theta_2, phi_2, varphi_2, a, phi_a, k, wires): ############# # Input checks wires = check_wires(wires) n_wires = len(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) expected_shapes = [ (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, expected_shapes, msg="wrong shape of weight input(s) detected") ############### Do for all layers 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 _preprocess(theta, phi, varphi, wires): """Validate and pre-process inputs as follows: * Check the shape of the three weight tensors. Args: theta (tensor_like): trainable parameters of the template phi (tensor_like): trainable parameters of the template varphi (tensor_like): trainable parameters of the template wires (Wires): wires that the template acts on Returns: tuple: shape of varphi tensor """ n_wires = len(wires) n_if = n_wires * (n_wires - 1) // 2 if qml.tape_mode_active(): shape = qml.math.shape(theta) if shape != (n_if, ): raise ValueError(f"Theta must be of shape {(n_if,)}; got {shape}.") shape = qml.math.shape(phi) if shape != (n_if, ): raise ValueError(f"Phi must be of shape {(n_if,)}; got {shape}.") shape_varphi = qml.math.shape(varphi) if shape_varphi != (n_wires, ): raise ValueError( f"Varphi must be of shape {(n_wires,)}; got {shape_varphi}.") else: weights_list = [theta, phi, varphi] expected_shapes = [(n_if, ), (n_if, ), (n_wires, )] check_shapes(weights_list, expected_shapes, msg="wrong shape of weight input(s) detected") shape_varphi = get_shape(varphi) return shape_varphi
def _preprocess(theta_1, phi_1, varphi_1, r, phi_r, theta_2, phi_2, varphi_2, a, phi_a, k, wires): """Validate and pre-process inputs as follows: * Check that the first dimensions of all weight tensors match * Check that the other dimensions of all weight tensors are correct for the number of qubits. Args: heta_1 (tensor_like): shape :math:`(L, K)` tensor of transmittivity angles for first interferometer phi_1 (tensor_like): shape :math:`(L, K)` tensor of phase angles for first interferometer varphi_1 (tensor_like): shape :math:`(L, M)` tensor of rotation angles to apply after first interferometer r (tensor_like): shape :math:`(L, M)` tensor of squeezing amounts for :class:`~pennylane.ops.Squeezing` operations phi_r (tensor_like): shape :math:`(L, M)` tensor of squeezing angles for :class:`~pennylane.ops.Squeezing` operations theta_2 (tensor_like): shape :math:`(L, K)` tensor of transmittivity angles for second interferometer phi_2 (tensor_like): shape :math:`(L, K)` tensor of phase angles for second interferometer varphi_2 (tensor_like): shape :math:`(L, M)` tensor of rotation angles to apply after second interferometer a (tensor_like): shape :math:`(L, M)` tensor of displacement magnitudes for :class:`~pennylane.ops.Displacement` operations phi_a (tensor_like): shape :math:`(L, M)` tensor of displacement angles for :class:`~pennylane.ops.Displacement` operations k (tensor_like): shape :math:`(L, M)` tensor of kerr parameters for :class:`~pennylane.ops.Kerr` operations wires (Wires): wires that template acts on Returns: int: number of times that the ansatz is repeated """ n_wires = len(wires) n_if = n_wires * (n_wires - 1) // 2 if qml.tape_mode_active(): # check that first dimension is the same weights_list = [theta_1, phi_1, varphi_1, r, phi_r, theta_2, phi_2, varphi_2, a, phi_a, k] shapes = [qml.math.shape(w) for w in weights_list] first_dims = [s[0] for s in shapes] if len(set(first_dims)) > 1: raise ValueError( f"The first dimension of all parameters needs to be the same, got {first_dims}" ) repeat = shapes[0][0] second_dims = [s[1] for s in shapes] expected = [ n_if, n_if, n_wires, n_wires, n_wires, n_if, n_if, n_wires, n_wires, n_wires, n_wires, ] if not all(e == d for e, d in zip(expected, second_dims)): raise ValueError("Got unexpected shape for one or more parameters.") else: 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) expected_shapes = [ (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, expected_shapes, msg="Got unexpected shape for one or more parameters" ) return repeat
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 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 = check_wires(wires) n_wires = len(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) expected_shapes = [ (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, expected_shapes, msg="wrong shape of weight input(s) detected") ############### 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 test_check_shape_list_of_inputs(self, inpt, target_shape, bound): """Tests that list version of shape check succeeds for valid arguments.""" check_shapes(inpt, target_shape, bounds=[bound] * len(inpt), msg="XXX")