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

Add reference implementation of EstimatorV2 #11227

Merged
merged 69 commits into from Jan 30, 2024

Conversation

ikkoham
Copy link
Contributor

@ikkoham ikkoham commented Nov 10, 2023

Summary

Add EstiamtorV2

This PR includes base classes and StatevectorEstimator (reference implementation).

Details and comments

This PR is based on the following RFCs:
https://github.com/Qiskit/RFCs/blob/master/0015-estimator-interface.md
https://github.com/Qiskit/RFCs/blob/master/0018-primitive-options-type.md
https://github.com/Qiskit/RFCs/blob/master/0017-base-primitive-unification.md

Items not included in this PR:

  • Estimation for non-Pauli observables
  • SamplerV2
  • Deprecation of V1 and primitives.utils
  • BindingsArray with str key

Note: I might do a force push. → done

@ikkoham ikkoham added Changelog: New Feature Include in the "Added" section of the changelog mod: primitives Related to the Primitives module labels Nov 10, 2023
@mtreinish mtreinish added this to the 1.0.0pre1 milestone Nov 10, 2023
qiskit/primitives/base/estimator_task.py Outdated Show resolved Hide resolved

def __init__(
self,
observables: Union[BasisObservableLike, ArrayLike],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was this comment in pec-runtime's ObservablesArray:

    # NOTE: With support for arrays of observables, its ambiguous how
    # to treat a non-sequence input, so we should deprecate passing in
    # a single BasisObservableLike

I'm not sure why single BasisObservableLike is ambiguous, but it'd need to be converted or tolist() won't return a list.

self.parameter_values.validate()
# Cross validate circuits and observables
for i, observable in enumerate(self.observables):
num_qubits = len(next(iter(observable)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This validation doesn't work when passing in a PauliList for a circuit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PauliList will be deprecated #11055

qiskit/primitives/base/estimator_task.py Outdated Show resolved Hide resolved
qiskit/primitives/base/bindings_array.py Outdated Show resolved Hide resolved
@coveralls
Copy link

coveralls commented Nov 13, 2023

Pull Request Test Coverage Report for Build 7707314260

Warning: This coverage report may be inaccurate.

We've detected an issue with your CI configuration that might affect the accuracy of this pull request's coverage report.
To ensure accuracy in future PRs, please see these guidelines.
A quick fix for this PR: rebase it; your next report should be accurate.

  • -2 of 56 (96.43%) changed or added relevant lines in 3 files are covered.
  • 234 unchanged lines in 25 files lost coverage.
  • Overall coverage decreased (-0.04%) to 89.486%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/primitives/statevector_estimator.py 52 54 96.3%
Files with Coverage Reduction New Missed Lines %
crates/qasm2/src/expr.rs 1 93.81%
qiskit/circuit/classical/types/ordering.py 1 94.64%
qiskit/circuit/controlflow/control_flow.py 1 92.86%
qiskit/circuit/tools/pi_check.py 1 91.15%
qiskit/passmanager/passmanager.py 1 93.62%
qiskit/primitives/containers/bindings_array.py 1 92.59%
qiskit/primitives/containers/data_bin.py 1 96.97%
qiskit/primitives/statevector_sampler.py 1 99.09%
qiskit/transpiler/passes/layout/set_layout.py 1 95.65%
crates/qasm2/src/lex.rs 2 92.19%
Totals Coverage Status
Change from base Build 7659269267: -0.04%
Covered Lines: 59631
Relevant Lines: 66637

💛 - Coveralls

@t-imamichi
Copy link
Member

@t-imamichi
Copy link
Member

How about moving bindings_array.py, ovservables_array, and shape.py to a new directory qiskit.primitives.containers?

@ikkoham
Copy link
Contributor Author

ikkoham commented Nov 16, 2023

Will you implement DataBin defined by https://github.com/Qiskit/RFCs/blob/master/0017-base-primitive-unification.md#databin?

Sure. It's in the TODO list in the issue comment. I am waiting for internal discussions for DataBin.

@t-imamichi
Copy link
Member

Do you plan to deprecate public methods in qiskit.primitives.utils? I think they are not used when qiskit.algorithms and PrimtiivesV1 are removed.

@t-imamichi t-imamichi mentioned this pull request Nov 17, 2023
4 tasks
@ikkoham
Copy link
Contributor Author

ikkoham commented Nov 21, 2023

Do you plan to deprecate public methods in qiskit.primitives.utils? I think they are not used when qiskit.algorithms and PrimtiivesV1 are removed.

IMO, at the same time with PrimitiveV1, so 0.46?

@ikkoham ikkoham marked this pull request as ready for review November 21, 2023 15:03
@ikkoham ikkoham requested a review from a team as a code owner November 21, 2023 15:03
@qiskit-bot
Copy link
Collaborator

One or more of the the following people are requested to review this:

  • @Qiskit/terra-core
  • @ajavadia
  • @ikkoham
  • @levbishop
  • @t-imamichi

ihincks
ihincks previously approved these changes Jan 19, 2024
Copy link
Member

@chriseclectic chriseclectic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The raises on precision > 0 needs to be removed. Other than that I would suggest adding more detailed API documentation to this class rather than referring to BaseEstimatorV2's documentation since it is more a developer facing class, not a user facing class (I am fine with the docs being done as follow up to get this into the release candidate though).

qiskit/primitives/statevector_estimator.py Show resolved Hide resolved
Comment on lines 38 to 39
if precision is not None and precision > 0:
raise ValueError("precision must be None or 0 for StatevectorEstimator.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if precision is not None and precision > 0:
raise ValueError("precision must be None or 0 for StatevectorEstimator.")
if precision is None:
precision = 0

We don't want to error if a user puts in precision > 0 value for precision (which is a target precision), since we will always exceed it since precision is guaranteed to be 0 by implementation. This would prevent actually being able to use this in any application / algorithm workflow which uses a sensible initial guess for precision which would always be > 0.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chriseclectic do you propose to:

  1. ignore the precision
  2. artificially add imprecision, as was previously implemented

The argument for (1) is that this is a statevector simulation, so it should always be perfect.
The argument for (2) is that some folks may want to use this implementation to test their ideas, which might include testing that their ideas are robust to finite noise.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1: in the base class we say that it is a target precision, which i implicitly assumed meant "I want you to return me a mean estimate with standard error <= this value". Maybe we can make wording more explicit.

To add to my lack of documentation comments, this classes doc string should explain how the simulation is done and say that it will always return standard error as zero because it returns exact expectation value means computed from the full state vector.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But (1) and (2) both satisfy "return me an estimate with standard error <= this value". Sure, (1) does a better estimation job, but 2 technically satisfies the requirements if the sampling is done right.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but neither raises an error and makes the estimator unusable in portable workflow like this does

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @ikkoham : the main point of the StatevectorEstimator is not really to compute expectation values for users, it is to provide a simple reference implementation, and a tool for testing. This is because there are other implementations that are strictly better at computing expectation values: for non-exponential (albeit nisqy) runtime you need to use a runtime backend, and for more performant classical simulations (including Clifford) you can use aer. I realize this is a reversal of some previous comments I made, but I've had a bit more time to think about it now.

So, I am in favour of (2) artificially add imprecision, as was previously implemented because some folks may want to use this implementation to test their ideas, which might include testing that their ideas are robust to finite noise. If some user is in the unlikely situation that they want to call some_application_funcion(estimator: BaseEstimatorV2) with noiseless estimator, and they know that some_application_function internally hard-codes specific precisions >0, they can subclass StatevectorEstimator:

class NoiselessEstimator(StatevectorEstimator):
    def run(self, pubs, precision = None):
        return super().run(pubs, 0.0)

result = some_application_function(NoiselessEstimator())

I do think that the default precision for StatevectorEstimator should be 0, however.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why a user should expect this estimator to be noisy unless we tell them that it is. I think we should explicitly state this is an ideal estimator and the precision argument is ignored since it always perfectly estimates the observable in the class documentation.

You need to consider how an estimator is used in computational workflows. If someone is writing an algorithm (say VQE for example) that takes an estimator to run on, it will choose some initial guess for a precision, and then perhaps depending on the variance of the result may modify this processing for subsequent evaluations. If you have some special test Estimator that only works with an argument of precision 0 this will error in these sort of applications. You don't loose anything by return a precision less than requested.

If you want to add noise to this estimator so precision effects the results artificially you can do so (or make a second subclass estimator that adds noise), but i will not approve this PR while it raises an error or warning under a canonical workflow.

Copy link
Contributor

@ihincks ihincks Jan 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Also, to be clear, and I think we all three agree on this point: the StatevectorEstimator shouldn't raise errors when given precision>0 in any case. Thanks @chriseclectic for flagging this, I shouldn't have missed that. That would make the thing non-portable, in addition to being annoying. So I'm in particular trying to get to the bottom of choosing between 1 and 2, outlined near the top of this thread)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want the class to add noise for precision > 0 I am fine with that, just dont add a shots option, all you shuold need is a seed field (like sampler) and initialize an rng in run and do something like rng.normal(evs, precision) (im not exactly sure correct syntax for normal distribution sampling in numpy)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, let's go with (2) and implement rng seeding in the same way as the StatevectorSampler. No need to mention shots anywhere.

@t-imamichi
Copy link
Member

t-imamichi commented Jan 23, 2024

As class name becomes StatevectorEstimator, it would be nice to rename the test file name with test_statevector_estimator.py.

ikkoham and others added 2 commits January 24, 2024 23:30
Co-authored-by: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com>
obs = SparsePauliOp(paulis, coeffs) # TODO: support non Pauli operators
expectation_value = final_state.expectation_value(obs)
if precision != 0:
expectation_value = rng.normal(expectation_value, precision)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to make sure expectation_value is float to use normal. Otherwise, it raises an error as follows.

In [4]: rng.normal(1j, 0.1)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[4], line 1
----> 1 rng.normal(1j, 0.1)

File numpy/random/_generator.pyx:1220, in numpy.random._generator.Generator.normal()

File _common.pyx:585, in numpy.random._common.cont()

TypeError: float() argument must be a string or a real number, not 'complex'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. You are right. Actually, I haven't validate this since V1.

t-imamichi
t-imamichi previously approved these changes Jan 25, 2024
Copy link
Member

@t-imamichi t-imamichi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

ihincks
ihincks previously approved these changes Jan 29, 2024
Copy link
Contributor

@ihincks ihincks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR LGTM, but I'm going to wait for @chriseclectic make any final comments about the docstring or other lingering concerns. My preference is to get this merged (overdue) and make final tweaks as needed.

Co-authored-by: Ian Hincks <ian.hincks@gmail.com>
@ikkoham ikkoham dismissed stale reviews from ihincks and t-imamichi via dbf4363 January 30, 2024 04:19
Copy link
Contributor

@ihincks ihincks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Member

@chriseclectic chriseclectic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@chriseclectic chriseclectic added this pull request to the merge queue Jan 30, 2024
Merged via the queue into Qiskit:main with commit 8002a3e Jan 30, 2024
12 checks passed
@ikkoham ikkoham deleted the primitives/estimator-v2 branch January 31, 2024 02:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: New Feature Include in the "Added" section of the changelog mod: primitives Related to the Primitives module priority: high
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants