Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
    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])
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
    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
Ejemplo n.º 5
0
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)
Ejemplo n.º 6
0
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_)
Ejemplo n.º 7
0
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)
Ejemplo n.º 8
0
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)
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
    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
Ejemplo n.º 11
0
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_)