def test_u2_operations(self, layers, qubits, init_state): """Test the correctness of the ParticleConservingU2 template including the gate count and order, the wires each operation acts on and the correct use of parameters in the circuit.""" weights = np.random.normal(0, 2 * np.pi, (layers, 2 * qubits - 1)) n_gates = 1 + (qubits + (qubits - 1) * 3) * layers exp_gates = ([qml.RZ] * qubits + ([qml.CNOT] + [qml.CRX] + [qml.CNOT]) * (qubits - 1)) * layers with pennylane._queuing.OperationRecorder() as rec: ParticleConservingU2(weights, wires=range(qubits), init_state=init_state) # number of gates assert len(rec.queue) == n_gates # initialization assert isinstance(rec.queue[0], qml.BasisState) # order of gates for op1, op2 in zip(rec.queue[1:], exp_gates): assert isinstance(op1, op2) # gate parameter params = np.array([ rec.queue[i].parameters for i in range(1, n_gates) if rec.queue[i].parameters != [] ]) weights[:, qubits:] = weights[:, qubits:] * 2 assert np.allclose(params.flatten(), weights.flatten()) # gate wires wires = Wires(range(qubits)) nm_wires = [ wires.subset([l, l + 1]) for l in range(0, qubits - 1, 2) ] nm_wires += [ wires.subset([l, l + 1]) for l in range(1, qubits - 1, 2) ] exp_wires = [] for _ in range(layers): for i in range(qubits): exp_wires.append(wires.subset([i])) for j in nm_wires: exp_wires.append(j) exp_wires.append(j[::-1]) exp_wires.append(j) res_wires = [rec.queue[i]._wires for i in range(1, n_gates)] assert res_wires == exp_wires
def test_subset_method(self): """Tests the ``subset()`` method.""" wires = Wires([4, 0, 1, 5, 6]) assert wires.subset([2, 3, 0]) == Wires([1, 5, 4]) assert wires.subset(1) == Wires([0]) assert wires.subset([4, 5, 7], periodic_boundary=True) == Wires([6, 4, 1]) # if index does not exist with pytest.raises(WireError, match="Cannot subset wire at index"): wires.subset([10])
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 (tensor_like): size :math:`(M(M-1)/2,)` tensor of transmittivity angles :math:`\theta` phi (tensor_like): size :math:`(M(M-1)/2,)` tensor of phase angles :math:`\phi` varphi (tensor_like): size :math:`(M,)` tensor of rotation angles :math:`\varphi` wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or a Wires object. 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 """ wires = Wires(wires) M = len(wires) shape_varphi = _preprocess(theta, phi, varphi, 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=Wires(w1)) Beamsplitter(theta[n], 0, wires=Wires([w1, w2])) elif beamsplitter == "pennylane": Beamsplitter(theta[n], phi[n], wires=Wires([w1, w2])) else: raise ValueError( f"did not recognize beamsplitter {beamsplitter}") 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.subset([k, k + 1])) elif beamsplitter == "pennylane": Beamsplitter(theta[n], phi[n], wires=wires.subset([k, k + 1])) else: raise ValueError( f"did not recognize beamsplitter {beamsplitter} ") n += 1 else: raise ValueError(f"did not recognize mesh {mesh}") # apply the final local phase shifts to all modes for i in range(shape_varphi[0]): act_on = wires[i] Rotation(varphi[i], wires=act_on)
def test_particle_conserving_u1_operations(self): """Test the correctness of the ParticleConservingU1 template including the gate count and order, the wires each operation acts on and the correct use of parameters in the circuit.""" qubits = 4 layers = 2 weights = particle_conserving_u1_normal(layers, qubits) gates_per_u1 = 16 gates_per_layer = gates_per_u1 * (qubits - 1) gate_count = layers * gates_per_layer + 1 gates_u1 = [ qml.CZ, qml.CRot, qml.PhaseShift, qml.CNOT, qml.PhaseShift, qml.CNOT, qml.PhaseShift, ] gates_ent = gates_u1 + [qml.CZ, qml.CRot] + gates_u1 wires = Wires(range(qubits)) nm_wires = [wires.subset([l, l + 1]) for l in range(0, qubits - 1, 2)] nm_wires += [wires.subset([l, l + 1]) for l in range(1, qubits - 1, 2)] with pennylane._queuing.OperationRecorder() as rec: ParticleConservingU1(weights, wires, init_state=np.array([1, 1, 0, 0])) assert gate_count == len(rec.queue) # check initialization of the qubit register assert isinstance(rec.queue[0], qml.BasisState) # check all quantum operations idx_CRot = 8 for l in range(layers): for i in range(qubits - 1): exp_wires = self._wires_gates_u1(nm_wires[i]) phi = weights[l, i, 0] theta = weights[l, i, 1] for j, exp_gate in enumerate(gates_ent): idx = gates_per_layer * l + gates_per_u1 * i + j + 1 # check that the gates are applied in the right order assert isinstance(rec.queue[idx], exp_gate) # check the wires the gates act on assert rec.queue[idx]._wires == Wires(exp_wires[j]) # check that parametrized gates take the parameters \phi and \theta properly if exp_gate is qml.CRot: if j < idx_CRot: exp_params = [-phi, np.pi, phi] if j > idx_CRot: exp_params = [phi, np.pi, -phi] if j == idx_CRot: exp_params = [0, 2 * theta, 0] assert rec.queue[idx].parameters == exp_params elif exp_gate is qml.PhaseShift: if j < idx_CRot: exp_params = -phi if j == idx_CRot / 2: exp_params = phi if j > idx_CRot: exp_params = phi if j == (3 * idx_CRot + 2) / 2: exp_params = -phi assert rec.queue[idx].parameters == exp_params
def SingleExcitationUnitary(weight, wires=None): r"""Circuit to exponentiate the tensor product of Pauli matrices representing the single-excitation operator entering the Unitary Coupled-Cluster Singles and Doubles (UCCSD) ansatz. UCCSD is a VQE ansatz commonly used to run quantum chemistry simulations. The CC single-excitation operator is given by .. math:: \hat{U}_{pr}(\theta) = \mathrm{exp} \{ \theta_{pr} (\hat{c}_p^\dagger \hat{c}_r -\mathrm{H.c.}) \}, where :math:`\hat{c}` and :math:`\hat{c}^\dagger` are the fermionic annihilation and creation operators and the indices :math:`r` and :math:`p` run over the occupied and unoccupied molecular orbitals, respectively. Using the `Jordan-Wigner transformation <https://arxiv.org/abs/1208.5986>`_ the fermionic operator defined above can be written in terms of Pauli matrices (for more details see `arXiv:1805.04340 <https://arxiv.org/abs/1805.04340>`_). .. math:: \hat{U}_{pr}(\theta) = \mathrm{exp} \Big\{ \frac{i\theta}{2} \bigotimes_{a=r+1}^{p-1}\hat{Z}_a (\hat{Y}_r \hat{X}_p) \Big\} \mathrm{exp} \Big\{ -\frac{i\theta}{2} \bigotimes_{a=r+1}^{p-1} \hat{Z}_a (\hat{X}_r \hat{Y}_p) \Big\}. The quantum circuit to exponentiate the tensor product of Pauli matrices entering the latter equation is shown below (see `arXiv:1805.04340 <https://arxiv.org/abs/1805.04340>`_): | .. figure:: ../../_static/templates/subroutines/single_excitation_unitary.png :align: center :width: 60% :target: javascript:void(0); | As explained in `Seely et al. (2012) <https://arxiv.org/abs/1208.5986>`_, the exponential of a tensor product of Pauli-Z operators can be decomposed in terms of :math:`2(n-1)` CNOT gates and a single-qubit Z-rotation referred to as :math:`U_\theta` in the figure above. If there are :math:`X` or :math:`Y` Pauli matrices in the product, the Hadamard (:math:`H`) or :math:`R_x` gate has to be applied to change to the :math:`X` or :math:`Y` basis, respectively. The latter operations are denoted as :math:`U_1` and :math:`U_2` in the figure above. See the Usage Details section for more information. Args: weight (float): angle :math:`\theta` entering the Z rotation acting on wire ``p`` wires (Iterable or Wires): Wires that the template acts on. The wires represent the subset of orbitals in the interval ``[r, p]``. Must be of minimum length 2. The first wire is interpreted as ``r`` and the last wire as ``p``. Wires in between are acted on with CNOT gates to compute the parity of the set of qubits. Raises: ValueError: if inputs do not have the correct format .. UsageDetails:: Notice that: #. :math:`\hat{U}_{pr}(\theta)` involves two exponentiations where :math:`\hat{U}_1`, :math:`\hat{U}_2`, and :math:`\hat{U}_\theta` are defined as follows, .. math:: [U_1, U_2, U_{\theta}] = \Bigg\{\bigg[R_x(-\pi/2), H, R_z(\theta/2)\bigg], \bigg[H, R_x(-\frac{\pi}{2}), R_z(-\theta/2) \bigg] \Bigg\} #. For a given pair ``[r, p]``, ten single-qubit and ``4*(len(wires)-1)`` CNOT operations are applied. Notice also that CNOT gates act only on qubits ``wires[1]`` to ``wires[-2]``. The operations performed across these qubits are shown in dashed lines in the figure above. An example of how to use this template is shown below: .. code-block:: python import pennylane as qml from pennylane.templates import SingleExcitationUnitary dev = qml.device('default.qubit', wires=3) @qml.qnode(dev) def circuit(weight, wires=None): SingleExcitationUnitary(weight, wires=wires) return qml.expval(qml.PauliZ(0)) weight = 0.56 print(circuit(weight, wires=[0, 1, 2])) """ ############## # Input checks wires = Wires(wires) if len(wires) < 2: raise ValueError("expected at least two wires; got {}".format( len(wires))) expected_shape = () check_shape( weight, expected_shape, msg="'weight' must be of shape {}; got {}".format( expected_shape, get_shape(weight)), ) ############### # Interpret first and last wire as r and p r = wires[0] p = wires[-1] # Sequence of the wires entering the CNOTs between wires 'r' and 'p' set_cnot_wires = [wires.subset([l, l + 1]) for l in range(len(wires) - 1)] # ------------------------------------------------------------------ # Apply the first layer # U_1, U_2 acting on wires 'r' and 'p' RX(-np.pi / 2, wires=r) Hadamard(wires=p) # Applying CNOTs between wires 'r' and 'p' for cnot_wires in set_cnot_wires: CNOT(wires=cnot_wires) # Z rotation acting on wire 'p' RZ(weight / 2, wires=p) # Applying CNOTs in reverse order for cnot_wires in reversed(set_cnot_wires): CNOT(wires=cnot_wires) # U_1^+, U_2^+ acting on wires 'r' and 'p' RX(np.pi / 2, wires=r) Hadamard(wires=p) # ------------------------------------------------------------------ # Apply the second layer # U_1, U_2 acting on wires 'r' and 'p' Hadamard(wires=r) RX(-np.pi / 2, wires=p) # Applying CNOTs between wires 'r' and 'p' for cnot_wires in set_cnot_wires: CNOT(wires=cnot_wires) # Z rotation acting on wire 'p' RZ(-weight / 2, wires=p) # Applying CNOTs in reverse order for cnot_wires in reversed(set_cnot_wires): CNOT(wires=cnot_wires) # U_1^+, U_2^+ acting on wires 'r' and 'p' Hadamard(wires=r) RX(np.pi / 2, wires=p)
def ParticleConservingU1(weights, wires, init_state=None): r"""Implements the heuristic VQE ansatz for quantum chemistry simulations using the particle-conserving gate :math:`U_{1,\mathrm{ex}}` proposed by Barkoutsos *et al.* in `arXiv:1805.04340 <https://arxiv.org/abs/1805.04340>`_. This template prepares :math:`N`-qubit trial states by applying :math:`D` layers of the entangler block :math:`U_\mathrm{ent}(\vec{\phi}, \vec{\theta})` to the Hartree-Fock state .. math:: \vert \Psi(\vec{\phi}, \vec{\theta}) \rangle = \hat{U}^{(D)}_\mathrm{ent}(\vec{\phi}_D, \vec{\theta}_D) \dots \hat{U}^{(2)}_\mathrm{ent}(\vec{\phi}_2, \vec{\theta}_2) \hat{U}^{(1)}_\mathrm{ent}(\vec{\phi}_1, \vec{\theta}_1) \vert \mathrm{HF}\rangle. The circuit implementing the entangler blocks is shown in the figure below: | .. figure:: ../../_static/templates/layers/particle_conserving_u1.png :align: center :width: 50% :target: javascript:void(0); | The repeated units across several qubits are shown in dotted boxes. Each layer contains :math:`N-1` particle-conserving two-parameter exchange gates :math:`U_{1,\mathrm{ex}}(\phi, \theta)` that act on pairs of nearest neighbors qubits. The unitary matrix representing :math:`U_{1,\mathrm{ex}}(\phi, \theta)` is given by (see `arXiv:1805.04340 <https://arxiv.org/abs/1805.04340>`_), .. math:: U_{1, \mathrm{ex}}(\phi, \theta) = \left(\begin{array}{cccc} 1 & 0 & 0 & 0 \\ 0 & \mathrm{cos}(\theta) & e^{i\phi} \mathrm{sin}(\theta) & 0 \\ 0 & e^{-i\phi} \mathrm{sin}(\theta) & -\mathrm{cos}(\theta) & 0 \\ 0 & 0 & 0 & 1 \\ \end{array}\right). The figure below shows the circuit decomposing :math:`U_{1, \mathrm{ex}}` in elementary gates. The Pauli matrix :math:`\sigma_z` and single-qubit rotation :math:`R(0, 2 \theta, 0)` apply the Pauli Z operator and an arbitrary rotation on the qubit ``n`` with qubit ``m`` bein the control qubit, | .. figure:: ../../_static/templates/layers/u1_decomposition.png :align: center :width: 80% :target: javascript:void(0); | :math:`U_A(\phi)` is the unitary matrix .. math:: U_A(\phi) = \left(\begin{array}{cc} 0 & e^{-i\phi} \\ e^{-i\phi} & 0 \\ \end{array}\right), which is applied controlled on the state of qubit ``m`` and can be further decomposed in terms of the `quantum operations <https://pennylane.readthedocs.io/en/stable/introduction/operations.html>`_ supported by Pennylane, | .. figure:: ../../_static/templates/layers/ua_decomposition.png :align: center :width: 70% :target: javascript:void(0); | where, | .. figure:: ../../_static/templates/layers/phaseshift_decomposition.png :align: center :width: 65% :target: javascript:void(0); | The quantum circuits above decomposing the unitaries :math:`U_{1,\mathrm{ex}}(\phi, \theta)` and :math:`U_A(\phi)` are implemented by the ``u1_ex_gate`` and ``decompose_ua`` functions, respectively. :math:`R_\phi` refers to the ``PhaseShift`` gate in the circuit diagram. Args: weights (array[float]): Array of weights of shape ``(D, M, 2)``. ``D`` is the number of entangler block layers and :math:`M=N-1` is the number of exchange gates :math:`U_{1,\mathrm{ex}}` per layer. wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or a Wires object. init_state (array[int]): length ``len(wires)`` vector representing the Hartree-Fock state used to initialize the wires Raises: ValueError: if inputs do not have the correct format .. UsageDetails:: #. The number of wires :math:`N` has to be equal to the number of spin orbitals included in the active space. #. The number of trainable parameters scales linearly with the number of layers as :math:`2D(N-1)`. An example of how to use this template is shown below: .. code-block:: python import pennylane as qml from pennylane.templates import ParticleConservingU1 from functools import partial # Build the electronic Hamiltonian from a local .xyz file h, qubits = qml.qchem.molecular_hamiltonian("h2", "h2.xyz") # Define the Hartree-Fock state electrons = 2 ref_state = qml.qchem.hf_state(electrons, qubits) # Define the device dev = qml.device('default.qubit', wires=qubits) # Define the ansatz ansatz = partial(ParticleConservingU1, init_state=ref_state) # Define the cost function cost_fn = qml.ExpvalCost(ansatz, h, dev) # Compute the expectation value of 'h' layers = 2 params = qml.init.particle_conserving_u1_normal(layers, qubits) print(cost_fn(params)) """ wires = Wires(wires) layers = weights.shape[0] if len(wires) < 2: raise ValueError( "This template requires the number of qubits to be greater than one; a wire sequence with {} elements" .format(len(wires))) expected_shape = (layers, len(wires) - 1, 2) check_shape( weights, expected_shape, msg="'weights' must be of shape {}; got {}".format( expected_shape, get_shape(weights)), ) nm_wires = [wires.subset([l, l + 1]) for l in range(0, len(wires) - 1, 2)] nm_wires += [wires.subset([l, l + 1]) for l in range(1, len(wires) - 1, 2)] qml.BasisState(init_state, wires=wires) for l in range(layers): for i, wires_ in enumerate(nm_wires): u1_ex_gate(weights[l, i, 0], weights[l, i, 1], 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); * A custom pattern can be passed by providing a list of wire lists to ``pattern``. The ``unitary`` is applied to each set of wires specified in the list. .. figure:: ../../_static/templates/broadcast_custom.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 (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or a Wires object. 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 dev = qml.device('default.qubit', wires=4) @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 dev = qml.device('default.qubit', wires=4) @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]) * Custom pattern For a custom pattern, the wire lists for each application of the unitary is passed to ``pattern``: .. code-block:: python dev = qml.device('default.qubit', wires=5) pattern = [[0, 1], [3, 4]] @qml.qnode(dev) def circuit(): broadcast(unitary=qml.CNOT, pattern=pattern, wires=range(5)) return qml.expval(qml.PauliZ(0)) circuit() When using a parametrized unitary, make sure that the number of wire lists in ``pattern`` corresponds to the number of parameters in ``parameters``. .. code-block:: python pattern = [[0, 1], [3, 4]] @qml.qnode(dev) def circuit(pars): broadcast(unitary=qml.CRot, pattern=pattern, wires=range(5), parameters=pars) return qml.expval(qml.PauliZ(0)) pars1 = [1, 2, 3] pars2 = [-1, 3, 1] pars = [pars1, pars2] assert len(pars) == len(pattern) circuit(pars) """ OPTIONS = [ "single", "double", "double_odd", "chain", "ring", "pyramid", "all_to_all", "custom" ] ######### # Input checks wires = Wires(wires) check_type( parameters, [Iterable, type(None)], msg="'parameters' must be either of type None or " "Iterable; got {}".format(type(parameters)), ) if kwargs is None: kwargs = {} check_type( kwargs, [dict], msg="'kwargs' must be a dictionary; got {}".format(type(kwargs)), ) custom_pattern = None if isinstance(pattern, str): check_is_in_options( pattern, OPTIONS, msg="did not recognize option {} for 'pattern'".format(pattern), ) else: # turn custom pattern into list of Wires objects custom_pattern = [Wires(w) for w in pattern] # set "pattern" to "custom", indicating that custom settings have to be used pattern = "custom" 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, "custom": len(custom_pattern) if custom_pattern is not None else None, } # check that there are 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 sequences for patterns wire_sequence = { "single": [wires[i] for i in range(len(wires))], "double": [wires.subset([i, i + 1]) for i in range(0, len(wires) - 1, 2)], "double_odd": [wires.subset([i, i + 1]) for i in range(1, len(wires) - 1, 2)], "chain": [wires.subset([i, 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), "custom": custom_pattern, } # broadcast the unitary for wires, pars in zip(wire_sequence[pattern], parameters): wires = wires.tolist( ) # TODO: Delete once operator takes Wires objects unitary(*pars, wires=wires, **kwargs)
def DoubleExcitationUnitary(weight, wires1=None, wires2=None): r"""Circuit to exponentiate the tensor product of Pauli matrices representing the fermionic double-excitation operator entering the Unitary Coupled-Cluster Singles and Doubles (UCCSD) ansatz. UCCSD is a VQE ansatz commonly used to run quantum chemistry simulations. The CC double-excitation operator is given by .. math:: \hat{U}_{pqrs}(\theta) = \mathrm{exp} \{ \theta (\hat{c}_p^\dagger \hat{c}_q^\dagger \hat{c}_r \hat{c}_s - \mathrm{H.c.}) \}, where :math:`\hat{c}` and :math:`\hat{c}^\dagger` are the fermionic annihilation and creation operators and the indices :math:`r, s` and :math:`p, q` run over the occupied and unoccupied molecular orbitals, respectively. Using the `Jordan-Wigner transformation <https://arxiv.org/abs/1208.5986>`_ the fermionic operator defined above can be written in terms of Pauli matrices (for more details see `arXiv:1805.04340 <https://arxiv.org/abs/1805.04340>`_): .. math:: \hat{U}_{pqrs}(\theta) = \mathrm{exp} \Big\{ \frac{i\theta}{8} \bigotimes_{b=s+1}^{r-1} \hat{Z}_b \bigotimes_{a=q+1}^{p-1} \hat{Z}_a (\hat{X}_s \hat{X}_r \hat{Y}_q \hat{X}_p + \hat{Y}_s \hat{X}_r \hat{Y}_q \hat{Y}_p + \hat{X}_s \hat{Y}_r \hat{Y}_q \hat{Y}_p + \hat{X}_s \hat{X}_r \hat{X}_q \hat{Y}_p - \mathrm{H.c.} ) \Big\} The quantum circuit to exponentiate the tensor product of Pauli matrices entering the latter equation is shown below: | .. figure:: ../../_static/templates/subroutines/double_excitation_unitary.png | :align: center :width: 60% :target: javascript:void(0); As explained in `Seely et al. (2012) <https://arxiv.org/abs/1208.5986>`_, the exponential of a tensor product of Pauli-Z operators can be decomposed in terms of :math:`2(n-1)` CNOT gates and a single-qubit Z-rotation. If there are :math:`X` or :math:`Y` Pauli matrices in the product, the Hadamard (:math:`H`) or :math:`R_x` gate has to be applied to change to the :math:`X` or :math:`Y` basis, respectively. Args: weight (float): angle :math:`\theta` entering the Z rotation acting on wire ``p`` wires1 (Iterable or Wires): Wires of the qubits representing the subset of occupied orbitals in the interval ``[s, r]``. Accepts an iterable of numbers or strings, or a Wires object, with minimum length 2. The first wire is interpreted as ``s`` and the last wire as ``r``. Wires in between are acted on with CNOT gates to compute the parity of the set of qubits. wires2 (Iterable or Wires): Wires of the qubits representing the subset of virtual orbitals in the interval ``[q, p]``. Accepts an iterable of numbers or strings, or a Wires object. Must be of minimum length 2. The first wire is interpreted as ``q`` and the last wire is interpreted as ``p``. Wires in between are acted on with CNOT gates to compute the parity of the set of qubits. Raises: ValueError: if inputs do not have the correct format .. UsageDetails:: Notice that: #. :math:`\hat{U}_{pqrs}(\theta)` involves eight exponentiations where :math:`\hat{U}_1`, :math:`\hat{U}_2`, :math:`\hat{U}_3`, :math:`\hat{U}_4` and :math:`\hat{U}_\theta` are defined as follows, .. math:: [U_1, && U_2, U_3, U_4, U_{\theta}] = \\ && \Bigg\{\bigg[H, H, R_x(-\frac{\pi}{2}), H, R_z(\theta/8)\bigg], \bigg[R_x(-\frac{\pi}{2}), H, R_x(-\frac{\pi}{2}), R_x(-\frac{\pi}{2}), R_z(\frac{\theta}{8}) \bigg], \\ && \bigg[H, R_x(-\frac{\pi}{2}), R_x(-\frac{\pi}{2}), R_x(-\frac{\pi}{2}), R_z(\frac{\theta}{8}) \bigg], \bigg[H, H, H, R_x(-\frac{\pi}{2}), R_z(\frac{\theta}{8}) \bigg], \\ && \bigg[R_x(-\frac{\pi}{2}), H, H, H, R_z(-\frac{\theta}{8}) \bigg], \bigg[H, R_x(-\frac{\pi}{2}), H, H, R_z(-\frac{\theta}{8}) \bigg], \\ && \bigg[R_x(-\frac{\pi}{2}), R_x(-\frac{\pi}{2}), R_x(-\frac{\pi}{2}), H, R_z(-\frac{\theta}{8}) \bigg], \bigg[R_x(-\frac{\pi}{2}), R_x(-\frac{\pi}{2}), H, R_x(-\frac{\pi}{2}), R_z(-\frac{\theta}{8}) \bigg] \Bigg\} #. For a given quadruple ``[s, r, q, p]`` with :math:`p>q>r>s`, seventy-two single-qubit operations are applied. Notice also that consecutive CNOT gates act on qubits with indices between ``s`` and ``r`` and ``q`` and ``p`` while a single CNOT acts on wires ``r`` and ``q``. The operations performed across these qubits are shown in dashed lines in the figure above. An example of how to use this template is shown below: .. code-block:: python import pennylane as qml from pennylane.templates import DoubleExcitationUnitary dev = qml.device('default.qubit', wires=5) @qml.qnode(dev) def circuit(weight, pphh=None): DoubleExcitationUnitary(weight, wires=pphh) return qml.expval(qml.PauliZ(0)) weight = 1.34817 double_excitation = [0, 1, 3, 4] print(circuit(weight, pphh=double_excitation)) """ ############## # Input checks wires1 = Wires(wires1) wires2 = Wires(wires2) if len(wires1) < 2: raise ValueError( "expected at least two wires representing the occupied orbitals; " "got {}".format(len(wires1)) ) if len(wires2) < 2: raise ValueError( "expected at least two wires representing the unoccupied orbitals; " "got {}".format(len(wires2)) ) expected_shape = () check_shape( weight, expected_shape, msg="'weight' must be of shape {}; got {}".format(expected_shape, get_shape(weight)), ) ############### s = wires1[0] r = wires1[-1] q = wires2[0] p = wires2[-1] # Sequence of the wires entering the CNOTs cnots_occ = [wires1.subset([l, l + 1]) for l in range(len(wires1) - 1)] cnots_unocc = [wires2.subset([l, l + 1]) for l in range(len(wires2) - 1)] set_cnot_wires = cnots_occ + [Wires([r, q])] + cnots_unocc # Apply the first layer _layer1(weight, s, r, q, p, set_cnot_wires) # Apply the second layer _layer2(weight, s, r, q, p, set_cnot_wires) # Apply the third layer _layer3(weight, s, r, q, p, set_cnot_wires) # Apply the fourth layer _layer4(weight, s, r, q, p, set_cnot_wires) # Apply the fifth layer _layer5(weight, s, r, q, p, set_cnot_wires) # Apply the sixth layer _layer6(weight, s, r, q, p, set_cnot_wires) # Apply the seventh layer _layer7(weight, s, r, q, p, set_cnot_wires) # Apply the eighth layer _layer8(weight, s, r, q, p, set_cnot_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 (tensor_like): size :math:`(M(M-1)/2,)` tensor of transmittivity angles :math:`\theta` phi (tensor_like): size :math:`(M(M-1)/2,)` tensor of phase angles :math:`\phi` varphi (tensor_like): size :math:`(M,)` tensor of rotation angles :math:`\varphi` wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or a Wires object. 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 Example: The template requires :math:`3` sets of parameters. The ``mesh`` and ``beamsplitter`` keyword arguments are optional and have ``'rectangular'`` and ``'pennylane'`` as default values. .. code-block:: python dev = qml.device('default.gaussian', wires=4) @qml.qnode(dev) def circuit(params): qml.Interferometer(*params, wires=range(4)) return qml.expval(qml.Identity(0)) shapes = [[6, ], [6, ], [4, ]] params = [] for shape in shapes: params.append(np.random.random(shape)) Using these random parameters, the resulting circuit is: >>> print(qml.draw(circuit)(params)) 0: ──╭BS(0.0522, 0.0472)────────────────────╭BS(0.438, 0.222)───R(0.606)────────────────────┤ ⟨I⟩ 1: ──╰BS(0.0522, 0.0472)──╭BS(0.994, 0.59)──╰BS(0.438, 0.222)──╭BS(0.823, 0.623)──R(0.221)──┤ 2: ──╭BS(0.636, 0.298)────╰BS(0.994, 0.59)──╭BS(0.0818, 0.72)──╰BS(0.823, 0.623)──R(0.807)──┤ 3: ──╰BS(0.636, 0.298)──────────────────────╰BS(0.0818, 0.72)───R(0.854)────────────────────┤ Using different values for optional arguments: .. code-block:: python @qml.qnode(dev) def circuit(params): qml.Interferometer(*params, wires=range(4), mesh='triangular', beamsplitter='clements') return qml.expval(qml.Identity(0)) shapes = [[6, ], [6, ], [4, ]] params = [] for shape in shapes: params.append(np.random.random(shape)) The resulting circuit in this case is: >>> print(qml.draw(circuit)(params)) 0: ──R(0.713)──────────────────────────────────╭BS(0.213, 0)───R(0.681)──────────────────────────────────────────────────────────┤ ⟨I⟩ 1: ──R(0.00912)─────────────────╭BS(0.239, 0)──╰BS(0.213, 0)───R(0.388)──────╭BS(0.622, 0)──R(0.567)─────────────────────────────┤ 2: ──R(0.43)─────╭BS(0.534, 0)──╰BS(0.239, 0)───R(0.189)──────╭BS(0.809, 0)──╰BS(0.622, 0)──R(0.309)──╭BS(0.00845, 0)──R(0.757)──┤ 3: ──────────────╰BS(0.534, 0)────────────────────────────────╰BS(0.809, 0)───────────────────────────╰BS(0.00845, 0)──R(0.527)──┤ """ wires = Wires(wires) M = len(wires) shape_varphi = _preprocess(theta, phi, varphi, wires) with qml.tape.OperationRecorder() as rec: if M == 1: # the interferometer is a single rotation Rotation(varphi[0], wires=wires[0]) else: 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=Wires(w1)) Beamsplitter(theta[n], 0, wires=Wires([w1, w2])) elif beamsplitter == "pennylane": Beamsplitter(theta[n], phi[n], wires=Wires([w1, w2])) else: raise ValueError( f"did not recognize beamsplitter {beamsplitter}" ) 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.subset([k, k + 1])) elif beamsplitter == "pennylane": Beamsplitter(theta[n], phi[n], wires=wires.subset([k, k + 1])) else: raise ValueError( f"did not recognize beamsplitter {beamsplitter} " ) n += 1 else: raise ValueError(f"did not recognize mesh {mesh}") # apply the final local phase shifts to all modes for i in range(shape_varphi[0]): act_on = wires[i] Rotation(varphi[i], wires=act_on) return rec.queue
def expand(self): wires = Wires(self.wires) M = len(wires) theta = self.parameters[0] phi = self.parameters[1] varphi = self.parameters[2] mesh = self.parameters[3] beamsplitter = self.parameters[4] with qml.tape.QuantumTape() as tape: if M == 1: # the interferometer is a single rotation Rotation(varphi[0], wires=wires[0]) else: 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=Wires(w1)) Beamsplitter(theta[n], 0, wires=Wires([w1, w2])) elif beamsplitter == "pennylane": Beamsplitter(theta[n], phi[n], wires=Wires([w1, w2])) else: raise ValueError( f"did not recognize beamsplitter {beamsplitter}" ) 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.subset([k, k + 1])) elif beamsplitter == "pennylane": Beamsplitter(theta[n], phi[n], wires=wires.subset([k, k + 1])) else: raise ValueError(f"did not recognize beamsplitter {beamsplitter} ") n += 1 else: raise ValueError(f"did not recognize mesh {mesh}") # apply the final local phase shifts to all modes for i in range(self.shape_varphi[0]): act_on = wires[i] Rotation(varphi[i], wires=act_on) if self.inverse: tape.inv() return tape
def ParticleConservingU2(weights, wires, init_state=None): r"""Implements the heuristic VQE ansatz for Quantum Chemistry simulations using the particle-conserving entangler :math:`U_\mathrm{ent}(\vec{\theta}, \vec{\phi})` proposed in `arXiv:1805.04340 <https://arxiv.org/abs/1805.04340>`_. This template prepares :math:`N`-qubit trial states by applying :math:`D` layers of the entangler block :math:`U_\mathrm{ent}(\vec{\theta}, \vec{\phi})` to the Hartree-Fock state .. math:: \vert \Psi(\vec{\theta}, \vec{\phi}) \rangle = \hat{U}^{(D)}_\mathrm{ent}(\vec{\theta}_D, \vec{\phi}_D) \dots \hat{U}^{(2)}_\mathrm{ent}(\vec{\theta}_2, \vec{\phi}_2) \hat{U}^{(1)}_\mathrm{ent}(\vec{\theta}_1, \vec{\phi}_1) \vert \mathrm{HF}\rangle, where :math:`\hat{U}^{(i)}_\mathrm{ent}(\vec{\theta}_i, \vec{\phi}_i) = \hat{R}_\mathrm{z}(\vec{\theta}_i) \hat{U}_\mathrm{2,\mathrm{ex}}(\vec{\phi}_i)`. The circuit implementing the entangler blocks is shown in the figure below: | .. figure:: ../../_static/templates/layers/particle_conserving_u2.png :align: center :width: 60% :target: javascript:void(0); | Each layer contains :math:`N` rotation gates :math:`R_\mathrm{z}(\vec{\theta})` and :math:`N-1` particle-conserving exchange gates :math:`U_{2,\mathrm{ex}}(\phi)` that act on pairs of nearest-neighbors qubits. The repeated units across several qubits are shown in dotted boxes. The unitary matrix representing :math:`U_{2,\mathrm{ex}}(\phi)` (`arXiv:1805.04340 <https://arxiv.org/abs/1805.04340>`_) is decomposed into its elementary gates and implemented in the :func:`~.u2_ex_gate` function using PennyLane quantum operations. | .. figure:: ../../_static/templates/layers/u2_decomposition.png :align: center :width: 60% :target: javascript:void(0); | Args: weights (array[float]): Array of weights of shape ``(D, M)`` where ``D`` is the number of layers and ``M`` = ``2N-1`` is the total number of rotation ``(N)`` and exchange ``(N-1)`` gates per layer. wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or a Wires object. init_state (array[int]): length ``len(wires)`` vector representing the Hartree-Fock state used to initialize the wires. Raises: ValueError: if inputs do not have the correct format .. UsageDetails:: #. The number of wires has to be equal to the number of spin orbitals included in the active space. #. The number of trainable parameters scales with the number of layers :math:`D` as :math:`D(2N-1)`. An example of how to use this template is shown below: .. code-block:: python import pennylane as qml from pennylane.templates import ParticleConservingU2 from functools import partial # Build the electronic Hamiltonian from a local .xyz file h, qubits = qml.qchem.molecular_hamiltonian("h2", "h2.xyz") # Define the HF state ref_state = qml.qchem.hf_state(2, qubits) # Define the device dev = qml.device('default.qubit', wires=qubits) # Define the ansatz ansatz = partial(ParticleConservingU2, init_state=ref_state) # Define the cost function cost_fn = qml.ExpvalCost(ansatz, h, dev) # Compute the expectation value of 'h' for a given set of parameters layers = 1 params = qml.init.particle_conserving_u2_normal(layers, qubits) print(cost_fn(params)) """ wires = Wires(wires) layers = weights.shape[0] if len(wires) < 2: raise ValueError( "This template requires the number of qubits to be greater than one;" "got a wire sequence with {} elements".format(len(wires))) expected_shape = (layers, 2 * len(wires) - 1) check_shape( weights, expected_shape, msg="'weights' must be of shape {}; got {}".format( expected_shape, get_shape(weights)), ) nm_wires = [wires.subset([l, l + 1]) for l in range(0, len(wires) - 1, 2)] nm_wires += [wires.subset([l, l + 1]) for l in range(1, len(wires) - 1, 2)] qml.BasisState(init_state, wires=wires) for l in range(layers): for j, _ in enumerate(wires): RZ(weights[l, j], wires=wires[j]) for i, wires_ in enumerate(nm_wires): u2_ex_gate(weights[l, len(wires) + i], wires=wires_)