-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Unify identifer handling of Var
and Stretch
in DAGCircuit
#14000
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
This is only really a user-facing error message when working with DAGCircuit, since QuantumCircuit first checks if the bits being added to it are duplicates. And, in the case of DAGCircuit, the previous error message was already unfriendly: ValueError: Existing bit ShareableQubit(Owned { register: OwningRegisterInfo { name: "q16", size: 2, subclass: QUBIT }, index: 0 }) cannot be re-added in strict mode.
Tracks stretches the same way we track vars. Also happens to fix a bug in DAG equality where order mattered between stretch captures (it should never have). And, fixes a serialization bug with stretches.
One or more of the following people are relevant to this code:
|
Pull Request Test Coverage Report for Build 14337982611Details
💛 - Coveralls |
If we have any we get it indirectly through tests that are using multiprocessing (like transpiling > 1 circuit) because pickle will be used for IPC or deep copy which also internally uses pickle. That being said see #13980 for some explicit tests I had to add for another pickle bug that's been around since 1.3 (when we ported DAGCircuit to rust). |
Great, thanks! I've added a few tests based on those here to validate that stretches are preserved after pickling and deepcopy. I also tested vars locally, but I found that there's a bug there that predates 2.0 so those tests aren't added in this PR (we ought to address that separately): |
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 know you've got a pending rebase coming so let me just leave my comments from part way through the review and I'll look again after the rebase is up. That being said I'm wondering if we should concentrate on a dedicated 2.0 fix in a direct PR on stable/2.0 and leave the larger changes in this PR for just 2.1?
@@ -2206,8 +2269,8 @@ impl DAGCircuit { | |||
/// but was changed by issue #2564 to return number of qubits + clbits | |||
/// with the new function DAGCircuit.num_qubits replacing the former | |||
/// semantic of DAGCircuit.width(). | |||
fn width(&self) -> usize { | |||
self.qubits.len() + self.clbits.len() + self.vars_info.len() | |||
fn width(&self, py: Python) -> usize { |
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 this is something that won't be required after #14068 is done?
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.
Should be possible, though it's not clear to me if it should happen in that PR or a follow-up. That PR (so far) ports the Python Expr
and Type
hierarchies to Rust, but does not yet change over how we track things in the circuit.
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.
Yeah as we discussed offline I think it's fine to do this in a follow up after #14068.
I'm just mildly concerned that this is adding a deeper dependency on Python for something we should be able to figure out structurally from the dag without Python.
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.
Agreed. We do have this information in a way that doesn't need Python as it is, but it'd be inefficient since we'd have to walk over all of the DAGIdentifierInfo
s and count which ones are variables.
I will find some faster solution in a follow-up that ditches use of the Python
token.
crates/circuit/src/dag_circuit.rs
Outdated
/// stretch: the stretch to add. | ||
fn add_captured_stretch(&mut self, py: Python, stretch: &Bound<PyAny>) -> PyResult<()> { |
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 actually a breaking api change right? Python's arguments by default are both positional and by name, and we didn't declare the signature of this pymethod to just deal with this positionally the name can be used to do dag.add_captured_stretch(var=stretch)
which would be broken by this change. So I don't think we can do this change until 3.0 since we released 2.0.0rc1 and rc2 with a fixed API surface we're stuck with this name for now.
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 was originally hoping this PR would land earlier, but I've renamed stretch
to var
in both signatures for now: 7bba590
crates/circuit/src/dag_circuit.rs
Outdated
if let Some(previous) = self.declared_stretches.get(&var_name) { | ||
if var.eq(previous)? { | ||
/// stretch: the stretch to add. | ||
fn add_declared_stretch(&mut self, py: Python, stretch: &Bound<PyAny>) -> PyResult<()> { |
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 the same problem with this being a breaking change
crates/circuit/src/dag_circuit.rs
Outdated
#[getter] | ||
fn num_stretches(&self, py: Python) -> usize { | ||
self.num_captured_stretches(py) + self.num_declared_stretches(py) | ||
} | ||
|
||
/// Number of captured stretches tracked by the circuit. | ||
#[getter] | ||
fn num_captured_stretches(&self, py: Python) -> usize { | ||
self.captured_stretches.bind(py).len() | ||
} | ||
|
||
/// Number of declared local stretches tracked by the circuit. | ||
#[getter] | ||
fn num_declared_stretches(&self, py: Python) -> usize { | ||
self.declared_stretches.bind(py).len() | ||
} |
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.
If this is for 2.0 I don't think we can backport this since these are new attributes for the dag. This would only be for 2.1. They make total sense to add, but we might want to split this PR out into multiple parts.
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.
Makes sense. I've removed the backport label and will add a release note for this PR for 2.1.
That sounds good. I've removed the backport label from this PR and will open a new one against |
DAGCircuit
with stretchesVar
and Stretch
in DAGCircuit
@@ -2206,8 +2269,8 @@ impl DAGCircuit { | |||
/// but was changed by issue #2564 to return number of qubits + clbits | |||
/// with the new function DAGCircuit.num_qubits replacing the former | |||
/// semantic of DAGCircuit.width(). | |||
fn width(&self) -> usize { | |||
self.qubits.len() + self.clbits.len() + self.vars_info.len() | |||
fn width(&self, py: Python) -> usize { |
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.
Yeah as we discussed offline I think it's fine to do this in a follow up after #14068.
I'm just mildly concerned that this is adding a deeper dependency on Python for something we should be able to figure out structurally from the dag without Python.
fn from_pickle(ob: &Bound<PyAny>) -> PyResult<Self> { | ||
let val_tuple = ob.downcast::<PyTuple>()?; |
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.
Is there a reason to not just do?:
fn from_pickle(ob: &Bound<PyAny>) -> PyResult<Self> { | |
let val_tuple = ob.downcast::<PyTuple>()?; | |
fn from_pickle(val_tuple: &Bound<PyTuple>) -> PyResult<Self> { |
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 just to be consistent with the other from_pickle
methods, really. There isn't any auto-extraction possible here anyway, since we store this all in a PyDict
which, when iterated over, yields PyAny
pairs.
pub struct Var(u32); | ||
|
||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] | ||
pub struct Stretch(u32); |
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.
Is there a reason to not make the inner u32 pub 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.
I didn't make it public since it doesn't have real meaning, at least compared to the int of a Qubit
or Clbit
.
@@ -37,53 +37,60 @@ use pyo3::prelude::*; | |||
use pyo3::types::{PySequence, PyTuple}; | |||
use pyo3::PyTypeInfo; | |||
|
|||
pub type BitType = u32; |
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.
Is the reason to remove this because it's used for more than bits now?
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.
Pretty much, yeah.
I'd introduced it originally, but it doesn't feel very Rust-y. I think it wasn't doing much for us.
@@ -10,6 +10,8 @@ | |||
# copyright notice, and modified files need to carry a notice indicating | |||
# that they have been altered from the originals. | |||
|
|||
# pylint: disable=invalid-name |
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's the invalid name that pylint was complaining about?
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, I believe, because of the additional letter-only names I used. Like "c" and "d".
There were already letter-only names, so I figured this should be allowed. Let me know if you have other suggestions :)
Summary
During testing, I've found two bugs with
DAGCircuit
's handling of stretches:DAGCircuit
does not properly pickle stretches #13999They are fixed here.
Details and comments
I've also reworked
DAGCircuit
's tracking of stretches to be the same as what we do for vars. Namely, there's now just anidentifier_info
field (which replacesvar_info
) which makes it easier to detect name collisions between all identifiers in tracking, and also should make it easier to support native handling of stretches in the future.To make things a bit cleaner, I've moved
Var
tolib.rs
and defined a macroimpl_circuit_identifier
to reduce boilerplate between ourQubit
,Clbit
,Var
, and nowStretch
identifier types.I've also moved the logic of pickling identifier info tracking from
__getstate__
and__setstate__
toFromPyObject
andIntoPyObject
implementations.To-do
DAGCircuit
. Can that possibly be true?