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
API changes for optional registers in QuantumCircuit. #5486
API changes for optional registers in QuantumCircuit. #5486
Conversation
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.
So I think this looks good and I like the direction. Just a couple of questions below and a few inline comments/questions:
-
For a register-free bit it basically is just a type/class and a hash/id for the object? Longer term It does make we wonder if we just want to use int indices for bits in QuantumCircuit/DAGCircuit and registers are lists of ints. (ie basically metadata around the bits in a circuit). But that would be a much larger change and probably too hard to make. (this is more just an idle thought, I do think the direction in this PR makes the most sense)
-
Have you done any performance testing on a registerless circuit vs a register one? I know that we've spent a bunch of time tuning the register <-> bit creation and comparisons as operations with bits end up being a lot of the checks we do in the transpiler. Looking at the new bit classes and how
__repr__
is changed I think we should look into this ahead of time.
if (register, index) == (None, None): | ||
self._register = None | ||
self._index = None | ||
self._hash = object.__hash__(self) |
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.
Is there a difference between this and hash(self)
?
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.
hash(self)
would call the overridden Bit.__hash__
, so this is only here to sidestep and use the default object hash
for new-style bits (while allowing backwards compatibility for old-style). I'll add a comment to clarify, if you're okay with this approach.
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 ok, yeah that makes sense. Yeah I think having a comment about this is a good idea. It is a bit confusing.
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.
Comment added in 528f8593f8e2182f9d71303ba11ba8dac44a22b0 .
@@ -49,33 +54,50 @@ def _update_hash(self): | |||
@property | |||
def register(self): | |||
"""Get bit's register.""" | |||
if (self._register, self._index) == (None, None): | |||
raise CircuitError('Attmped to query register of a new-style Bit.') |
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 would probably say registerless bit instead of new-style. I think this context around new-style is temporary.
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.
Agree the context is temporary and only for the transition. I went with old- and new-style over registerless because the new Bit
s can still be a part of a register, which might be confusing. There are only a few places that call out the differences, so I'm happy to update if you think it's clearer.
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.
It would be clearer to me. In my head this kind of bit is always registerless even if it's part of a register it's still a registerless bit right (the bit doesn't have a register in its context) but instead the register contains the bits? It's splitting hairs but that's how I would think about it. But, I'll defer to others because what's clearer to me isn't necessarily clear for everyone else and this isn't a big deal either way.
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.
Okay, good point. For now, I'll leave these in place and when we're writing documentation/release notes, we can decide how best to phrase it (and update the warnings accordingly).
@@ -43,6 +44,61 @@ def test_circuit_constructor_wires_wrong_mix(self): | |||
self.assertRaises(CircuitError, QuantumCircuit, 1, ClassicalRegister(2)) | |||
|
|||
|
|||
class TestAddingBitsWithoutRegisters(QiskitTestCase): |
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.
The only test I would think to add here is one for the mixed register, registerless bit case that index in gates works as expected. But that might be in one of the follow on commits, I haven't looked at those yet.
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.
Good point. I don't think there's a test covering mixed registered and registerless Bits in the tests covering addition of gates on a specific qubit index (updated in #5498). I'll add one there.
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.
Test added in 71ec0a4 .
qiskit/circuit/bit.py
Outdated
if (self._register, self._index) == (None, None): | ||
return "%s(%s)" % (self.__class__.__name__, hex(id(self))) |
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.
Is there a reason not to cache this? It definitely will be faster than the repr based on the register case, but we still end up using this a lot (because we use str(qargs) as the sort key for lexicographical_topological_sort()
) and this basically gets called once for each bit in an op's qargs list each time we create a new DAGNode. So I wonder what the overhead is going to be for doing the string manipulation and hex conversion once per op vs once per bit.
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.
This is another sidestep to have new-style Bit
s look more like placeholder objects (though looking now, similarly using object.__str__
would've been better). I'll update this.
For lexicographical_topological_sort()
, I was hoping to remove the string comparison because we would only need an object comparison with new-style Bit
s, but looking again, rx.lexicographical_topological_sort
expects a key
function that returns a string. I missed this in the DAGCircuit PR so this is a good catch, I'll update DAGNode.sort_key
there and if string construction still shows up in profiling, we can resume caching.
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 doing the string comparison on the retworkx rust side was done for performance. When we first added that function about a year ago having the callback return a string key was a conscious tradeoff between covering a wider set of use cases and performance for how DAGCircuit used it because at least back then the testing showed rust side string comparison to be faster than a python callback returning a bool, but this might have changed some in the past year).
That's right. Long term, converting to
Agree, I've done some preliminary testing on the development branch of the final state (all converted to new-style Bits) but only to check the overall design for large regressions and not covering the intermediate state where we need to check and toggle between old- and new-style (which I would expect to be slower, but not substantially). |
35ce723
to
734e48d
Compare
734e48d
to
d18b0e2
Compare
3b53fee
to
d18b0e2
Compare
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, a couple more quick inline comments and questions. But nothing really blocking (lets just fix the typo in the exception message before merging).
return True | ||
|
||
res = False | ||
if type(self) is type(other) and \ |
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 probably would have used ==
here but I don't think it matters.
all( | ||
# For new-style bits, check bitwise equality. | ||
sbit == obit | ||
for sbit, obit in zip(self, other) | ||
if None in | ||
(sbit._register, sbit._index, | ||
obit._register, obit._index) | ||
): |
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 know I asked this in an earlier review, but was there performance impact here. In #5272 we removed the bit wise equality check because it had significant overhead for DAGNode creation. I don't think it will come up because it was per bit iirc and only an artifact of Bit.__eq__
previously comparing registers, but it's just something we should keep in mind.
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.
Agree, much of the transpiler has been updated to avoid register comparisons in #5515 so it's unlikely to be an issue, but agree its something to keep an eye on.
The constructors of the :class:`~qiskit.circuit.Bit` class and subclasses, | ||
:class:`~qiskit.circuit.Qubit`, :class:`~qiskit.circuit.Clbit`, and | ||
:class:`~qiskit.circuit.AncillaQubit`, have been updated such that their | ||
two parameters, ``register`` and ``index`` are now optional. |
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.
Maybe have an example here? But, we can just do it in the release time roundup.
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.
Good point, I'll make sure to add it prior to release.
Summary
Updates to
Bit
,Register
,QuantumCircuit
andDAGCircuit
classes to move fromRegisters
as the core data structure unit toward makingBit
s fundamental andRegister
s optional.Details and comments
This PR covers only the API changes required to create and add
Bit
s toQuantumCircuit
s,DAGCircuit
s andRegister
s. Subsequent PRs:Bit.register
and.index
internally inQuantumCircuit
andDAGCircuit
(and thus supportingRegister
s as optional)Bit.index
andBit.register
properties'q'
/'c'
registers fromQuantumCircuit
int constructorSee working branch master...kdk:quantumcircuit-optional-registers .