-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Add Expr
support to QPY for conditions and targets
#10392
Conversation
One or more of the the following people are requested to review this:
|
Pull Request Test Coverage Report for Build 5603129027
💛 - Coveralls |
This adds support to QPY for the current `Expr` nodes, for *all* instruction parameters and conditions. The `Expr` tree is written out to the file in a sort of forwards Polish notation; each node has a type code and header, followed by a type-code-specific number of `Expr` children. While only `IfElseOp.condition`, `WhileLoopOp.condition` and `SwitchCaseOp.target` are allowed to have these nodes in Terra's data model right now, the QPY serialisation does not need to have this arbitrary restriction, and it's much easier just to write the general case. The backwards-compatibility guarantees of QPY are now brought to bear on the `Unary.Op` and `Binary.Op` enumeration values. They were already marked in the source code as their values needing to be part of the stable public interface, and their use in things like QPY is the reason why.
Now rebased over main. |
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 LGTM, thanks for working through this. Using the expression visitor class works quite naturally for this which makes it easier to deal with. I just left a few small comments and questions inline, most are my idle musings and not really critical.
@@ -632,8 +715,10 @@ def generate_circuits(version_parts): | |||
if version_parts >= (0, 24, 1): | |||
output_circuits["open_controlled_gates.qpy"] = generate_open_controlled_gates() | |||
output_circuits["controlled_gates.qpy"] = generate_controlled_gates() | |||
if version_parts > (0, 24, 2): | |||
if version_parts >= (0, 24, 2): |
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, good catch yeah now that 0.24.2 has been released this was skipping the layout test from 0.24.2 -> 0.25.0
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, I think I may have done that accidentally - it was part of a merge conflict haha
"condition_register_size", | ||
"condition_value", | ||
"num_ctrl_qubits", | ||
"ctrl_state", | ||
], | ||
) | ||
CIRCUIT_INSTRUCTION_V2_PACK = "!HHHII?HqII" | ||
CIRCUIT_INSTRUCTION_V2_PACK = "!HHHIIBHqII" |
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 didn't version this pack string because ?
and B
are both a single byte and are interchangeable so nothing will break going from the old format to the new (except the parsing but it is a new format so that's fine).
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.
Ah, yep I see that called out in the docs too so yeah that makes sense.
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, exactly - and the struct-pack conversion to ?
guarantees that the written value is exactly 0
or 1
, so there's precise ABI compatibility
@@ -123,9 +227,9 @@ def _read_parameter_expression(file_obj): | |||
if _optional.HAS_SYMENGINE: | |||
import symengine | |||
|
|||
expr = symengine.sympify(parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE))) | |||
expr_ = symengine.sympify(parse_expr(file_obj.read(data.expr_size).decode(common.ENCODE))) |
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.
Heh, I didn't expect to ever have a conflict with this string. I should have written out symbolic_expression
here or something
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.
Eh, I probably should have thought that it's probably a relatively common abbreviation before squatting the expr
name for the module I added.
# decode fine so it's not worth another special case. They'll encode to | ||
# b"\xff\x80\x00\x00...", but we could encode them to b"\x80\x00\x00...". | ||
num_bytes = (node.value.bit_length() // 8) + 1 | ||
buffer = node.value.to_bytes(num_bytes, "big", signed=True) |
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.
Heh, I would have totally missed setting this explicitly. At least it looks like it defaults to big endian and we'd have matched the endianness for qpy.
I guess this is needed in case there is a >63 bit register being used in the expression so we can't use a fixed width integer encoding 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.
Yeah, that's the exact reason I put this conversion in. int.to_bytes
and int.from_bytes
both require you to set the endianness, so I had to remember 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.
LGTM, thanks for the fast update.
* Add `Expr` support to QPY for conditions and targets This adds support to QPY for the current `Expr` nodes, for *all* instruction parameters and conditions. The `Expr` tree is written out to the file in a sort of forwards Polish notation; each node has a type code and header, followed by a type-code-specific number of `Expr` children. While only `IfElseOp.condition`, `WhileLoopOp.condition` and `SwitchCaseOp.target` are allowed to have these nodes in Terra's data model right now, the QPY serialisation does not need to have this arbitrary restriction, and it's much easier just to write the general case. The backwards-compatibility guarantees of QPY are now brought to bear on the `Unary.Op` and `Binary.Op` enumeration values. They were already marked in the source code as their values needing to be part of the stable public interface, and their use in things like QPY is the reason why. * Improve documentation of `EXPRESSION` payload * Factor out magic numbers from discriminator sizes
Summary
This adds support to QPY for the current
Expr
nodes, for all instruction parameters and conditions. TheExpr
tree is written out to the file in a sort of forwards Polish notation; each node has a type code and header, followed by a type-code-specific number ofExpr
children.While only
IfElseOp.condition
,WhileLoopOp.condition
andSwitchCaseOp.target
are allowed to have these nodes in Terra's data model right now, the QPY serialisation does not need to have this arbitrary restriction, and it's much easier just to write the general case.The backwards-compatibility guarantees of QPY are now brought to bear on the
Unary.Op
andBinary.Op
enumeration values. They were already marked in the source code as their values needing to be part of the stable public interface, and their use in things like QPY is the reason why.Details and comments
Close #10289. Depends on #10359. Changelog as part of #10331.