Skip to content
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

Merged
merged 3 commits into from
Jul 20, 2023

Conversation

jakelishman
Copy link
Member

Summary

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.

Details and comments

Close #10289. Depends on #10359. Changelog as part of #10331.

@jakelishman jakelishman added Changelog: New Feature Include in the "Added" section of the changelog mod: qpy Related to QPY serialization labels Jul 6, 2023
@jakelishman jakelishman added this to the 0.25.0 milestone Jul 6, 2023
@jakelishman jakelishman requested a review from a team as a code owner July 6, 2023 02:25
@qiskit-bot
Copy link
Collaborator

One or more of the the following people are requested to review this:

@coveralls
Copy link

coveralls commented Jul 6, 2023

Pull Request Test Coverage Report for Build 5603129027

  • 202 of 241 (83.82%) changed or added relevant lines in 5 files are covered.
  • 92 unchanged lines in 7 files lost coverage.
  • Overall coverage increased (+0.02%) to 86.076%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/qpy/binary_io/circuits.py 22 23 95.65%
qiskit/qpy/binary_io/value.py 102 120 85.0%
qiskit/qpy/type_keys.py 45 65 69.23%
Files with Coverage Reduction New Missed Lines %
qiskit/transpiler/instruction_durations.py 1 92.96%
qiskit/transpiler/passmanager_config.py 1 97.26%
crates/qasm2/src/lex.rs 2 91.65%
qiskit/extensions/quantum_initializer/squ.py 2 80.0%
qiskit/providers/fake_provider/fake_backend.py 4 91.12%
qiskit/compiler/transpiler.py 13 92.89%
qiskit/circuit/quantumcircuit.py 69 94.18%
Totals Coverage Status
Change from base Build 5597428209: 0.02%
Covered Lines: 72804
Relevant Lines: 84581

💛 - 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.
@jakelishman
Copy link
Member Author

Now rebased over main.

@jakelishman jakelishman removed the on hold Can not fix yet label Jul 19, 2023
Copy link
Member

@mtreinish mtreinish left a 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):
Copy link
Member

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

Copy link
Member Author

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

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).

Copy link
Member

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.

Copy link
Member Author

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

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

Copy link
Member Author

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.

qiskit/qpy/__init__.py Outdated Show resolved Hide resolved
# 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)
Copy link
Member

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.

Copy link
Member Author

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.

qiskit/qpy/binary_io/value.py Outdated Show resolved Hide resolved
Copy link
Member

@mtreinish mtreinish left a 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.

@mtreinish mtreinish enabled auto-merge July 19, 2023 19:18
@mtreinish mtreinish added this pull request to the merge queue Jul 19, 2023
Merged via the queue into Qiskit:main with commit 02886d0 Jul 20, 2023
13 checks passed
@jakelishman jakelishman deleted the expr/qpy branch July 20, 2023 08:16
to24toro pushed a commit to to24toro/qiskit-terra that referenced this pull request Aug 3, 2023
* 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
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: qpy Related to QPY serialization
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for Expr and Type values in QPY serialisation
5 participants