Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support variable number of trotter steps in the UCCSD ansatz #5741

Open
S-Erik opened this issue May 27, 2024 · 12 comments
Open

Support variable number of trotter steps in the UCCSD ansatz #5741

S-Erik opened this issue May 27, 2024 · 12 comments
Labels
enhancement ✨ New feature or request

Comments

@S-Erik
Copy link

S-Erik commented May 27, 2024

Feature details

The UCCSD ansatz, as written in the documentation, implements a first order Trotter-approximation of the UCCSD unitary. I suggest introducing a new parameter to the UCCSD function that defines the Trotter order, e.g. via specifying the number of repetitions of the current UCCSD ansatz similar to the reps parameter in the qiskit UCCSD implementation.

This is relevant to check and reduce the error coming from the first-order Trotter approximation.

Implementation

I am not sure how one would implement higher-order Trotter approximations in a way that is mathematically correct, but judging from the qiskit NLocal implementation on which their UCCSD ansatz is based it seems that simply repeating the first-order UCCSD ansatz reps times does the trick. Further I think that for each repetition one would have to introduce new parameters such that each repetition has its own set of parameters.

I thought of the following code as a starting point:

import numpy as np
import pennylane as qml
from pennylane.operation import Operation, AnyWires
from pennylane.ops import BasisState

class UCCSD_w_reps(Operation):
    num_wires = AnyWires
    grad_method = None

    def __init__(
        self,
        weights,
        wires,
        s_wires=None,
        d_wires=None,
        init_state=None,
        reps=1,
        id=None,
    ):
        if (not s_wires) and (not d_wires):
            raise ValueError(
                f"s_wires and d_wires lists can not be both empty; got ph={s_wires}, pphh={d_wires}"
            )

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

        shape = qml.math.shape(weights)
        if shape != ((len(s_wires) + len(d_wires)) * reps,):
            raise ValueError(
                f"Weights tensor must be of shape {((len(s_wires) + len(d_wires)) * reps,)}; got {shape}."
            )

        init_state = qml.math.toarray(init_state)

        if init_state.dtype != np.dtype("int"):
            raise ValueError(
                f"Elements of 'init_state' must be integers; got {init_state.dtype}"
            )

        super().__init__(weights, wires=wires, id=id)

        self._hyperparameters = {
            "init_state": init_state,
            "s_wires": s_wires,
            "d_wires": d_wires,
            "reps": reps,
        }

    @property
    def num_params(self):
        return 1

    @staticmethod
    def compute_decomposition(
        weights, wires, s_wires, d_wires, init_state, reps
    ):  # pylint: disable=arguments-differ
        op_list = []

        op_list.append(BasisState(init_state, wires=wires))

        for rep in range(reps):
            for i, (w1, w2) in enumerate(d_wires):
                op_list.append(
                    qml.FermionicDoubleExcitation(
                        weights[len(s_wires) + i + (len(s_wires) + len(d_wires)) * rep],
                        wires1=w1,
                        wires2=w2,
                    )
                )

            for j, s_wires_ in enumerate(s_wires):
                op_list.append(
                    qml.FermionicSingleExcitation(
                        weights[j + (len(s_wires) + len(d_wires)) * rep], wires=s_wires_
                    )
                )

        return op_list

How important would you say this feature is?

1: Not important. Would be nice to have.

Additional information

No response

@S-Erik S-Erik added the enhancement ✨ New feature or request label May 27, 2024
@CatalinaAlbornoz
Copy link
Contributor

Hi @S-Erik ,

Thanks for your suggestion! My colleague Soran helped me with some insights here.

It is correct the our implementation of UCCSD, following literature, assumes first-order Trotter expansion.
What Qiskit does is that it repeats the template n times. You can code this as part of any circuit in PennyLane too!
We also have the kUpCCGSD template where k is a user-defined parameter specifying the number of repetitions.

Take a look at this template, it might be exactly what you were looking for! 😃

@S-Erik
Copy link
Author

S-Erik commented May 29, 2024

Thank you for your quick answer @CatalinaAlbornoz. Now, I have a few further questions:

How do you imagine repeating the UCCSD template in PennyLane? Because a simple for loop in a circuit definition does not work. For example

@qml.qnode(dev)
def circuit(params):
    for rep in range(uccsd_reps):
        qml.UCCSD(params[rep], wires, s_wires, d_wires, init_state=hf_state)
    return qml.expval(H)

