-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Add BackendEstimatorV2 #11931
Add BackendEstimatorV2 #11931
Conversation
One or more of the the following people are requested to review this:
|
Pull Request Test Coverage Report for Build 8293552364Details
💛 - Coveralls |
# calculate [ <psi2(theta2)|H1|psi2(theta2)> ] | ||
result2 = estimator.run([psi2], [hamiltonian1], [theta2]).result() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
H2
should be H1
because the test uses hamiltonian1
. Same for other estimator tests.
basis = PassManagerConfig.from_backend(backend).basis_gates | ||
self._passmanager = PassManager( | ||
[Optimize1qGatesDecomposition(basis=basis, target=backend.target)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a workaround to support both BasicSimulator
and AerSimulator
when translating measurement circuits to their basis gates.
If I don't set basis
, some tests for BasicSimulator
fail as follows.
ERROR: test_run_numpy_params_1 (test.python.primitives.test_backend_estimator_v2.TestBackendEstimatorV2)
test.python.primitives.test_backend_estimator_v2.TestBackendEstimatorV2.test_run_numpy_params_1
...
qiskit.providers.basic_provider.exceptions.BasicProviderError: 'basic_simulator encountered unrecognized operation "sdg"'
On the other hand, if I use generate_preset_passmanager
instead of Optimize1qGatesDecomposition
, it causes an error with AerSimulator
as follows.
ERROR: test_aer_1 (test.python.primitives.test_backend_estimator_v2.TestBackendEstimatorV2)
test.python.primitives.test_backend_estimator_v2.TestBackendEstimatorV2.test_aer_1
...
qiskit.transpiler.exceptions.TranspilerError: "Unable to translate the operations in the circuit: ['sdg', 'h', 'measure'] to the backend's (or manually specified) target basis: ['barrier', 'measure', 'h', 'snapshot']. This likely means the target basis is not universal or there are additional equivalence rules needed in the EquivalenceLibrary being used. For more details on this error see: https://docs.quantum.ibm.com/api/qiskit/transpiler_passes.BasisTranslator#translation-errors"
return PrimitiveResult([self._run_pub(pub) for pub in pubs]) | ||
|
||
def _run_pub(self, pub: EstimatorPub) -> PubResult: | ||
shots = int(np.ceil(self._VARIANCE_UPPER_BOUND / pub.precision**2)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since I'm not sure the details of precison
, this shots
estimation is quite rough one. Could you tell me how can we determine the number of shots depending on precision
, @ihincks and @chriseclectic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, I find confusing that the default precision
parameter is 0 but precision should be larger than 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will update it with the same default value with qiskit-ibm-runtime.
https://github.com/Qiskit/qiskit-ibm-runtime/blob/50797a909f376a5ab051b4cefd787ced7a2160e0/qiskit_ibm_runtime/options/estimator_options.py#L44
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a different comment about the default value before reading this. Please go ahead and set it to any reasonable >0 value you like.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how can we determine the number of shots
What you've written looks like a good first stab, and essentially the same as the one in the IBM runtime at the moment. For this reason I think it's okay as-is.
If we wanted to make a better guess we could do this: suppose a sparse Pauli sum observable A=\sum_i a_i P_i
. Then Var[A]=\sum_i |a_i|^2 Var[P_i]
. If we take the worst case that each P_i has expectation value 0, then each Var[P_i] will be about 1, so that Var[A]=\sum_i |a_i|^2
. So a conservative guess would be to go through every observable in the array, find the one with the worst such variance, and use that to decide the shots.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the details. I copied the API doc of qiskit-ibm-runtime and simplified the code by removing _VARIANCE_UPPER_BOUND
.
The following example with Aer: import numpy as np
from qiskit.circuit.library import IQP
from qiskit import transpile
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit.primitives import BackendEstimatorV2
from qiskit_aer import Aer
qasm_simulator = Aer.get_backend('qasm_simulator')
qasm_simulator_estimator = BackendEstimatorV2(backend=qasm_simulator)
n_qubits = 12
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
observable = SparsePauliOp("Z" * n_qubits)
isa_circuit = transpile(circuit, backend=qasm_simulator, optimization_level=1)
isa_observable = observable.apply_layout(isa_circuit.layout)
job = qasm_simulator_estimator.run([(isa_circuit, isa_observable)], precision=0.01)
result = job.result()
print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")
|
Here an example with FakeBackendV2: import numpy as np
from qiskit.circuit.library import IQP
from qiskit import transpile
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit.primitives import BackendEstimatorV2
from qiskit_ibm_runtime.fake_provider import FakeAlmadenV2
fake_almaden = FakeAlmadenV2()
fake_almaden_estimator = BackendEstimatorV2(backend=fake_almaden)
n_qubits = 12
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
observable = SparsePauliOp("Z" * n_qubits)
isa_circuit = transpile(circuit, backend=fake_almaden, optimization_level=1)
isa_observable = observable.apply_layout(isa_circuit.layout)
job = fake_almaden_estimator.run([(isa_circuit, isa_observable)], precision=0.01)
result = job.result()
print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank-you @t-imamichi this looks very good! I don't have any major comments.
return PrimitiveResult([self._run_pub(pub) for pub in pubs]) | ||
|
||
def _run_pub(self, pub: EstimatorPub) -> PubResult: | ||
shots = int(np.ceil(self._VARIANCE_UPPER_BOUND / pub.precision**2)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a different comment about the default value before reading this. Please go ahead and set it to any reasonable >0 value you like.
return PrimitiveResult([self._run_pub(pub) for pub in pubs]) | ||
|
||
def _run_pub(self, pub: EstimatorPub) -> PubResult: | ||
shots = int(np.ceil(self._VARIANCE_UPPER_BOUND / pub.precision**2)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how can we determine the number of shots
What you've written looks like a good first stab, and essentially the same as the one in the IBM runtime at the moment. For this reason I think it's okay as-is.
If we wanted to make a better guess we could do this: suppose a sparse Pauli sum observable A=\sum_i a_i P_i
. Then Var[A]=\sum_i |a_i|^2 Var[P_i]
. If we take the worst case that each P_i has expectation value 0, then each Var[P_i] will be about 1, so that Var[A]=\sum_i |a_i|^2
. So a conservative guess would be to go through every observable in the array, find the one with the worst such variance, and use that to decide the shots.
Co-authored-by: Ian Hincks <ian.hincks@gmail.com>
self, | ||
*, | ||
backend: BackendV1 | BackendV2, | ||
default_precision: float = 0.015625, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated it with the same value with qiskit-ibm-runtime
https://github.com/Qiskit/qiskit-ibm-runtime/blob/16e90f475e78a9d2ae77daa139ef750cfa84ca82/qiskit_ibm_runtime/options/estimator_options.py#L44
Args: | ||
backend: The backend to run the primitive on. | ||
default_precision: The default precision to use if none are specified in :meth:`~run`. | ||
Default: 0.015625 (1 / sqrt(4096)). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added Default
copied from qiskit-ibm-runtime
https://github.com/Qiskit/qiskit-ibm-runtime/blob/16e90f475e78a9d2ae77daa139ef750cfa84ca82/qiskit_ibm_runtime/options/estimator_options.py#L44
return PrimitiveResult([self._run_pub(pub) for pub in pubs]) | ||
|
||
def _run_pub(self, pub: EstimatorPub) -> PubResult: | ||
shots = int(np.ceil(1.0 / pub.precision**2)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed _VARIANCE_UPPER_BOUND
and simplified this part since the numerator is 1.
I added BackendV1 support and tests with Fake7QPulseV1. |
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager | ||
from qiskit.utils import optionals | ||
|
||
BACKENDS = [BasicSimulator(), Fake7QPulseV1(), BackendV2Converter(Fake7QPulseV1())] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be great if we could set random seed number via constructor of these simulators. I currently set a conservative target precision value. But it may fail in the worst case.
I'm wondering how to add
|
I made a PoC to add run method constructor |
@t-imamichi We had said in the options RFC that we didn't want to pass options to estimator = BackendEstimatorV2(...)
estimator.options.seed_simulator = 42 This would also be more consistent with what |
Thank you for your suggestion. I made an option based on dataclass. Could you take a look at it? |
7d8d7c2
to
aea3460
Compare
@t-imamichi thanks for the update! I'd prefer having |
# calculate [ <psi2(theta2)|H1|psi2(theta2)> ] | ||
ham1 = hamiltonian1.apply_layout(psi2.layout) | ||
result2 = estimator.run([(psi2, ham1, theta2)]).result() | ||
np.testing.assert_allclose(result2[0].data.evs, [2.97797666], rtol=self._rtol) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
now you can seed the execution, do you still need assert_allclose
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. It is necessary because unit tests use both an exact simulator and noise simulators.
Some non-blocking comments. It they can be addressed in this PR, great. Otherwise, in a follow up. The comment in #11931 (comment) seems to be the only blocking issue on this PR. |
I moved I have a question. I used |
On my side, LGTM and this can be merged.
I don't think it matters. @jyu00 ? |
Dataclass is actually more preferable than |
Summary
This PR adds
BackendEstimatorV2
as aBaseEstimatorV2
implementation using a backend.This is another approach to #11899.
This PR provides an implementation that directly relies on backend.run while #11899 provides a converter from EstimatorV1 to EstimatorV2.
This PR can do qubit-wise grouping taking into account broadcasting, e.g.,
estimator.run([(circuit, ["IX", "YI"])])
-> run 1 circuit.I made tests based on those for
StatevectorEstimator
.Notes (same as #11928)
I intentionally specified onlyBackendV2
asbackend
option though it still supportsBackendV1
because I think we need to support only the latest version of backends. If requested, I can addBackendV1
.Details and comments