-
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
[Stretch] Add stretch
variable support to QuantumCircuit
.
#13852
Conversation
This reverts commit e2b3221.
Types that have some natural order no longer have an ordering when one of them is strictly greater but has an incompatible const-ness (i.e. when the greater type is const but the other type is not).
We need to reject types with const=True in QPY until it supports them. For now, I've also made the Index and shift operator constructors lift their RHS to the same const-ness as the target to make it less likely that existing users of expr run into issues when serializing to older QPY versions.
This is probably a better default in general, since we don't really have much use for const except for timing stuff.
Since we're going for using a Cast node when const-ness differs, this will be fine.
I wasn't going to have this, but since we have DANGEROUS Float => Int, and we have Int => Bool, I think this makes the most sense.
A Stretch can always represent a Duration (it's just an expression without any unresolved stretch variables, in this case), so we allow implicit conversion from Duration => Stretch. The reason for a separate Duration type is to support things like Duration / Duration => Float. This is not valid for stretches in OpenQASM (to my knowledge).
Also adds support to expr.lift to create a value expression of type types.Duration from an instance of qiskit.circuit.Duration.
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.
Thanks for all this work - I know quite a lot of it is tedious duplication of Var
-based stuff.
I'm not convinced I'd have spotted if there's stretch
handling missing from anywhere, but I think that's par for the course for reviews like this.
One more point I didn't spot in the review (but maybe overlooked): could you add a test that QuantumCircuit
equality requires the stretches to be declared in the same order?
crates/circuit/src/dag_circuit.rs
Outdated
if self.vars_info.contains_key(&var_name) { | ||
return Err(DAGCircuitError::new_err( | ||
"cannot add var as its name shadows an existing var", | ||
)); | ||
} | ||
if let Some(previous) = self.declared_stretches.get(&var_name) { | ||
if var.eq(previous)? { | ||
return Err(DAGCircuitError::new_err("already present in the circuit")); | ||
} | ||
return Err(DAGCircuitError::new_err( | ||
"cannot add var as its name shadows an existing var", | ||
)); | ||
} | ||
if let Some(previous) = self.captured_stretches.get(&var_name) { | ||
if var.eq(previous)? { | ||
return Err(DAGCircuitError::new_err("already present in the circuit")); | ||
} | ||
return Err(DAGCircuitError::new_err( | ||
"cannot add var as its name shadows an existing var", | ||
)); | ||
} |
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 "input var" should be referred to as stretch
, and the "existing var" would now be an "existing identifier"? Should add_var
have its terminology in the error messages updated too?
var: uuid.UUID | ||
"""A :class:`~uuid.UUID` to uniquely identify this stretch.""" | ||
name: str | None | ||
"""The name of the stretch variable.""" |
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.
Two minor points:
- would
var
be better named asuid
?Var
originally called its inner fieldvar
because the inner object was aClbit
orClassicalRegister
, then the field got co-opted to mean something more like "l-value ID".Stretch
isn't wrapping anything else, so we might be better jumping straight touid
-based naming? (Strictly, we don't need the uid to be universally unique, just unique among stretches, so we might not want to hard commit to saying it's universally unique.) name
must always be set and be astr
forStretch
, I think?name: None
was for implicitVar
nodes that wrapClbit
, etc.
def __getstate__(self): | ||
return (self.var, self.name) | ||
|
||
def __setstate__(self, state): | ||
var, name = state | ||
super().__setattr__("type", types.Duration()) | ||
super().__setattr__("const", True) | ||
super().__setattr__("var", var) | ||
super().__setattr__("name", 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.
Potentially we should just swap to a __getnewargs__
form, but I'm assuming that's pre-existing based on how Var
was written? Let's do both at once (in a different PR post 2.0), if so.
# Use the stretches as Delay duration. | ||
qc.delay(a, [0, 1]) | ||
qc.delay(b, 2) | ||
qc.delay(c, [3, 4]) | ||
qc.barrier() |
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.
Strictly I think this PR doesn't add this bit, right?
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.
Correct. It'll come in the next PR.
qc.barrier() | ||
|
||
For additional context and examples, refer to the | ||
`QASM language specification. <https://openqasm.com/language/delays.html#duration-and-stretch-types>`__ |
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.
`QASM language specification. <https://openqasm.com/language/delays.html#duration-and-stretch-types>`__ | |
`OpenQASM 3 language specification. <https://openqasm.com/language/delays.html#duration-and-stretch-types>`__ |
use :meth:`.QuantumCircuit.add_stretch`. The resulting expression is a constant | ||
expression of type :class:`~.types.Duration`, which can currently be used as the ``duration`` | ||
argument of a :meth:`~.QuantumCircuit.delay` or :meth:`~.QuantumCircuit.box` instruction. |
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 probably won't be able to be used by box
in time for 2.0, so likely best drop that line.
@@ -411,17 +411,22 @@ def test_copy_empty_like_circuit(self): | |||
copied = qc.copy_empty_like("copy") | |||
self.assertEqual(copied.name, "copy") | |||
|
|||
# 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.
I think this comment was supposed to go inside the function so it scopes to only that, but I also don't care because I don't like this rule haha.
copied.add_var(d, 0xFF) | ||
self.assertEqual({a, b}, set(copied.iter_input_vars())) | ||
self.assertEqual({c, d}, set(copied.iter_declared_vars())) | ||
self.assertEqual({e}, set(copied.iter_declared_stretches())) | ||
self.assertEqual({a}, set(qc.iter_input_vars())) | ||
self.assertEqual({c}, set(qc.iter_declared_vars())) | ||
self.assertEqual({e}, set(qc.iter_declared_stretches())) | ||
|
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.
iirc, the spirit of this test is to copied.add_stretch(f)
and to assert that {e, f}
is in copied
, while qc
is still {e}
(to catch the case of a shared reference to a Python object).
That said, we then seem to repeat functionally the same test another 20ish times, so probably not a big deal lol
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'm fine leaving Stretch.var
as var
and not changing to uid
- if nothing else it helps duck-typing over Stretch
and Var
, and they are used in similar contexts sometimes.
Last comment is about the equality assertions, but it's fine - let's just leave it til a follow up. At the moment we'll false negative some assertions, but we false negative loads of things in QuantumCircuit
comparisons, so it's not the biggest deal ever (might matter for control-flow builders one day, so we ought to fix it, but still).
qc1 = QuantumCircuit(captures=[a, b, c]) | ||
self.assertEqual(qc1, QuantumCircuit(captures=[a, b, c])) | ||
self.assertNotEqual(qc1, QuantumCircuit(captures=[c, b, a])) |
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.
Actually this particular one should compare equal in both cases - captures are orderless, it's just declarations for which the order matters (because captures don't introduce a new degree of freedom).
So in this test they should both be equal, but
qc1 = QuantumCircuit()
qc1.add_stretch(a)
qc1.add_stretch(b)
qc2 = QuantumCircuit()
qc2.add_stretch(b)
qc2.add_stretch(a)
would have qc1 != qc2
.
That said, at this point I'm fine leaving this til a follow-up anyway, though let's note to do it.
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.
Added to my list, I'll sort it out in #13853
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 there's no time, it can wait til post 2.0 - it's a valid bugfix.
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.
Re-approving, assuming I didn't bork the merge conflict. (Just trying to get this in the queue before I got to bed.)
aad2200
to
7634b3e
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.
Thanks for the QPY docs, and for pointing out I'd missed them haha!
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.
LGTM!
Summary
Adds support for adding new
stretch
variables to a circuit viaQuantumCircuit::add_stretch
. The returned variable follows the same scoping rules as other classical circuit variables. For now, we don't support introducing a lower bound when declaring newstretch
variables, but this can be added in the future.Details and comments
Based on #13850. See the readable diff here.Stretch
. It's type is alwaysDuration
, and it is always a constant expression.QuantumCircuit
"input" variables.Delay
andBox
start using them in their duration expressions.To-do