def test_single_ex_unitary_operations(self, ph, ref_gates):
        """Test the correctness of the SingleExcitationUnitary template including the gate count
        and order, the wires each operation acts on and the correct use of parameters 
        in the circuit."""

        sqg = 10
        cnots = 4*(ph[1]-ph[0])
        weight = np.pi/3
        with qml.utils.OperationRecorder() as rec:
            SingleExcitationUnitary(weight, wires=ph)

        assert len(rec.queue) == sqg + cnots            

        for gate in ref_gates:
            idx = gate[0]

            exp_gate = gate[1]
            res_gate = rec.queue[idx]
            assert isinstance(res_gate, exp_gate)

            exp_wires = gate[2]
            res_wires = rec.queue[idx]._wires
            assert res_wires == exp_wires

            exp_weight = gate[3]
            res_weight = rec.queue[idx].parameters
            assert res_weight == exp_weight
Exemple #2
0
def UCCSD(weights, wires, ph=None, pphh=None, init_state=None):
    r"""Implements the Unitary Coupled-Cluster Singles and Doubles (UCCSD) ansatz.

    The UCCSD ansatz calls the
    :func:`~.SingleExcitationUnitary` and :func:`~.DoubleExcitationUnitary`
    templates to exponentiate the coupled-cluster excitation operator. UCCSD is a VQE ansatz
    commonly used to run quantum chemistry simulations.

    The UCCSD unitary, within the first-order Trotter approximation, is given by:

    .. math::

        \hat{U}(\vec{\theta}) =
        \prod_{p > r} \mathrm{exp} \Big\{\theta_{pr}
        (\hat{c}_p^\dagger \hat{c}_r-\mathrm{H.c.}) \Big\}
        \prod_{p > q > r > s} \mathrm{exp} \Big\{\theta_{pqrs}
        (\hat{c}_p^\dagger \hat{c}_q^\dagger \hat{c}_r \hat{c}_s-\mathrm{H.c.}) \Big\}

    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 UCCSD unitary defined above can be written in terms
    of Pauli matrices as follows (for more details see
    `arXiv:1805.04340 <https://arxiv.org/abs/1805.04340>`_):

    .. math::

        \hat{U}(\vec{\theta}) = && \prod_{p > r} \mathrm{exp} \Big\{ \frac{i\theta_{pr}}{2}
        \bigotimes_{a=r+1}^{p-1} \hat{Z}_a (\hat{Y}_r \hat{X}_p - \mathrm{H.c.}) \Big\} \\
        && \times \prod_{p > q > r > s} \mathrm{exp} \Big\{ \frac{i\theta_{pqrs}}{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\}.

    Args:
        weights (array): length ``len(ph)+len(pphh)`` vector containing the parameters
            :math:`\theta_{pr}` and :math:`\theta_{pqrs}` entering the Z rotation in
            :func:`~.SingleExcitationUnitary`
            and
            :func:`~.DoubleExcitationUnitary`. These parameters are precisely the coupled-cluster
            amplitudes that need to be optimized for each configuration generated by the
            excitation operator.
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or
            a Wires object.
        ph (Sequence[list]): Sequence of two-element lists containing the indices ``r, p`` that
            define the 1particle-1hole (ph) configuration :math:`\vert \mathrm{ph} \rangle =
            \hat{c}_p^\dagger \hat{c}_r \vert \mathrm{HF} \rangle`,
            where :math:`\vert \mathrm{HF} \rangle` denotes the Hartee-Fock (HF) reference state.
        pphh (Sequence[list]): Sequence of four-elements lists containing the indices
            ``s, r, q, p`` that define the 2particle-2hole configuration (pphh)
            :math:`\vert \mathrm{pphh} \rangle = \hat{c}_p^\dagger \hat{c}_q^\dagger \hat{c}_r
            \hat{c}_s \vert \mathrm{HF} \rangle`.
        init_state (array[int]): Length ``len(wires)`` occupation-number vector representing the
            HF state. ``init_state`` is used to initialize the qubit register.

    Raises:
        ValueError: if inputs do not have the correct format

    .. UsageDetails::

        Notice that:

        #. The number of wires has to be equal to the number of spin-orbitals included in
           the active space.

        #. The ``ph`` and ``pphh`` lists can be generated with the function
           :func:`~.sd_excitations`. See example below.

        #. The vector of parameters ``weights`` is a one-dimensional array of size
           ``len(ph)+len(pphh)``


        An example of how to use this template is shown below:

        .. code-block:: python

            import pennylane as qml
            from pennylane.templates import UCCSD

            # Number of active electrons
            n_active_electrons = 2

            # Number of active spin-orbitals
            n_active_orbitals = 4

            # Set the ``ref_state`` array to encode the HF state
            ref_state = qml.qchem.hf_state(n_active_electrons, n_active_orbitals)

            # Spin-projection selection rule to generate the excitations
            DSz = 0

            # Generate the particle-hole configurations
            ph, pphh = qml.qchem.sd_excitations(n_active_electrons, n_active_orbitals, DS=DSz)

            dev = qml.device('default.qubit', wires=n_active_orbitals)

            @qml.qnode(dev)
            def circuit(weights, wires, ph=ph, pphh=pphh, init_state=ref_state):
                UCCSD(weights, wires, ph=ph, pphh=pphh, init_state=ref_state)
                return qml.expval(qml.PauliZ(0))

            params = np.random.normal(0, np.pi, len(ph) + len(pphh))
            print(circuit(params, wires, ph=ph, pphh=pphh, init_state=ref_state))

    """

    ##############
    # Input checks

    wires = Wires(wires)

    if (not ph) and (not pphh):
        raise ValueError(
            "'ph' and 'pphh' lists can not be both empty; got ph={}, pphh={}".format(ph, pphh)
        )

    check_type(ph, [list], msg="'ph' must be a list; got {}".format(ph))
    expected_shape = (2,)
    for i_ph in ph:
        check_type(i_ph, [list], msg="Each element of 'ph' must be a list; got {}".format(i_ph))
        check_shape(
            i_ph,
            expected_shape,
            msg="Elements of 'ph' must be of shape {}; got {}".format(
                expected_shape, get_shape(i_ph)
            ),
        )
        for i in i_ph:
            check_type(
                i, [int], msg="Each element of 'ph' must be a list of integers; got {}".format(i_ph)
            )

    check_type(pphh, [list], msg="'pphh' must be a list; got {}".format(pphh))
    expected_shape = (4,)
    for i_pphh in pphh:
        check_type(
            i_pphh, [list], msg="Each element of 'pphh' must be a list; got {}".format(i_pphh)
        )
        check_shape(
            i_pphh,
            expected_shape,
            msg="Elements of 'pphh' must be of shape {}; got {}".format(
                expected_shape, get_shape(i_pphh)
            ),
        )
        for i in i_pphh:
            check_type(
                i,
                [int],
                msg="Each element of 'pphh' must be a list of integers; got {}".format(i_pphh),
            )

    check_type(
        init_state,
        [np.ndarray],
        msg="'init_state' must be a Numpy array; got {}".format(init_state),
    )
    for i in init_state:
        check_type(
            i,
            [int, np.int64],
            msg="Elements of 'init_state' must be integers; got {}".format(init_state),
        )

    expected_shape = (len(ph) + len(pphh),)
    check_shape(
        weights,
        expected_shape,
        msg="'weights' must be of shape {}; got {}".format(expected_shape, get_shape(weights)),
    )

    expected_shape = (len(wires),)
    check_shape(
        init_state,
        expected_shape,
        msg="'init_state' must be of shape {}; got {}".format(
            expected_shape, get_shape(init_state)
        ),
    )

    ###############

    wires = wires.tolist()  # TODO: Remove when ops accept wires

    qml.BasisState(np.flip(init_state), wires=wires)

    for d, i_pphh in enumerate(pphh):
        DoubleExcitationUnitary(weights[len(ph) + d], wires=i_pphh)

    for s, i_ph in enumerate(ph):
        SingleExcitationUnitary(weights[s], wires=i_ph)
 def circuit(weight):
     init_state = np.flip(np.array([1,1,0,0]))
     qml.BasisState(init_state, wires=wires)
     SingleExcitationUnitary(weight, wires=ph)
 def circuit(weight=weight, wires=ph):
     SingleExcitationUnitary(weight=weight, wires=ph)
     return qml.expval(qml.PauliZ(0))
