-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
[WIP] interface and implementation for low-level expectation value #8532
Conversation
Use plum-dispatch library.
This comment was marked as duplicate.
This comment was marked as duplicate.
Pull Request Test Coverage Report for Build 2880017667
💛 - Coveralls |
Why doesn't |
That makes sense. I just added it, but the interface needs to be adjusted a bit before I update the PR. |
If the operator is a string, then pass it first, considering it semantically an operator.
I pushed some changes to the code. I added a notebook , although I don't think it is allowed to live permanently where I put it. The notebook shows examples of the latest version of the API. @nonhermitian I'd be interested to know if you think the implementation for |
This comment was marked as outdated.
This comment was marked as outdated.
Can't one just check diagonality and raise if not the case? I really think this whole multi-basis stuff is a solution looking for a problem. |
An out-of-band discussion, and more thought, brought some issues to light. (Many points below have come up elsewhere and people have already argued for some ideas below.) Something we already knew is that there are two concerns addressed in this PR, and the hope was that this is a neat way to do both at once:
I'll discuss 1 and 2 separately. My conclusion is that this PR does not adequately answer these questions, at least without significant revision. I discuss 1 here and leave 2 for later. Expectation value of
|
@nonhermitian, I'm inclined to agree. I think We could enforce only
Or allow any operator in a Pauli basis, in which case the doc would have:
A couple of comments: a. The documentation of the second option is more complex than the first. And we don't have any evidence that anyone wants this yet. b. I would bet that fairly frequently users will neglect to read the doc string and/or due to a thinko try to compute the expectation value for non-diagonal operators. Then they'll be frustrated and we'll see bug reports. So maybe one of these options:
EDIT: One more thing. It should be pretty easy to get something like this merged as class methods on |
I would advocate for more than just Pauli's and include the zero and one state projectors. They are diagonal in the computational basis, and are useful to use for projections onto subsets. eg. one could write strings like |
Oh, that makes sense. ... I think that would mean throw out counts if any of the 0s and 1s don't match The more I think about it, I think supporting only what people need now is best. If something else is needed, we probably won't do it correctly now anyway and would have to deprecate/change it for some reason when it is needed. |
Regarding item number 2 at the top if this comment #8532 (comment), that what do we think of multiple dispatch in this PR?
To address the first point, it would be a good idea to review this with an eye toward reducing the complexity. Furthermore it could be organized better, commented better, perhaps explained better in a notebook. Regarding the second point: I wanted to see how much I could get out of multiple dispatch here without regard to efficiency. If this is done well, it's instructive for understanding how it MD is used. Then, start removing some abstractions or clarity in order to improve performance. Still, with some effort, you might be able to reorganize the code such that you don't have to sacrifice too much to get more performant code. @jakelishman and @garrison made a few suggestions. For example suppose you use MD inside a loop. You can instead ask the MD system outside the loop to find the correct method without actually calling it. There is a function, It was also pointed out that allowing "implicit arguments" and so forth reduces the uniformity of the interface. Maybe to the point where you don't want to try to claim it's one function. In any case, I now no longer think omitting the first |
Before we considered that an operator is all Zs if it is omitted. Now we require an explicit operator. We disallow strings like '1100' to represent 'ZZII'. For `Counts` and `QuasiDistribution`, we allow characters 0 and 1 in a string. These represent projectors onto 0 and 1 subspaces.
@nonhermitian , I pushed an implementation of this to this PR. Seems to work ok. Needs a bit of refactoring. Also the overall interface still needs some work. |
Great PoC. Thank you. I left some comments:
When
Where would be the appropriate place to put this? If it depends on |
|
||
|
||
@dispatch(precedence=1) | ||
def expectation_value(oper: Pauli, state: StabilizerState, qargs: QargsT): |
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.
Why isn't these sort of specializations implemented in the StabilizerState/Statevector/DensityMatrix
expectation_value methods? It seems like you should put the dispatching on the state class methods that compute these for various different operator types, and then if you want a separate function interface to them, its dispatch registration just needs to call the states method.
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.
That makes sense. In particular in the case of StabilizerState
(as far as this PR is developed). But Statevector/DensityMatrix
share a single method here. Which class should this go in? Or maybe I'm misunderstanding something.
How to integrate MD with existing language features and styles is not clear to me. There is an argument for doing it the other way around. Have the class methods call the generic functions. Or maybe its better to avoid being able call class methods and generic functions to do the same thing.
@ikkoham thanks for the comments
If I understand correctly, standard Python supports keyword-only, positional-only, and positional-or-keyword arguments. I'm fairly (not quite 100%) certain that
When searching for a matching method, the number of positional arguments must match. The type of each positional argument must match. The order of arguments must match. If no matching method is found, an error is raised. If more than one method matches, then the "most specific" method is chosen. For example calling with an
I don't understand.
I think you are saying that a method could be added for this.
That's a good question. I put as much in one file as possible for convenience. But, it's probably better to put the methods in different places. How fine grained to make that is then a question. It would be nice to keep MD away from the API, so it can be changed easily. I didn't yet try to do that. It may be difficult. |
Apparently the NetKet project uses plum-dispatch mainly for expectation values
|
This is out of date. I think most things here have been or will be moved to Rust. |
This is a WIP, RFC PR.
This PR implements a low-level function
expectation_value
with 17 methods for various combinations of input using a multiple-dispatch library. The function is not a method of any class. It's (more or less) an ordinary function.What's in the code
Counts
,QuasiDistribution
,SamplerResult
. Till now there are none for these classes. The need for these comes from specific use cases reported by @nonhermitian. I did not understand the details of the use cases, so these implementations may not yet fit the bill exactly.expectation_value
inquantum_info
.quantum_info
, but that were not previously covered.Interface
The methods have signatures similar to the following
The last signature is not for states, but rather associated probabilities. If
operator
is omitted it defaults to thePauli
string that is diagonal in the measurement basis and that contains no one-qubit identity operators. Usually, this will be a string ofZ
s.If a state is given by$(c_1, c_2,\ldots)$ , then the probabilities are $(|c_1|^2, |c_2|^2, \ldots)$ . Since the phase information is lost, the only possible operators for expectation are those corresponding to the measurement basis. There has been concern that we don't want to narrow the semantics of
Counts
, etc. by assuming the computational basis. But, I think this is misplaced. Whatever the the Pauli string specifying the measurement basis was, the answer is correct, and there is no room for ambiguity.Why
Class methods are essentially a kind of single dispatch, if different classes have methods of the same name. Here, we dispatch on the number of arguments and the types of each.
For example here is a single method with the signature
with code that leaks no details of the
state
. In contrast, the current implementation is two separate methods that depend on details ofstate
.To handle the case where
qargs
is absent, there is a single, one-line method for twooperator
types and all types ofstate
. This is done via dispatch. In contrast, the current implementation uses a conditional checking forNone
and constructing the missing data. This boilerplate is repeated at the top of each method. Here, here, and here.quantum_info
is not unique in this respect. Rather, this pattern is common in qiskit.There is one very short method to handle
SparsePauliOp
for all subclasses ofQuantumState
. In contrast, currently this is handled with typechecking and conditionals in a method for each subclass. And one of them is not yet implemented. Using MD, we get it for free.How is it organized ?
qiskit/operations.py
. But, it could be organized differently. For example methods for a function can be spread across the code base in order to include them near code involving similar types. There is one example of this in this PR.Python projects using Multiple Dispatch
This is important. How is MD used with Python. Is it successful ? Not much here yet...
Disadvantages
quantum_info
and elsewhere is there precisely to avoid performance hits. In any case, here is an example benchmark of overhead. Overhead is rather high here because: 1) There are multiple levels of MD dispatch. 2) There are only two qubits, so in general function call overhead is more important.qargs
directly.To improve performance one can