Qiskit / documentation

The documentation content home for https://docs.quantum.ibm.com.
https://docs.quantum.ibm.com
Apache License 2.0
31 stars 49 forks source link

VQE tutorial has unnecessary recalculation of the cost in callback #1331

Closed HuangJunye closed 2 months ago

HuangJunye commented 2 months ago

URL to the relevant documentation

Source: https://github.com/Qiskit/documentation/blob/main/tutorials/variational-quantum-eigensolver/vqe.ipynb Webpage: https://learning.quantum.ibm.com/tutorial/variational-quantum-eigensolver#step-3-execute-using-qiskit-primitives

Select all that apply

Describe the fix.

In the VQE tutorial, there is a callback function which has the purpose of storing intermediate results. However, due to scipy's API, only the current vector / parameters can be passed in the callback function, not the cost. Therefore, the tutorial re-run estimator again with the parameters and observables to obtain the cost which was already calculated in the cost function in each iteration. As a result, each iteration the estimator is ran twice: once for the real usage inside the cost function, and then again for the sole purpose of showing the intermediate result

The callback function is in fact redundant. We can achieve storing intermediate results without callback and without running estimator twice in each iteration. We can simply pass the dictionary for storing intermediate results in the cost function cost_func

Fix

def cost_func(params, ansatz, hamiltonian, estimator):
    """Return estimate of energy from estimator

    Parameters:
        params (ndarray): Array of ansatz parameters
        ansatz (QuantumCircuit): Parameterized ansatz circuit
        hamiltonian (SparsePauliOp): Operator representation of Hamiltonian
        estimator (EstimatorV2): Estimator primitive instance
        callback_dict: dictionary for storing intermediate results

    Returns:
        float: Energy estimate
    """
    pub = (ansatz, [hamiltonian], [params])
    result = estimator.run(pubs=[pub]).result()
    energy = result[0].data.evs[0]

    callback_dict["iters"] += 1
    callback_dict["prev_vector"] = params
    callback_dict["cost_history"].append(energy)
    print(f"Iters. done: {callback_dict["iters"]} [Current cost: {energy}]")

return energy

and in a later cell where the session is created and minimizing loop is run, pass callback_dict to args of the minimize function

with Session(backend=backend) as session:
    estimator = Estimator(session=session)
    estimator.options.default_shots = 10000

    callback = build_callback(ansatz_isa, hamiltonian_isa, estimator, callback_dict)

    res = minimize(
        cost_func,
        x0,
        args=(ansatz_isa, hamiltonian_isa, estimator),
        method="cobyla",
        callback=callback,
    )

By the way there is a typo for the default_shots, it is currently 10_000 (has an unwanted _)

HuangJunye commented 2 months ago

I ran this on a real system and verified that the new code works as intended. No callback function is required to store intermediate results.

Eric-Arellano commented 2 months ago

it is currently 10000 (has an unwanted )

That's not a typo. Python allows underscores in numbers, which makes large numbers much easier to read.

HuangJunye commented 2 months ago

This is new to me. Still looks weird to me though.