-
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 QuantumCircuit.find_bit to find index and Registers for a Bit. #6621
Add QuantumCircuit.find_bit to find index and Registers for a Bit. #6621
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.
I think we also will need this for dags too, This comes up a lot in transpiler passes too. We can do that in a separate PR though
3e1afd2
to
3a6ff0f
Compare
3a6ff0f
to
8b56d85
Compare
Due to the addition of |
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 find_bit
function certainly seems useful given that Bit
no longer stores references to its containing registers. I wasn't around at the time that decision was made - if you want to be able to locate registers from Bit
instances in some form (like here), could you point me to any discussion of why to remove it? Was there a discussion about keeping weakrefs in the Bit
? Perhaps they could have made this reverse look-up a bit simpler, without introducing nasty reference cycles for the garbage collector to sort out.
As it stands, I have a worry about separation of concerns between QuantumCircuit.add_register
and QuantumCircuitQregs
in a few list-modifying methods. QuantumCircuitQregs
seems to suggest that modifying QuantumCircuit.qregs
directly is now acceptable (since it calls back to QuantumCircuit
), such as
In [9]: from qiskit import QuantumCircuit, QuantumRegister
...: qregs = [QuantumRegister(2) for _ in [None]*2]
...: qc = QuantumCircuit(qregs[0])
...: qc.qregs.append(qregs[1])
...: qc.x(range(4))
...: qc.draw()
Out[9]:
┌───┐
q12_0: ┤ X ├
├───┤
q12_1: ┤ X ├
├───┤
q13_0: ┤ X ├
├───┤
q13_1: ┤ X ├
└───┘
However, I think in doing so the responsibilities of QuantumCircuitQregs
and QuantumCircuit.add_register
have become slightly crossed. Now, since Qregs
is meant to appear like a list to the user, I'd expect to be able to do
In [2]: from qiskit import QuantumCircuit, QuantumRegister
...: qregs = [QuantumRegister(2) for _ in [None]*4]
...: qc = QuantumCircuit(qregs[0], qregs[1])
...: qc.qregs[2:] = qregs[2:]
---------------------------------------------------------------------------
CircuitError Traceback (most recent call last)
<ipython-input-1-98e2b42d9269> in <module>
2 qregs = [QuantumRegister(2) for _ in [None]*4]
3 qc = QuantumCircuit(qregs[0], qregs[1])
----> 4 qc.qregs[2:] = qregs[2:]
~/code/qiskit/terra/qiskit/circuit/quantumcircuitdata.py in __setitem__(self, key, value)
155
156 def __setitem__(self, key, value):
--> 157 self._circuit.add_register(value)
158 self._list[key] = value
159 del self._list[-1]
~/code/qiskit/terra/qiskit/circuit/quantumcircuit.py in add_register(self, *regs)
1230
1231 elif isinstance(register, list):
-> 1232 self.add_bits(register)
1233 else:
1234 raise CircuitError("expected a register")
~/code/qiskit/terra/qiskit/circuit/quantumcircuit.py in add_bits(self, bits)
1253 self._clbit_indices[bit] = BitLocations(len(self._clbits) - 1, [])
1254 else:
-> 1255 raise CircuitError(
1256 "Expected an instance of Qubit, Clbit, or "
1257 "AncillaQubit, but was passed {}".format(bit)
CircuitError: "Expected an instance of Qubit, Clbit, or AncillaQubit, but was passed QuantumRegister(2, 'q2')"
(Qregs.__setitem__
doesn't handle slices), or with negative indices:
In [6]: from qiskit import QuantumCircuit, QuantumRegister
...: qregs = [QuantumRegister(2) for _ in [None]*4]
...: qc = QuantumCircuit(qregs[0])
...: print(qc.qregs[-1] is qregs[0], qc.qregs[-1] is qregs[1])
...: qc.qregs[-1] = qregs[1]
...: print(qc.qregs[-1] is qregs[0], qc.qregs[-1] is qregs[1])
True False
True False
where the last line should be False True
.
I mentioned it further down, but I think we need some way for Qregs
to access only the (currently imaginary) QuantumCircuit._add_references_to_bits
functionality separately to the QuantumCircuit._add_references_to_registers
, since Qregs
is now meant to be handling that.
|
||
# str | ||
self.assertEqual( | ||
str(circ_regs), "[{}, {}, {}, {}]".format(*map(repr, [reg1, reg2, reg3, reg4])) |
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.
Logically should this be map(str, [...])
, even if __str__
calls __repr__
?
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.
Yup, this was copy paste from an existing test, but agree str
makes more sense here.
|
||
# repr | ||
self.assertEqual( | ||
repr(circ_regs), "[{}, {}, {}, {}]".format(*map(repr, [reg1, reg2, reg3, reg4])) |
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 to avoid repr([reg1, reg2, reg3, reg4])
?
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 don't think so, these should be equivalent. I'll update.
qiskit/circuit/quantumcircuitdata.py
Outdated
@abstractmethod | ||
def __delitem__(self, i): | ||
pass | ||
|
||
@abstractmethod | ||
def __setitem__(self, index, value): | ||
pass | ||
|
||
@abstractmethod | ||
def insert(self, index, value): | ||
pass |
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 for leaving these abstract? With the docstring saying that this class is intended to implement list-like semantics, these would seem to have obvious default implementations, just like sort
or copy
.
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.
That's true. I left the methods that mutated self._list
as abstract because I couldn't see an obvious implementation that worked cleanly (across the few use cases we currently support) with validation, so this is left to the subclass.
qiskit/circuit/quantumcircuitdata.py
Outdated
try: | ||
# To consolidate validation to __setitem__, insert None and overwrite. | ||
self._list.insert(index, None) | ||
self[index] = value | ||
except CircuitError as err: | ||
del self._list[index] | ||
raise err |
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.
Alternatively, would it be cleaner to factor out the validation into a separate, private instance method, and then just have __setitem__
and insert
both call it?
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.
Thinking about this a bit more, I think yes, but this would require a not inconsiderable refactor to both the circuit and register. We're lumping a few concerns behind the concept of "validation" (is the item being added a valid candidate member for this data structure, and following this addition/modification, is the data structure left in a consistent state) which are currently implemented throughout QuantumCircuit.add_register
, QuantumCircuit.add_bits
, and Register.__init__
. If keeping these list-like interfaces is something we intend to support in the long term (and likely even if not), I agree there are definite opportunities for re-structuring to make this logic cleaner which I think we can tackle as a separate issue.
qiskit/circuit/quantumcircuitdata.py
Outdated
def __delitem__(self, index): | ||
del self._list[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.
Could this be made the concrete definition in VerifiedMutableSequence
? I can't necessarily see a case where del
would need to do any subclass-specific value validation, and the other defined subclass below uses the same method.
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.
Come to think of it, I can think of a case - If Qregs
manages the registers, and a register is deleted from the list, it should presumably call back to the circuit to delete references to the contained Bit
instances, and raise a noisy exception if any current instructions operate on those bits.
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 catch, I think there may be a case that I'm missing here (maybe removing a register that has old-style bits needs to be verboten until old-style bits are removed, or removing a register that's used in a conditional of a subsequent instruction) but if not, this could be made concrete.
qiskit/circuit/quantumcircuitdata.py
Outdated
def __setitem__(self, key, value): | ||
self._circuit.add_register(value) | ||
self._list[key] = value | ||
del self._list[-1] |
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 cyclical call structure here is difficult to follow for me. As I understand it:
self._circuit.add_register(value)
ends up callingself.append(value)
- now that the bit references are in place within
self._circuit
, we set the actual element of our list to the correct value - we delete the extra data that the unwanted
self.append(value)
added
My worries here are:
- it feels like we actually just want something like
self._circuit._add_internal_bit_references(value)
, since in the end, this class is responsible for the append of registers. - this won't work if
key
is negative, or aslice
, both of which are supported by list syntax. For example,self[-1] = QuantumRegister(2)
would append the quantum register to the end of the list, overwrite the same list location with the same data, and then delete it off the end. A slice would cause a list to get nested in the middle.
I think the solution to this involves changing the call to self._circuit.add_register
, so that the method by which we register bits in the circuit does not affect self
, and from there we can delegate to self._list.__setitem__
entirely. This would probably involve splitting out the "add to list of registers" logic from the "add references to all bits logic" inside QubitCircuit.add_register
, so we can only call the latter.
Qiskit/RFCs#15 (though dated) outlines the motivation behind removing
The intention here was actually the opposite, that
This seems like the right direction, and I think a lot of your (valid!) concerns stem from the tangling of validation logic in places like |
c87e1de
to
fb1365f
Compare
fb1365f
to
7d5d8eb
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.
Most comments are typo fixes, and in principle everything looks good to me in this PR. My most major comments were whether we can de-duplicate some population of QuantumCircuit._qubit_indices
by pushing some work onto QuantumCircuit.add_register
(I think we can, without performance issues), and probably that we should use namedtuple attribute access rather than indexed access (unless it is critically slow on Python 3.7 - again, I think we should be ok).
3c26aa0
to
1a9154e
Compare
1a9154e
to
4e1e5e8
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.
Looks good to me - just needs a reno now.
I pushed up a minor change adding type information and Sphinx crossrefs to the documentation of find_bit
, and removed the accidentally included test file (there's a bug in the visualisation tests that's generating an extra file at the moment), just in the interests of getting things merged quickly.
I'll approve properly after reno.
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.
These more minimal changes look good to me now, so now there's nothing blocking #6565!
This resets the state of the QASM 3 exporter draft back to a clean version, as if it were built cleanly upon the `find_bit` feature (Qiskit#6621), without any of the extra changes that subsequently got cut out of that PR.
Replace with `QuantumCircuit.find_bit` when `qiskit-terra` 0.18.4 is released: Qiskit/qiskit#6621
Replace with `QuantumCircuit.find_bit` when `qiskit-terra` 0.18.4 is released: Qiskit/qiskit#6621
Replace with `QuantumCircuit.find_bit` when `qiskit-terra` 0.18.4 is released: Qiskit/qiskit#6621
Replace with `QuantumCircuit.find_bit` when `qiskit-terra` 0.18.4 is released: Qiskit/qiskit#6621
Replace with `QuantumCircuit.find_bit` when `qiskit-terra` 0.18.4 is released: Qiskit/qiskit#6621
…iskit#6621) * Add Register.index and Register._bit_indices. * Add QuantumCircuit.find_bit to find index and Registers for a Bit. * Add typing information and crossrefs to find_bit * Revert accidental changes from base * Add release note. * Fix formatting in reno Co-authored-by: Jake Lishman <jake.lishman@ibm.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
…iskit/qiskit#6621) * Add Register.index and Register._bit_indices. * Add QuantumCircuit.find_bit to find index and Registers for a Bit. * Add typing information and crossrefs to find_bit * Revert accidental changes from base * Add release note. * Fix formatting in reno Co-authored-by: Jake Lishman <jake.lishman@ibm.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Summary
Resolves #6495 by replacing
QuantumCircuit{._qubit_set,._clbit_set}
withQuantumCircuit{._qubit_indices,._clbits_indices}
which are dicts mappingQubit
andClbit
instances to a tuple of their index (inQuantumCircuit{.qubits,.clbits}
and any containingRegister
s on the circuit.Adds a method
QuantumCircuit.find_bit
to perform a lookup of a givenBit
.Details and comments
WIP:bit_indices
calculations withQuantumCircuit.find_bit