Skip to content

[Stretch] Add +, -, *, / operators for classical expressions. #13850

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

Merged
merged 121 commits into from
Mar 5, 2025

Conversation

kevinhartman
Copy link
Contributor

@kevinhartman kevinhartman commented Feb 15, 2025

Summary

Adds new arithmetic operators for use with numeric and a duration expressions. These are needed to express relationships between stretch variables and durations.

Details and comments

Based on #13844. More readable diff here..

  • All new operations support Uint and Float, but these cannot be intermixed. You must first cast one to the other explicitly, since Float => Uint and Uint => Float are both considered dangerous cast kinds. If two Uints of different width are used, the wider one is chosen for the expression's output type.
  • Addition and subtraction support for Duration.
  • Multiplication is supported Duration and Float operands. Currently, a Uint must first be cast to a Float to be used as a timing multiplier.
    • The type system does not prevent us from representing non-const expressions of type Duration, but the expr constructors will raise a ValueError if a user tries to produce such an expression (this was not possible in the original design of const-types, but what we have now feels more future-proof than baking this as a hard requirement into the type system).
    • A user would also be able to create a Var with a Duration type, which would not be a constant expression. If desired, we can block users from adding such variables to a circuit.
  • Division is supported between Duration and Float operands, but only where the duration is in the left position. It is also supported between two Duration types, in which case the result is a Float. Dividing a Duration by a non-const expression will raise a ValueError.

To-do

  • Release note.

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.
Copy link
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't looked at the test files (need to go to dinner), but here's a super quick scan over the implementation.

@kevinhartman kevinhartman added Changelog: New Feature Include in the "Added" section of the changelog mod: circuit Related to the core of the `QuantumCircuit` class or the circuit library and removed on hold Can not fix yet labels Mar 3, 2025
@kevinhartman kevinhartman requested a review from jakelishman March 3, 2025 21:55
@1ucian0 1ucian0 assigned jakelishman and unassigned jakelishman Mar 4, 2025
Copy link
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes! I think everything here is all right (though I don't trust that I read every test exactly correctly haha).

"""
left, right = _lift_binary_operands(left, right)
type: types.Type
if left.type.kind is right.type.kind is types.Duration:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooh, chained is comparisons. Not a Python feature you see every day haha

@@ -314,6 +314,15 @@ class Op(enum.Enum):
container types (e.g. unsigned integers) as the left operand, and any integer type as the
right-hand operand. In all cases, the output bit width is the same as the input, and zeros
fill in the "exposed" spaces.

The binary arithmetic operators :data:`ADD`, :data:`SUB:, :data:`MUL`, and :data:`DIV`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The binary arithmetic operators :data:`ADD`, :data:`SUB:, :data:`MUL`, and :data:`DIV`
The binary arithmetic operators :data:`ADD`, :data:`SUB`, :data:`MUL`, and :data:`DIV`

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe apply this in the next PR, just so I can get this one in the merge queue already?

opcode, expr.Var(cr, types.Uint(8)), expr.Value(200, types.Uint(8)), types.Uint(8)
),
)
self.assertFalse(function(cr, 200).const)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super minor, but the expr.Binary equality check should already be doing the const checks. Could we have written the tests such that the const is set explicitly in the expr.Binary constructor?

(No need to change all the tests now.)

Comment on lines +811 to +816
# Multiply timing expressions by non-const floats:
non_const_float = expr.Var.new("a", types.Float())
with self.assertRaisesRegex(ValueError, "would result in a non-const"):
expr.mul(Duration.dt(1000), non_const_float)
with self.assertRaisesRegex(ValueError, "would result in a non-const"):
expr.mul(non_const_float, Duration.dt(1000))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a world where we have a use and a way of functioning with runtime-specified delay, I can imagine us relaxing this restriction, but I think it's good to assert right now.

@jakelishman jakelishman added this pull request to the merge queue Mar 5, 2025
Merged via the queue into Qiskit:main with commit 22daa04 Mar 5, 2025
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: New Feature Include in the "Added" section of the changelog mod: circuit Related to the core of the `QuantumCircuit` class or the circuit library priority: high
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants