Skip to content

Conversation

jakelishman
Copy link
Member

@jakelishman jakelishman commented Nov 10, 2021

"Summary"

This completely overhauls InstructionSet.c_if to use the containing
QuantumCircuit instance (if there is one) to resolve requests for
classical resources. This has several benefits:

  1. The meaning of a integer is guaranteed to be the same between
    instances of QuantumCircuit and InstructionSet.

  2. There's no need for code duplication between the two.

  3. With the resolution tied to pre-existing instance methods or
    functions now, there's no need for each InstructionSet instance
    to build up its own resolution graph over registers, which is a
    speed boost.

  4. It makes it possible for resolvers in the future to reject certain
    requests for resources, if they cannot be satisfied. This can
    easily be used by scoping constructs, to forbid access to extra
    resource once the scope has closed, since InstructionSet.c_if can
    mutate the contents after the fact.

Since InstructionSet is technically part of the public API, albeit not
one we expect people to actually be using, this deprecates the
circuit_cregs keyword argument, and maintains a compatibility layer so
existing calls will continue to work until the deprecation period
expires. All internal usages of InstructionSet use the new form.

There remains an unfortunate linear complexity in the new resource
checker, when seeing if a ClassicalRegister target is valid. This is
because there is currently no set-like interface to the registers stored
in any given circuit, unlike the bits. QuantumCircuit exposes its
lists of registers publically, so adding this map safely is non-trivial,
as it is simple for a user or subclasses to cause any new map to fall
out of sync with the list. This commit does not attempt to fix this,
but simply suffers the linear penalty for now; we expect there to be
only a few registers, so it should not be too onerous.

The previous form of InstructionSet.c_if had a hack to support a
limitation of disassemble. Previously, any single-bit conditions
where the bit was part of a register of size one would get automatically
promoted to be conditional on the register, not the bit. With
QasmQobj, single-bit conditions are indistinguishable from conditions
on size-one registers, so InstructionSet.c_if promoted them all to
register comparisons to try and make disassemble(assemble(qc)) a
complete round-trip in those cases. Since QasmQobj cannot cope with
loose classical bits or overlapping registers, anyway, this was at best
a stop-gap measure, as not all circuits can be round-tripped already.
This commit removes the auto-promotion behaviour from
InstructionSet.c_if, but adds the logic to disassemble instead,
noting in the documentation that not all circuits will produce exactly
equivalent output when round-tripped. This is to make the "type system"
of classical conditions strict, which significantly simplifies the
resource management.

Details and comments

This PR isn't as big as it looks - it's 90% tests and documentation.

The idea of a "resource resolver" callback is better for consistency, but I also designed it to do double duty. The control-flow circuit builder interface would struggle to work safely with c_if, because c_if mutates instructions after they've passed through QuantumCircuit.append, which is where the builders hook in to build scopes and manage resources, but having c_if call back to a resource manager lets them work safely again (and also ensures that you can't mess with instructions once the scope has closed).

@jakelishman jakelishman requested a review from a team as a code owner November 10, 2021 20:40
@jakelishman jakelishman added Changelog: Bugfix Include in the "Fixed" section of the changelog Changelog: Deprecation Include in "Deprecated" section of changelog labels Nov 10, 2021
@jakelishman jakelishman added this to the 0.19 milestone Nov 10, 2021
@coveralls
Copy link

coveralls commented Nov 10, 2021

Pull Request Test Coverage Report for Build 1468227974

  • 60 of 60 (100.0%) changed or added relevant lines in 3 files are covered.
  • 3 unchanged lines in 1 file lost coverage.
  • Overall coverage increased (+0.004%) to 82.542%

Files with Coverage Reduction New Missed Lines %
qiskit/pulse/library/waveform.py 3 90.38%
Totals Coverage Status
Change from base Build 1467462259: 0.004%
Covered Lines: 49830
Relevant Lines: 60369

💛 - Coveralls

@jakelishman jakelishman added the on hold Can not fix yet label Nov 12, 2021
@jakelishman
Copy link
Member Author

On hold until #7123 merges, because while there won't be any merge conflicts, logically this PR will want to make a couple of changes to some error checking routines in that PR once it's in. The code here is still good for immediate review, though.

This completely overhauls `InstructionSet.c_if` to use the containing
`QuantumCircuit` instance (if there is one) to resolve requests for
classical resources.  This has several benefits:

  1. The meaning of a integer is guaranteed to be the same between
     instances of `QuantumCircuit` and `InstructionSet`.

  2. There's no need for code duplication between the two.

  3. With the resolution tied to pre-existing instance methods or
     functions now, there's no need for each `InstructionSet` instance
     to build up its own resolution graph over registers, which is a
     speed boost.

  4. It makes it possible for resolvers in the future to reject certain
     requests for resources, if they cannot be satisfied.  This can
     easily be used by scoping constructs, to forbid access to extra
     resource once the scope has closed, since `InstructionSet.c_if` can
     mutate the contents after the fact.

Since `InstructionSet` is technically part of the public API, albeit not
one we expect people to actually be using, this deprecates the
`circuit_cregs` keyword argument, and maintains a compatibility layer so
existing calls will continue to work until the deprecation period
expires.  All internal usages of `InstructionSet` use the new form.

There remains an unfortunate linear complexity in the new resource
checker, when seeing if a `ClassicalRegister` target is valid.  This is
because there is currently no set-like interface to the registers stored
in any given circuit, unlike the bits.  `QuantumCircuit` exposes its
lists of registers publically, so adding this map safely is non-trivial,
as it is simple for a user or subclasses to cause any new map to fall
out of sync with the list.  This commit does not attempt to fix this,
but simply suffers the linear penalty for now; we expect there to be
only a few registers, so it should not be too onerous.

The previous form of `InstructionSet.c_if` had a hack to support a
limitation of `disassemble`.  Previously, any single-bit conditions
where the bit was part of a register of size one would get automatically
promoted to be conditional on the register, not the bit.  With
`QasmQobj`, single-bit conditions are indistinguishable from conditions
on size-one registers, so `InstructionSet.c_if` promoted them all to
register comparisons to try and make `disassemble(assemble(qc))` a
complete round-trip in those cases.  Since `QasmQobj` cannot cope with
loose classical bits or overlapping registers, anyway, this was at best
a stop-gap measure, as not all circuits can be round-tripped already.
This commit removes the auto-promotion behaviour from
`InstructionSet.c_if`, but adds the logic to `disassemble` instead,
noting in the documentation that not all circuits will produce exactly
equivalent output when round-tripped.  This is to make the "type system"
of classical conditions strict, which significantly simplifies the
resource management.
@jakelishman jakelishman removed the on hold Can not fix yet label Nov 15, 2021
@jakelishman
Copy link
Member Author

Updated to account for the extra changes in #7123, so this is no longer on hold.

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, I especially like the documentation improvements. Just a few inline comments and questions but mostly just minor things and existing quirks with this functionality.

I also don't think the linear scan for registers will be that bad in practice. It's not like there are circuits out there with thousands of registers, I'd say the typical use case is one classical register for all the measurement bits. In the worst case it'll be a handful but like never more than 10, probably nothing where we should have scaling issues.

.. note::

``disassemble(assemble(qc))`` is not guaranteed to produce an exactly equal circuit to the
input, due to limitations in the :obj:`.QasmQobj` format that need to be maintained for
Copy link
Member

Choose a reason for hiding this comment

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

Does sphinx resolve this correctly? If so neat, I didn't realize we could short cut it like this.

Copy link
Member Author

@jakelishman jakelishman Nov 16, 2021

Choose a reason for hiding this comment

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

:obj:`.x` only displays what's after the . (I think), and in module/class a.b.C it has the search path:

  1. a.b.C.x
  2. a.b.x
  3. a.x
  4. x
  5. any prefix.x

Sphinx is meant to warn if there are duplicate names that it could resolve to, but I've never actually tried that myself - if I know there are potentially duplicate names, I tend to use slightly longer forms because if it's confusing for me/Sphinx, it'll probably be confusing for the reader too. For example, in the release notes I used :meth:`.InstructionSet.c_if` to avoid conflict with Instruction.c_if, and that displays like this:

Screenshot 2021-11-16 at 10 05 15

edit: also yes, it does resolve :obj:`.QasmQobj` correctly in those docs.

Comment on lines +43 to +49
.. note::

``disassemble(assemble(qc))`` is not guaranteed to produce an exactly equal circuit to the
input, due to limitations in the :obj:`.QasmQobj` format that need to be maintained for
backend system compatibility. This is most likely to be the case when using newer features
of :obj:`.QuantumCircuit`. In most cases, the output should be equivalent, if not quite
equal.
Copy link
Member

Choose a reason for hiding this comment

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

This is a good note to include, I'm always telling people this that the QC -> Qobj -> QC path isn't guaranteed to be lossless (most recently here: https://quantumcomputing.stackexchange.com/questions/21795/is-there-a-quantum-circuit-builder-package-that-allows-you-to-import-export-a-ci/21800#21800 ). Making this explicit in the documentation is a good idea.

" when creating this InstructionSet."
)
if self._resolver is not None:
classical = self._resolver(classical)
for gate in self.instructions:
gate.c_if(classical, val)
return self
Copy link
Member

Choose a reason for hiding this comment

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

It is a bit weird if we don't allow chaining here that we return self like this.

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, seems slightly odd. Perhaps we were thinking of adding more methods to Instruction/InstructionSet so you may want to do multiple things at once.

Copy link
Member

@kdk kdk left a comment

Choose a reason for hiding this comment

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

Few small questions, LGTM otherwise.

@jakelishman jakelishman requested review from mtreinish and kdk November 16, 2021 11:01
kdk
kdk previously approved these changes Nov 16, 2021
Copy link
Member

@kdk kdk 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 this updates, this LGTM.

This makes it clearer that the requester is actually returning a
resource for the caller to use, which makes it semantically more natural
that it may track those resources as well.
@jakelishman jakelishman requested a review from kdk November 16, 2021 19:25
@kdk kdk merged commit 271a82f into Qiskit:main Nov 16, 2021
@jakelishman jakelishman deleted the fix/c_if branch November 26, 2021 12:05
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 Changelog: Deprecation Include in "Deprecated" section of changelog
Projects
None yet
4 participants