-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Add QubitSparsePauli and QubitSparsePauliList classes to quantum_info #14283
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
Conversation
One or more of the following people are relevant to this code:
|
9eb7d07
to
b90b0cf
Compare
Pull Request Test Coverage Report for Build 15333944839Warning: This coverage report may be inaccurate.This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.
Details
💛 - Coveralls |
with self.assertRaisesRegex(ValueError, "explicitly given 'num_qubits'"): | ||
QubitSparsePauli(data, num_qubits=data.num_qubits + 1) | ||
|
||
with_phase = Pauli("-jIYYXY") |
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.
Oh, you went for ignoring the phases then. Let's make sure that everyone sees this before we merge, and speak now or forever hold your peace
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 feels fine to me if explicitly documented, which it is.
) | ||
|
||
def test_from_raw_parts(self): | ||
# Happiest path: exactly typed inputs. |
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.
What do you mean here?
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 was copied over from the SparseObservable
tests. This test case tests various input types that are flagged with comments, and I think this comment is just flagging that the first test block is for when the inputs are supplied in the ideal type.
pub mod qubit_sparse_pauli; | ||
|
||
pub fn pauli_lindblad_map(m: &Bound<PyModule>) -> PyResult<()> { | ||
m.add_class::<PyQubitSparsePauliList>()?; |
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.
Check if qiskit tends to use alphabetical order. If so, switch these two
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.
In general I believe it does, so I'll switch them.
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.
The order you add stuff here doesn't really matter - if we're usually alphabetical, it's probably just individual people all separately choosing that for aesthetic reasons.
/// `TypeError`-related ones are statically forbidden (within Rust) by the language, and conversion | ||
/// failures on entry to Rust from Python space will automatically raise `TypeError`. | ||
#[derive(Error, Debug)] | ||
pub enum CoherenceError { |
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 assume you use them all?
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 think the only one not used in this PR is the first one, but it will be used by PauliLindbladMap
in the subsequent PR.
/// ================== =========== ============================================================= | ||
/// Attribute Length Description | ||
/// ================== =========== ============================================================= | ||
/// :attr:`bit_terms` :math:`s` Each of the non-identity single-qubit Pauli operators. These |
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 would prefer something less computer-sciency and more quantum, like "pauli_terms." And if so, I would rename also the rust class. What do you think?
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 would vote to keep it as is for interface consistency with SparseObservable
.
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.
Update: The C tests are currently failing due to a naming collision between the BitTerm
here and the BitTerm
in sparse_observable/mod.rs
. Even if we can fix this technically and keep the name, this adds a point in favour of changing the name: the naming collision could be confusing.
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.
Just renamed BitTerm
to Pauli
based on suggestion by @jakelishman
e6ec9d2
to
07d896d
Compare
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.
Overall, this looks like it'd be generally fine to me. I don't love the amount of code duplication from SparseObservable
, but I also don't have any reasonable alternative.
I appreciate the effort you went to to strip the concept of "arithmetic" from the object, since it's list-like and not sum-like, both in any remnant method names and the documentation.
One question about the structure: since you're making a "collection" not a "sum": do you think that a set-like interface (with automatic de-duplication) would be most appropriate for you, or should duplicates definitely be allowed? SparseObservable
uses the list-like form because it was optimising to allow direct list-like access into the fields even from Python space, in part to allow fast arithmetic operations. I suspect that that might be less of a concern for you, and you'll care more about union/intersection operations?
As a follow-on from that: do you need to expose the backing data structures (boundaries
and the flat lists indices
and paulis
) to Python space? Just asking because if there's no need and you can provide higher-level interfaces only, I'd keep the internals private - that a) simplifies the code a whole bunch, and b) gives us way more flexibility in the future.
I haven't reviewed #14328 in any real detail, but some considerations from having scanned it:
- I see that you make all the internal fields of
QubitSparsePauliList
pub
. I'd be really cautious about that, because it allows users of the struct to rely on the internals, and to make arbitrary changes to them, which can destroy data coherence really easily. I suspect that many of the uses of that can be wrapped up into safe interfaces (like iterating over terms, etc), and hopefully all the remaining cases can just have extra Rust-space methods as well. If that's not possible, it might indicate an API-boundary problem, so it's worth consideration now. - Given how
PauliLindbladMap
associates a rate with aQubitSparsePauliList
, that does feel like a set-like backing might be appropriate?
The Sphinx docs errors look like they were mostly caused by the naming clash with qiskit.quantum_info.Pauli
. The solution will be to replace anywhere (other than this file) in Qiskit that writes :class:`.Pauli`
with :class:`~.quantum_info.Pauli`
which should do it. It's slightly annoying busy-work, but it's something we have to do with other classes already.
So we need
I think probably yes... otherwise I'm not sure how a user would even know what data the object contains? I guess they could iterate through it and look at the underlying
I can think about this when progressing to that PR - I'll need to remove the
A rate is not associated with a
Okay I'll fix this, thanks. |
In the latest round of commits I've removed the I've tried the following:
but am still locally getting some docs build errors that are unclear, e.g.:
Interestingly this file does no documentation referencing to |
5d1302c
to
1c9585e
Compare
3803ed8
to
3ab1846
Compare
The principle conflict is caused by quantum-info-related modules moving into a separate `qiskit_quantum_info` crate, which this merge resolution does.
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 was a slightly surreal experience, since so much of this (especially the test suite!) was originally stuff I wrote haha.
I had a couple of minor questions, but nothing major about the interfaces from this point. I fixed the merge conflicts (#14342 moved where the file should be - note it's in crates/quantum_info
now), and a minor documentation hold-over from SparseObservable
that I spotted.
Happy to merge based on answers to the last couple of questions.
crates/quantum_info/src/pauli_lindblad_map/qubit_sparse_pauli.rs
Outdated
Show resolved
Hide resolved
crates/quantum_info/src/pauli_lindblad_map/qubit_sparse_pauli.rs
Outdated
Show resolved
Hide resolved
crates/quantum_info/src/pauli_lindblad_map/qubit_sparse_pauli.rs
Outdated
Show resolved
Hide resolved
@ddt.data( | ||
# This is every combination of (0, 1, many) for (terms, qubits, non-identites per term). | ||
QubitSparsePauli.from_label("IIXIZI"), | ||
QubitSparsePauli.from_label("X"), | ||
) | ||
def test_repr(self, data): | ||
# The purpose of this is just to test that the `repr` doesn't crash, rather than asserting | ||
# that it has any particular form. | ||
self.assertIsInstance(repr(data), str) | ||
self.assertIn("QubitSparsePauli", repr(data)) |
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 don't think this comment is accurate (it's missing the empty Pauli), but also both "terms" and "non-identities per term" would only apply to QubitSparsePauliList
, not QubitSparsePauli
.
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.
Nice catch - I dropped the comment, and added an additional test case
QubitSparsePauli.from_label("III")
@ddt.data( | ||
# This is every combination of (0, 1, many) for (terms, qubits, non-identites per term). | ||
QubitSparsePauliList.empty(0), | ||
QubitSparsePauliList.empty(1), | ||
QubitSparsePauliList.empty(10), | ||
QubitSparsePauliList.from_label("IIXIZI"), | ||
QubitSparsePauliList.from_label("X"), | ||
QubitSparsePauliList.from_list(["YIXZII"]), | ||
QubitSparsePauliList.from_list(["YIXZII", "ZZYYXX"]), | ||
) |
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.
Not sure the comment is accurate here either, but then again, I wouldn't 100% swear I was correct in my original either.
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.
Likewise dropped the comment here, and added the test case:
QubitSparsePauliList.from_list(["IIIIII", "ZZYYXX"])
(adding a case with an identity operator).
cd2e806
to
ae371d7
Compare
Thanks for the thorough review Jake! I've dropped the methods you mentioned, removed the comments, and rerun the formatting. |
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.
Ace, thanks Dan - this looks like a solid base to me.
We need to remember to include a release note before the actual release, but we can do that in the last PR of the series. (edit: now I see you've got the base of one in the later PRs - nice.)
…Qiskit#14283) * attempting to add initial structure * adding module creation function * partial progress with copying * partial progress 2 * partial progress 3 * copying over * successful compilation and import * minor changes * renaming SparseTerm to QubitSparsePauli * starting to transfer tests * moving more tests * updating repr of QubitSparsePauliList and adding tests * testing from_pauli * more test progress * moved relevant QubitSparsePauliList tests * adding from_label and from_pauli constructors to QubitSparsePauli * adding tests from from_pauli and from_label * added from_sparse_label method * fixing QubitSparsePauli constructors * working through tests for QubitSparsePuli * saving * done adding tests (I think) * formatting and linting * QubitSparsePauli main doc * done initial pass on QubitSparsePauliList docs * initial pass on docs for QubitSparsePauli * last edits * Update crates/accelerate/src/pauli_lindblad_map/qubit_sparse_pauli.rs Co-authored-by: Samuele Ferracin <sam.ferracin@ibm.com> * Update crates/accelerate/src/pauli_lindblad_map/qubit_sparse_pauli.rs Co-authored-by: Samuele Ferracin <sam.ferracin@ibm.com> * changing the order of the class additions in mod.rs * attempting to fix table * fixing last warning in docs build * renaming BitTerm -> Pauli * fixing formatting * fixing table indentation * fixing error message * making error checking test more specific * cleaning up some docs * fixing test * changing all docs references to standard quantum_info to :class: * updating all pauli references * removing from_raw_parts and raw array data properties from QubitSparsePauliList * deleting documentation for QubitSparsePauliList that details the internal array storage * removing the Pauli attribute from QubitSparsePauliList * fixing tests * re-adding Pauli alphabet to QubitSparsePauli * formatting * minor docs change * removing IterMut * removing unnecessary methods return mutable references * fixing method name * linting * Correct extraneous documentation * removing Pauli.py_name and changing all calls to it to py_label * dropping other unused methods * fixing test comments/cases * formatting --------- Co-authored-by: Samuele Ferracin <sam.ferracin@ibm.com> Co-authored-by: Jake Lishman <jake.lishman@ibm.com>
Summary
Edit: This is the first PR in a series of three introducing
QubitSparsePauli
,QubitSparsePauliList
, andPauliLindbladMap
. This PR is primarily about introducing the data structure forQubitSparsePauli
andQubitSparsePauliList
.This PR adds
QubitSparsePauli
andQubitSparsePauliList
representing, respectively, a Pauli operator and a list of Pauli operators in qubit-sparse format. These classes are essentially copies ofSparseTerm
andSparseObservable
, with the changes:"IXYZ"
The choice to develop these classes was made during the first attempt at implementing
PauliLindbladMap
; see the closed and incomplete PR #14260 .PauliLindbladMap
required storing a qubit-sparse list of paulis, but we realized that we want the user to be able to construct qubit-sparse Paulis and qubit-sparse Pauli lists directly to be used as method arguments (e.g. apauli_fidelity
method) or to be able to return a qubit-sparsely specified list of Paulis (e.g. asample
method which could return a list of qubit-sparse Paulis when drawing multiple samples). As such it made sense to makeQubitSparsePauli
andQubitSparsePauliList
as standalone objects. I've put these in apauli_lindblad_map
folder and rust module in anticipation of also adding aPauliLindbladMap
class there.In a subsequent PR, I will remake
PauliLindbladMap
, and it will make use ofQubitSparsePauliList
to internally store the list of operators.Details and comments
As mentioned these classes are nearly direct copies of
SparseTerm
andSparseObservable
, but with a lot of minor docs changes, changes to names of methods (e.g.SparseObservable.zero
was renamed toQubitSparsePauliList.empty
), and in some cases removal of functionality that doesn't make sense for a "list" (e.g.SparseObservable.identity
was deleted). Some more detailed (but non-exhaustive) notes:At the moment I couldn't get the docs to build locally due to some issues with
doxygen
.