does not work as intended because the BasisState(init_state) is used every time qml.UCCSD is called, see this code line in pennylane/uccsd.py. Therefore, each application of qml.UCCSD first resets the state of the quantum circuit to the init_state.

A naïve approach trying to work around this reset by setting init_state=None,

@qml.qnode(dev)
def circuit(params):
    for rep in range(uccsd_reps):
        init_state = hf_state if rep == 0 else None # ValueError: Elements of 'init_state' must be integers; got object
        qml.UCCSD(params[rep], wires, s_wires, d_wires, init_state=init_state)
    return qml.expval(H)

, results in a ValueError: Elements of 'init_state' must be integers; got object, since BasisState accepts only a list of integers and always prepares a basis state.

You are correct that the implementation of the kUpCCGSD template is very similar to what I proposed in my initial comment. But, since the kUpCCGSD template uses generalized fermionic excitations it generates a different circuit than the UCCSD template even for $k=1$.

I hope I did not make a mistake implementing your suggestion.

@CatalinaAlbornoz
Copy link
Contributor

Hi @S-Erik,

I'm checking with the team on this. We'll be back in the next few days with more info on what can be done here. Thanks for your questions!

@soranjh
Copy link
Contributor

soranjh commented May 31, 2024

Hi @S-Erik and thanks for opening the issue. Repeating the first-order UCCSD ansatz, as you suggested, is an option for improving accuracy but it will make your circuit deep and expensive to simulate. The better alternative is to build adaptive circuits, where extra gates are added systematically to improve the quality of your ansatz. You can find more details in this demo. Please let me know if the adaptive method is a solution for your problem.

@S-Erik
Copy link
Author

S-Erik commented Jun 3, 2024

Hi @soranjh. Thank you for your concerns and suggestions. I am aware of the ADAPT-VQE approach and its usage in PennyLane. Unfortunately, my aim for this issue is that one can improve the accuracy of the UCCSD ansatz by using higher-orders. The main reason for this is to investigate the limit of the UCCSD ansatz and the error from different Trotter-orders. Since ADAPT-VQE converges to the FCI value for large ADAPT-steps value and not to the UCCSD limit, I cannot assess the UCCSD error with ADAPT-VQE.

I hope you can better understand my aim now and I am open for any further suggestions.

@soranjh
Copy link
Contributor

soranjh commented Jun 3, 2024

Thanks for the clarification @S-Erik. Have you considered using the SingleExcitation and DoubleExcitation gates and building your circuit manually? This will be very easy and you can repeat your gates as many time as you want. Please let me know if this solution is working, otherwise we can add a parameter to the UCCSD ansatz to allow repetition.

def circuit(params):
    qml.BasisState(hf_state, wires=range(qubits))

    for i, excitation in enumerate(excitations):
        if len(excitation) == 4:
            qml.DoubleExcitation(params[i], wires=excitation)
        elif len(excitation) == 2:
            qml.SingleExcitation(params[i], wires=excitation)
            
    # repeat as needed

    return qml.expval(...)

@S-Erik
Copy link
Author

S-Erik commented Jun 4, 2024

Thank you for your suggestion @soranjh. Building the circuit manually would indeed work. Your code suggestion is therefore very close to the already implemented UCCSD template and my UCCSD_w_reps template in my initial comment.

I believe it would benefit PennyLane to incorporate this functionality of allowing repetitions into the existing UCCSD template. Similarly, one could argue that while a UCCSD template isn't strictly necessary, because the circuit can also be build manually, it does streamline the process by automating the creation of the circuit.

@soranjh
Copy link
Contributor

soranjh commented Jun 4, 2024

Thanks @S-Erik, sounds good. Are you interested to open a quick PR and add a repeat parameter? The implementation will be similar to that of kUpCCGSD template.

@S-Erik
Copy link
Author

S-Erik commented Jun 4, 2024

Sounds perfect @soranjh. I will prepare a PR in the coming days. I will orient myself on the kUpCCGSD template as you said.
Thank you @soranjh @CatalinaAlbornoz for your quick responses. Very much appreciated ❤️

@CatalinaAlbornoz
Copy link
Contributor

It's great to hear that you decided to prepare the PR! Let us know if you have any questions along the way and please mention this issue in the PR too!

@S-Erik
Copy link
Author

S-Erik commented Jun 5, 2024

I have just created a PR, see #5801.

@CatalinaAlbornoz
Copy link
Contributor

Thanks @S-Erik !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement ✨ New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants