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

Fix QPY handling of registers #9095

Merged
merged 7 commits into from
Nov 9, 2022
Merged

Conversation

jakelishman
Copy link
Member

Summary

QPY previously had rather complicated logic to handle deserialisation of registers that was heavily couched in the old model of registers "owning" the bits they contained. This is no longer the data model of the circuit, so the code was naturally complex to try and make it work.

Instead, in deserialisation now we just add the correct number of bit instances to the circuit at the start, and then build up any required registers by using the indices field.

It's not clear to me how an index ever could be negative here; it's not clear how a register could be in the circuit but its bits aren't added, so it feels like there might be the possibility of a bug I've missed.

Details and comments

Fix #9094.

@jakelishman jakelishman added stable backport potential The bug might be minimal and/or import enough to be port to stable Changelog: Bugfix Include in the "Fixed" section of the changelog mod: qpy Related to QPY serialization labels Nov 7, 2022
@jakelishman jakelishman added this to the 0.22.3 milestone Nov 7, 2022
@jakelishman jakelishman requested a review from a team as a code owner November 7, 2022 23:43
@qiskit-bot
Copy link
Collaborator

Thank you for opening a new pull request.

Before your PR can be merged it will first need to pass continuous integration tests and be reviewed. Sometimes the review process can be slow, so please be patient.

While you're waiting, please feel free to review other open PRs. While only a subset of people are authorized to approve pull requests for merging, everyone is encouraged to review open pull requests. Doing reviews helps reduce the burden on the core team and helps make the project's code better for everyone.

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

QPY previously had rather complicated logic to handle deserialisation of
registers that was heavily couched in the old model of registers
"owning" the bits they contained.  This is no longer the data model of
the circuit, so the code was naturally complex to try and make it work.

Instead, in deserialisation now we just add the correct number of bit
instances to the circuit at the start, and then build up any required
registers by using the `indices` field.

It's not clear to me how an index ever could be negative here; it's not
clear how a register could be in the circuit but its bits aren't added,
so it feels like there might be the possibility of a bug I've missed.
@coveralls
Copy link

coveralls commented Nov 8, 2022

Pull Request Test Coverage Report for Build 3432228181

  • 9 of 9 (100.0%) changed or added relevant lines in 1 file are covered.
  • 3 unchanged lines in 2 files lost coverage.
  • Overall coverage increased (+0.05%) to 84.607%

Files with Coverage Reduction New Missed Lines %
qiskit/qpy/binary_io/circuits.py 1 91.17%
src/sabre_swap/layer.rs 2 98.95%
Totals Coverage Status
Change from base Build 3432121402: 0.05%
Covered Lines: 62614
Relevant Lines: 74006

💛 - Coveralls

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.

TBH, this whole code path for trying to recreate the exact bit and register combination was always super sketchy. In the original version we were trying to exactly recreate the same combination of registers and bits was recreated exactly. IIRC we added QPY around the same time as standalone bits and it complicated everything especially around bit ownership which has definitely been refined since then. But, I still get a bit anxious touching this code path.

That being said I think this is ok, I did some manual tests of different mixes of register bits and standalone and they all worked. We also have some pretty extensive unit tests with different combinations of bits and registers which we had to cover all the weird edge cases I came up with. But before we move forward on this I think it would be good if we had two more test cases I just realized was missing coverage before, we should test the round trip of a circuit with empty registers and a mix of an empty register with standalone bits. I know it works with this PR (I tested it) but I just want to make sure we're asserting that.

@jakelishman
Copy link
Member Author

Sounds good - I added those new cases in 03d802c.

@jakelishman
Copy link
Member Author

If we're nervous about the scope of the change to the logic here, we could consider holding the bugfix until 0.23.

@mtreinish
Copy link
Member

Yeah, I think that would be wise. Or at least let it sit on main a bit longer than typically to ensure we've tested it sufficiently even if we decide to backport it eventually.

@mtreinish mtreinish added automerge and removed stable backport potential The bug might be minimal and/or import enough to be port to stable labels Nov 9, 2022
@mergify mergify bot merged commit c0ac5fb into Qiskit:main Nov 9, 2022
@jakelishman jakelishman deleted the fix-qpy-int-error branch November 14, 2022 21:16
Cryoris pushed a commit to Cryoris/qiskit-terra that referenced this pull request Jan 12, 2023
* Fix QPY handling of registers

QPY previously had rather complicated logic to handle deserialisation of
registers that was heavily couched in the old model of registers
"owning" the bits they contained.  This is no longer the data model of
the circuit, so the code was naturally complex to try and make it work.

Instead, in deserialisation now we just add the correct number of bit
instances to the circuit at the start, and then build up any required
registers by using the `indices` field.

It's not clear to me how an index ever could be negative here; it's not
clear how a register could be in the circuit but its bits aren't added,
so it feels like there might be the possibility of a bug I've missed.

* Remove unused import

* Add tests cases of empty registers

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
jakelishman added a commit to jakelishman/qiskit-terra that referenced this pull request Feb 3, 2023
A major bugfix for QPY's handling of loose circuit bits in c0ac5fb
(Qiskitgh-9095) caused the deprecated bit properties `index` and `register` to
be lost after the QPY roundtrip.  As far as Terra's data model is
concerned, the two circuits would still compare equal after this loss;
the output registers would still be equal to the inputs, and bit
equality is not considered directly since it general bit instances are
not expected to be equal between circuits.

Losing this information caused some downstream issues for the IBM
runtime, which was still relying on `Bit.index` working in some cases,
despite it issuing a deprecating warning since Terra 0.17 (April 2021).
While this can be corrected downstream, QPY can still do a better job of
roundtripping the deprecated information while it is still present.

The QPY format does not currently store enough information to
_completely_ roundtrip this information in cases that some but not all
owned bits from a register are present in the circuit.  (This partial
data is a decent part of the cause of the bugs that Qiskitgh-9095 fixed.)
Since this is just in the support of deprecated functionality that
Terra's data model does not even require for circuit equality (QPY's
goal), it seems not worth it to produce a new QPY binary format version
to store this, when the deprecated properties being removed would
obsolete the format again immediately.
jakelishman added a commit to jakelishman/qiskit-terra that referenced this pull request Feb 7, 2023
This allows complete round-tripping of all the deprecated register+index
information in the `Bit` instances through QPY, restoring us to the
_intended_ behaviour before Qiskitgh-9095.  The behaviour in the dumper before
that did not allow full reconstruction, because some of the information
was lost for bits that were encountered in more than one register.

This fixes the dumper to always output all the indexing information for
all bits, not to use `-1` whenever a bit _is_ in the circuit but has
previously been encountered.  The `standalone` field on a register is
sufficient to tell whether the bits contained in it should have their
"owner" information set; it's not possible (in valid Qiskit code) to
have a register that owns only _some_ of its bits.  To accomodate this,
the register reader now needs to be two-pass.
mergify bot added a commit that referenced this pull request Feb 16, 2023
* Retain more deprecated Bit properties in QPY roundtrip

A major bugfix for QPY's handling of loose circuit bits in c0ac5fb
(gh-9095) caused the deprecated bit properties `index` and `register` to
be lost after the QPY roundtrip.  As far as Terra's data model is
concerned, the two circuits would still compare equal after this loss;
the output registers would still be equal to the inputs, and bit
equality is not considered directly since it general bit instances are
not expected to be equal between circuits.

Losing this information caused some downstream issues for the IBM
runtime, which was still relying on `Bit.index` working in some cases,
despite it issuing a deprecating warning since Terra 0.17 (April 2021).
While this can be corrected downstream, QPY can still do a better job of
roundtripping the deprecated information while it is still present.

The QPY format does not currently store enough information to
_completely_ roundtrip this information in cases that some but not all
owned bits from a register are present in the circuit.  (This partial
data is a decent part of the cause of the bugs that gh-9095 fixed.)
Since this is just in the support of deprecated functionality that
Terra's data model does not even require for circuit equality (QPY's
goal), it seems not worth it to produce a new QPY binary format version
to store this, when the deprecated properties being removed would
obsolete the format again immediately.

* Fix lint

* Correct deprecated bit information in QPY

This allows complete round-tripping of all the deprecated register+index
information in the `Bit` instances through QPY, restoring us to the
_intended_ behaviour before gh-9095.  The behaviour in the dumper before
that did not allow full reconstruction, because some of the information
was lost for bits that were encountered in more than one register.

This fixes the dumper to always output all the indexing information for
all bits, not to use `-1` whenever a bit _is_ in the circuit but has
previously been encountered.  The `standalone` field on a register is
sufficient to tell whether the bits contained in it should have their
"owner" information set; it's not possible (in valid Qiskit code) to
have a register that owns only _some_ of its bits.  To accomodate this,
the register reader now needs to be two-pass.

* Add deprecated-bit checks to backwards compatibility tests

* Rewrite release note

* Improve internal documentation

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
mergify bot pushed a commit that referenced this pull request Feb 16, 2023
* Retain more deprecated Bit properties in QPY roundtrip

A major bugfix for QPY's handling of loose circuit bits in c0ac5fb
(gh-9095) caused the deprecated bit properties `index` and `register` to
be lost after the QPY roundtrip.  As far as Terra's data model is
concerned, the two circuits would still compare equal after this loss;
the output registers would still be equal to the inputs, and bit
equality is not considered directly since it general bit instances are
not expected to be equal between circuits.

Losing this information caused some downstream issues for the IBM
runtime, which was still relying on `Bit.index` working in some cases,
despite it issuing a deprecating warning since Terra 0.17 (April 2021).
While this can be corrected downstream, QPY can still do a better job of
roundtripping the deprecated information while it is still present.

The QPY format does not currently store enough information to
_completely_ roundtrip this information in cases that some but not all
owned bits from a register are present in the circuit.  (This partial
data is a decent part of the cause of the bugs that gh-9095 fixed.)
Since this is just in the support of deprecated functionality that
Terra's data model does not even require for circuit equality (QPY's
goal), it seems not worth it to produce a new QPY binary format version
to store this, when the deprecated properties being removed would
obsolete the format again immediately.

* Fix lint

* Correct deprecated bit information in QPY

This allows complete round-tripping of all the deprecated register+index
information in the `Bit` instances through QPY, restoring us to the
_intended_ behaviour before gh-9095.  The behaviour in the dumper before
that did not allow full reconstruction, because some of the information
was lost for bits that were encountered in more than one register.

This fixes the dumper to always output all the indexing information for
all bits, not to use `-1` whenever a bit _is_ in the circuit but has
previously been encountered.  The `standalone` field on a register is
sufficient to tell whether the bits contained in it should have their
"owner" information set; it's not possible (in valid Qiskit code) to
have a register that owns only _some_ of its bits.  To accomodate this,
the register reader now needs to be two-pass.

* Add deprecated-bit checks to backwards compatibility tests

* Rewrite release note

* Improve internal documentation

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
(cherry picked from commit b6aba59)
mergify bot added a commit that referenced this pull request Feb 16, 2023
* Retain more deprecated Bit properties in QPY roundtrip

A major bugfix for QPY's handling of loose circuit bits in c0ac5fb
(gh-9095) caused the deprecated bit properties `index` and `register` to
be lost after the QPY roundtrip.  As far as Terra's data model is
concerned, the two circuits would still compare equal after this loss;
the output registers would still be equal to the inputs, and bit
equality is not considered directly since it general bit instances are
not expected to be equal between circuits.

Losing this information caused some downstream issues for the IBM
runtime, which was still relying on `Bit.index` working in some cases,
despite it issuing a deprecating warning since Terra 0.17 (April 2021).
While this can be corrected downstream, QPY can still do a better job of
roundtripping the deprecated information while it is still present.

The QPY format does not currently store enough information to
_completely_ roundtrip this information in cases that some but not all
owned bits from a register are present in the circuit.  (This partial
data is a decent part of the cause of the bugs that gh-9095 fixed.)
Since this is just in the support of deprecated functionality that
Terra's data model does not even require for circuit equality (QPY's
goal), it seems not worth it to produce a new QPY binary format version
to store this, when the deprecated properties being removed would
obsolete the format again immediately.

* Fix lint

* Correct deprecated bit information in QPY

This allows complete round-tripping of all the deprecated register+index
information in the `Bit` instances through QPY, restoring us to the
_intended_ behaviour before gh-9095.  The behaviour in the dumper before
that did not allow full reconstruction, because some of the information
was lost for bits that were encountered in more than one register.

This fixes the dumper to always output all the indexing information for
all bits, not to use `-1` whenever a bit _is_ in the circuit but has
previously been encountered.  The `standalone` field on a register is
sufficient to tell whether the bits contained in it should have their
"owner" information set; it's not possible (in valid Qiskit code) to
have a register that owns only _some_ of its bits.  To accomodate this,
the register reader now needs to be two-pass.

* Add deprecated-bit checks to backwards compatibility tests

* Rewrite release note

* Improve internal documentation

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
(cherry picked from commit b6aba59)

Co-authored-by: Jake Lishman <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: Bugfix Include in the "Fixed" section of the changelog mod: qpy Related to QPY serialization
Projects
None yet
Development

Successfully merging this pull request may close these issues.

QPY fails to load circuits whose first register qubit is after a loose bit
4 participants