Exemple #5
0
def UCCSD(weights, wires, s_wires=None, d_wires=None, init_state=None):
    r"""Implements the Unitary Coupled-Cluster Singles and Doubles (UCCSD) ansatz.

    The UCCSD ansatz calls the
    :func:`~.SingleExcitationUnitary` and :func:`~.DoubleExcitationUnitary`
    templates to exponentiate the coupled-cluster excitation operator. UCCSD is a VQE ansatz
    commonly used to run quantum chemistry simulations.

    The UCCSD unitary, within the first-order Trotter approximation, is given by:

    .. math::

        \hat{U}(\vec{\theta}) =
        \prod_{p > r} \mathrm{exp} \Big\{\theta_{pr}
        (\hat{c}_p^\dagger \hat{c}_r-\mathrm{H.c.}) \Big\}
        \prod_{p > q > r > s} \mathrm{exp} \Big\{\theta_{pqrs}
        (\hat{c}_p^\dagger \hat{c}_q^\dagger \hat{c}_r \hat{c}_s-\mathrm{H.c.}) \Big\}

    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 UCCSD unitary defined above can be written in terms
    of Pauli matrices as follows (for more details see
    `arXiv:1805.04340 <https://arxiv.org/abs/1805.04340>`_):

    .. math::

        \hat{U}(\vec{\theta}) = && \prod_{p > r} \mathrm{exp} \Big\{ \frac{i\theta_{pr}}{2}
        \bigotimes_{a=r+1}^{p-1} \hat{Z}_a (\hat{Y}_r \hat{X}_p - \mathrm{H.c.}) \Big\} \\
        && \times \prod_{p > q > r > s} \mathrm{exp} \Big\{ \frac{i\theta_{pqrs}}{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\}.

    Args:
        weights (array): Length ``len(s_wires) + len(d_wires)`` vector containing the parameters
            :math:`\theta_{pr}` and :math:`\theta_{pqrs}` entering the Z rotation in
            :func:`~.SingleExcitationUnitary`
            and
            :func:`~.DoubleExcitationUnitary`. These parameters are the coupled-cluster
            amplitudes that need to be optimized for each single and double excitation generated
            with the :func:`~.excitations` function.
        wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers
            or strings, or a Wires object.
        s_wires (Sequence[Sequence]): Sequence of lists containing the wires ``[r,...,p]``
            resulting from the single excitation
            :math:`\vert r, p \rangle = \hat{c}_p^\dagger \hat{c}_r \vert \mathrm{HF} \rangle`,
            where :math:`\vert \mathrm{HF} \rangle` denotes the Hartee-Fock reference state.
            The first (last) entry ``r`` (``p``) is considered the wire representing the
            occupied (unoccupied) orbital where the electron is annihilated (created).
        d_wires (Sequence[Sequence[Sequence]]): Sequence of lists, each containing two lists that
            specify the indices ``[s, ...,r]`` and ``[q,..., p]`` defining the double excitation
            :math:`\vert s, r, q, p \rangle = \hat{c}_p^\dagger \hat{c}_q^\dagger \hat{c}_r
            \hat{c}_s \vert \mathrm{HF} \rangle`. The entries ``s`` and ``r`` are wires
            representing two occupied orbitals where the two electrons are annihilated
            while the entries ``q`` and ``p`` correspond to the wires representing two unoccupied
            orbitals where the electrons are created. Wires in-between represent the occupied
            and unoccupied orbitals in the intervals ``[s, r]`` and ``[q, p]``, respectively.
        init_state (array[int]): Length ``len(wires)`` occupation-number vector representing the
            HF state. ``init_state`` is used to initialize the wires.

    Raises:
        ValueError: if inputs do not have the correct format

    .. UsageDetails::

        Notice that:

        #. The number of wires has to be equal to the number of spin orbitals included in
           the active space.

        #. The single and double excitations can be generated be generated with the function
           :func:`~.excitations`. See example below.

        #. The vector of parameters ``weights`` is a one-dimensional array of size
           ``len(s_wires)+len(d_wires)``


        An example of how to use this template is shown below:

        .. code-block:: python

            import pennylane as qml
            from pennylane import qchem
            from pennylane.templates import UCCSD

            from functools import partial

            # Build the electronic Hamiltonian
            name = "h2"
            geo_file = "h2.xyz"
            h, qubits = qchem.molecular_hamiltonian(name, geo_file)

            # Number of electrons
            electrons = 2

            # Define the HF state
            ref_state = qchem.hf_state(electrons, qubits)

            # Generate single and double excitations
            singles, doubles = qchem.excitations(electrons, qubits)

            # Map excitations to the wires the UCCSD circuit will act on
            s_wires, d_wires = qchem.excitations_to_wires(singles, doubles)

            # Define the device
            dev = qml.device('default.qubit', wires=qubits)

            # Define the UCCSD ansatz
            ansatz = partial(UCCSD, init_state=ref_state, s_wires=s_wires, d_wires=d_wires)

            # Define the cost function
            cost_fn = qml.ExpvalCost(ansatz, h, dev)

            # Compute the expectation value of 'h' for given set of parameters 'params'
            params = np.random.normal(0, np.pi, len(singles) + len(doubles))
            print(cost_fn(params))

    """

    ##############
    # Input checks

    wires = Wires(wires)

    if (not s_wires) and (not d_wires):
        raise ValueError(
            "'s_wires' and 'd_wires' lists can not be both empty; got ph={}, pphh={}"
            .format(s_wires, d_wires))

    check_type(
        init_state,
        [np.ndarray],
        msg="'init_state' must be a Numpy array; got {}".format(init_state),
    )
    for i in init_state:
        check_type(
            i,
            [int, np.int64, np.ndarray],
            msg="Elements of 'init_state' must be integers; got {}".format(
                init_state),
        )

    expected_shape = (len(s_wires) + len(d_wires), )
    check_shape(
        weights,
        expected_shape,
        msg="'weights' must be of shape {}; got {}".format(
            expected_shape, get_shape(weights)),
    )

    expected_shape = (len(wires), )
    check_shape(
        init_state,
        expected_shape,
        msg="'init_state' must be of shape {}; got {}".format(
            expected_shape, get_shape(init_state)),
    )

    for d_wires_ in d_wires:
        if len(d_wires_) != 2:
            raise ValueError(
                "expected entries of d_wires to be of size 2; got {} of length {}"
                .format(d_wires_, len(d_wires_)))

    ###############

    qml.BasisState(np.flip(init_state), wires=wires)

    # turn wire arguments into Wires objects
    s_wires = [Wires(w) for w in s_wires]
    d_wires = [[Wires(w1), Wires(w2)] for w1, w2 in d_wires]

    for i, (w1, w2) in enumerate(d_wires):
        DoubleExcitationUnitary(weights[len(s_wires) + i],
                                wires1=w1,
                                wires2=w2)

    for j, s_wires_ in enumerate(s_wires):
        SingleExcitationUnitary(weights[j], wires=s_wires_)