-
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] Support const
classical expressions.
#13811
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).
EDIT: In offline conversations, we decided that having a cast node is the most consistent choice for our expression system, so there is always an implicit cast node to take a const-typed expression down to its non-const counterpart. |
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.
837406f
to
db9d9cb
Compare
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.
One or more of the following people are relevant to this code:
|
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.
Minor comments aside, this looks solid to me, thanks. I wouldn't say I feel 100% confident on whether const
is part of the type or part of the expression (I know we talked about it offline), but I think the way you've done it is the better way to go for us - I think it's easier on us later to have it all very explicit in the structure.
By default, lifted scalars are not const. To lift supported scalars to const-typed | ||
expressions, specify `try_const=True`. | ||
|
||
If an explicit ``type`` is given, the typing in the output will reflect that, | ||
including its const-ness. The ``try_const`` parameter is ignored when this is specified. |
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 concern that literals shouldn't always be const
(also, I think we mean "literal", not "scalar")?
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.
There are two things I was thinking about here:
- In OpenQASM, the operands of a binary expression are always first cast to the greater of the two types. Following that pattern, if literal types are always const, then that would mean we'd always need a cast node in a binary operation between a
Var
and a literal, which felt weird. - Lifting literals to non-const by default was the more API compatible choice (e.g. we can still write expressions that don't use const types with pre-14 QPY).
The way we've got it in this PR, expr.lift
will lift literals to non-const by default. But, any of our API surface that we consider to be accepting an expression in a "const context" can lift its argument as const explicitly with try_const=True
. All binary operations where one side is already a const-typed expression do this, and perhaps we'd do this in an eventual QuantumCircuit.add_const_var(...)
as well.
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.
tbh, I don't think OpenQASM's type system (especially around const
) is that well defined. I guess my question is whether we think "literals may be either type const uint[n]
or uint[n]
" is more or less weird than "we have implicit Cast
nodes that only throw away const
-ness".
I'm fine to leave it like this - it's probably the best choice already, and if there's a problem, there's probably a lot bigger demons lurking in our type system elsewhere, since the initial thing was kind of just put together on vibes.
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.
literals may be either type const uint[n] or uint[n]
This might just be an artifact of having the const-ness as part of the type rather than the expression, since it allows a Value
expression to have a non-const type. If const-ness were instead an attribute of expression, then Value
could be made to always report itself as const.
Just a thought. It's probably too late to change this, but I'm happy to try if you think it's the right idea (and it's permitted by the feature freeze 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.
For posterity: having the join of the two types not be equal to one of the two isn't a problem for a type system (for example, C has this property, and anything with ad-hoc sum types like Python's Union
can do in certain contexts too), I was more meaning I worried we had assumptions elsewhere that it held.
But since 8021e00, it's no longer relevant anyway, so no worries.
:class:`~.expr.Var` nodes are l-values (unless their resolution type is `const`!), because | ||
they have some associated memory location:: |
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.
Possibly this is clearer as
non-
const
:class:`~.expr.Var`
nodes are l-values ...
since the const
bit is pretty fundamental 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 believe this comment is actually no longer valid, since I decided not to "decide" const-typed expressions can never be lvalues. Originally, I'd changed the implementation of is_lvalue
to return False
for any const-typed expression.
The reason for my deferral was that it seemed reasonable to me to use a store
instruction to initialize a const variable, even though we block it at the moment. I'm not sure if it's valid in QASM to initialize a constant without an initializer, e.g.:
duration d;
d = 1200dt;
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, I don't know if that's valid or not. But tbh, even if it is valid, I'd just rather not support it in Qiskit - a few languages let you forward-declare storage, and initialise it in some scope, as long as the set of initialising statements dominates the set of statements using the variable. Rust is one of those - it's valid to have let x;
and then assign to x
in some scope, provided the structure of the program guarantees that x
is assigned if it's used.
I'd rather just not support that in Qiskit, just to reduce complexity. If we gain more complex handling for classical control flow analysis later, we can always revisit.
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.
Sounds good to me. I thought of the let x;
pattern in Rust too, but then I realized that Rust doesn't allow const x;
, which is closer to the semantics I'm alluding to here—it requires an inline initializer.
qiskit/qpy/__init__.py
Outdated
EXPR_TYPE | ||
~~~~~~~~~ | ||
|
||
A :class:`~.types.Type` is encoded by a single-byte ASCII ``char`` that encodes the kind of type, | ||
followed by a payload that varies depending on the type. The defined codes are: | ||
|
||
====================== ========= ================================================================= | ||
Qiskit class Type code Payload | ||
====================== ========= ================================================================= | ||
:class:`~.types.Bool` ``b`` One `_Bool const`. | ||
|
||
:class:`~.types.Uint` ``u`` One ``uint32_t width``, followed by one ``_Bool const``. | ||
====================== ========= ================================================================= |
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.
Maybe want to be slightly clearer in this that this modifies the meanings of the type keys compared to version 9. Line 392 needs double backticks around _Bool const
.
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've changed this around a bit in the later PRs. If it's alright, it might be easier for us to keep this as is and then make sure that the final PR reflects everything in a cohesive and complete manner.
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 to defer, but can you open an issue tagged for the milestone about what you mean, just to remind us to do it as part of the documentation in the final PR?
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 issue #13927
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.
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.
Well, if not obsoleted, then at least the changes to QPY should now be localised to a later PR I 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.
Thanks for this - I think it's about ready to land, bar the merge conflict and the couple of minor comments. Two of them are just musings about the effects of the type system and non-actionable, one is super minor documentation, and the other I think is a missing bit of logic in the logical-operation type inference.
I feel like there's an easy chance I could have missed things in the tests, but that's what bugfixes are for haha, and it seems correct.
By making const-ness a property of expressions, we don't need any special serialization in QPY. That's because we assume that all `Var` expressions are non-const, and all `Value` expressions are const. And the const-ness of any expression is defined by the const-ness of its operands, e.g. when QPY reconstructs a binary operand, the constructed expression's `const` attribute gets set to `True` if both of the operands are `const`, which ultimately flows bottom-up from the `Var` and `Value` leaf nodes.
const
types in classical expressions.const
classical expressions.
Thanks for the review, @jakelishman. As we've discussed offline, your comments got me thinking that tracking const-ness as a property of For others, my reasoning was: The main idea is that we move The benefits are:
The branch has now been updated with these changes. This seems like a big change, but really we've just moved an attribute from one class to another, and everything else is largely removals. |
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 you're right, this will end up a lot cleaner, and an expression being const
doesn't oblige us to evaluate it at compile time. I think this somewhat formalises Value
to mean "immediate value", which I don't think I connected the dots on in the first iteration of the expr
system, but I think it makes a lot of sense: Var
is associated with memory locations; Value
is immediate values; and then we've got a nice framework to fit stretches and durations into.
Super minor typographical comments only.
The classical realtime-expressions module :mod:`qiskit.circuit.classical` can now represent | ||
constant expressions. The :class:`~.expr.Expr` class now has a bool | ||
:attr:`~.expr.Expr.const` attribute which indicates the expression's const-ness. This allows | ||
us to enforce that expressions in certain contexts must be possible to evaluate at compile-time. |
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.
us to enforce that expressions in certain contexts must be possible to evaluate at compile-time. | |
us to enforce that expressions in certain contexts must be possible to evaluate at compile time. |
Summary
Adds support for
const
classical expressions. This is necessary to properly representDuration
andStretch
data types, which are always constant expressions.Everything pretty much works the same, except
expr.Expr
has gained a property calledconst
which indicates its const-ness. An expression is constant if all of its operands are constant. TheValue
expression is always constant, whileVar
is (for now) non-const.Details and comments
Originally, this PR had introduced const-ness as a property of the type system, itself. This was done to mirror what we do in the QASM3 importer, and was what we (@jakelishman and I) thought was the best approach at the time. However, after the initial review, it became clear that const-ness as a property of the expression (
Expr
) is a much more natural fit for Qiskit's classical expression system.With const-ness as a property of
Expr
, we can get away with tracking const-ness entirely in the initializers of our expression classes, which simply set their own const-ness based on the const-ness of their operands. TheValue
andVar
nodes act as our base-case since their const-ness is known, statically. This also means that no special handling is needed for QPY.The original design notes of the type-based const-ness approach is below for posterity, but it is no longer true:
To-do