Expectation values of observables in Opflow framework are sometimes incorrect when circuit is deep.
See original GitHub issueEnvironment
- Qiskit Terra version: 0.19.2
- Python version: 3.9.7
- Operating system: CentOS 7
What is happening?
For moderately-sized systems where a deep ansatz circuit is used, the expectation value returned by sampling the circuit using CircuitSampler
will return a verifiably incorrect answer. I first noticed this issue when running VQE simulations for moderately-sized systems such as H20 (14 qubits), NH3 (16 qubits), and N2 (20 qubits). When using a callback function to print out the function evaluations at each iteration, sometimes the objective function would evaluate to exactly 0.0 or -0.0 when it did not make sense to. The larger the system, the higher the probability that this would happen. I have been able to reproduce this phenomenon outside the context of VQE by simply taking the expectation value of the Hamiltonian with respect to some circuits. This happens, for instance, when the UCCSD
ansatz is used, but not when a very shallow ansatz is used.
How can we reproduce the issue?
The following piece of code results in the bug:
from qiskit_nature.drivers import PySCFDriver
from qiskit.algorithms import VQE
from qiskit_nature.problems.second_quantization import ElectronicStructureProblem
from qiskit_nature.converters.second_quantization import QubitConverter
from qiskit_nature.mappers.second_quantization import JordanWignerMapper
from qiskit_nature.circuit.library import UCCSD, HartreeFock
from qiskit.utils.algorithm_globals import algorithm_globals
from qiskit.utils import QuantumInstance
from qiskit.opflow import CircuitStateFn, AerPauliExpectation, StateFn, CircuitSampler
from qiskit.providers.aer import AerSimulator
from qiskit.compiler import transpile
from time import perf_counter, process_time
import numpy as np
interatomic_distance = 1.0977
basis = 'sto6g'
ansatz_reps = 1
algorithm_globals.massive=True
algorithm_globals.random_seed = 1
backend = AerSimulator(method='statevector')
quantum_instance = QuantumInstance(backend=backend, seed_transpiler=1)
from qiskit_nature.drivers import PySCFDriver, UnitsType, Molecule
molecule = Molecule(geometry=[['N', [0., 0., 0.]],
['N', [0., 0., interatomic_distance]]],
charge=0, multiplicity=1)
driver = PySCFDriver(molecule = molecule, unit=UnitsType.ANGSTROM, basis=basis)
q_molecule = driver.run()
qubit_converter = QubitConverter(JordanWignerMapper())
es_problem = ElectronicStructureProblem(driver=driver)
second_q_op = es_problem.second_q_ops()
qubit_op = qubit_converter.convert(second_q_op[0])
num_qubits = 2*q_molecule.num_molecular_orbitals
HF_state = HartreeFock(num_spin_orbitals=num_qubits,
num_particles=(q_molecule.num_alpha,q_molecule.num_beta),
qubit_converter=qubit_converter)
ansatz = UCCSD(qubit_converter=qubit_converter, num_particles=(q_molecule.num_alpha,q_molecule.num_beta), num_spin_orbitals=num_qubits, reps=ansatz_reps, initial_state=HF_state)
#ansatz = HF_state
print(f'Num qubits: {num_qubits}')
print(f'num_parameters: {ansatz.num_parameters}')
expectation = AerPauliExpectation()
observable_meas = expectation.convert(StateFn(qubit_op, is_measurement=True))
ansatz_circuit_op = CircuitStateFn(ansatz)
expect_op = observable_meas.compose(ansatz_circuit_op).reduce()
param_bindings = dict(zip(ansatz.parameters, np.zeros(ansatz.num_parameters).transpose().tolist()))
sampled_expect_op = CircuitSampler(backend=backend).convert(expect_op, params=param_bindings)
means = np.real(sampled_expect_op.eval())
print(means)
This returns:
-0.0
If we un-comment the line ansatz = HF_state
, so that the ansatz used is no longer the UCCSD ansatz, but just the HartreeFock
circuit (much shallower), then it returns:
-132.16365914584333
What should happen?
In the first instance, we used the UCCSD
ansatz with all parameters set to zero, but initialized with the HartreeFock
circuit. Thus, the UCCSD
part of the circuit should be equivalent to the identity. In the second instance, we are just using the HartreeFock
circuit. These two circuits should be equivalent, but they return completely different expectation values.
Any suggestions?
I have tried this on my local machine (MacOS arm64) using the same Qiskit-terra version and have so far not been able to reproduce the bug. This leads me to believe that this issue could be specific to the Linux wheels published, although this is difficult to know for sure because the python environments on the two machines I tested this on likely do not have identical dependencies.
The nature of this bug also seems to be probabilistic in some way. It does not happen every time, but it seems that the deeper the circuit, the higher the probability that it occurs. If the above code needs to be adjusted in any way to ensure reproducibility, please let me know. (I think this is what algorithm_globals.random_seed
is supposed to do, but I’m not sure.)
Issue Analytics
- State:
- Created 2 years ago
- Comments:7 (4 by maintainers)
Top GitHub Comments
Just fyi the wheels for python 3.6-3.9 qiskit-aer-gpu are live on pypi now (as of yesterday): https://pypi.org/project/qiskit-aer-gpu/0.10.3/ the 3.10 wheels will take a little more time to get ironed out
Great thanks! I’ll close this issue for now then because it seems to go away when using the latest version of Aer. If it turns out I just haven’t tried hard enough to reproduce it and it shows up again, I’ll re-open it in the repository that seems most likely to be the issue.
Right now it seems that the fix most likely responsible for fixing this was mentioned in the 0.10.3 release notes as :
“Multi-threaded transpilations to generate diagonal gates will now work correctly if the number of gates of a circuit exceeds
fusion_parallelization_threshold
. Previously, different threads would occasionally fuse the same element into multiple blocks, causing incorrect results.”