-
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
Refactor output of OpenQASM 3 exporter to use fewer aliases #10249
Conversation
This removes spurious `_loose_bit` "registers" from the OpenQASM 3 output, and instead emits loose bits with individual `bit` and `qubit` declarations. Non-overlapping registers are emitted using regular `bit[n]` and `qubit[n]` definitions when possible, and we only resort to aliasing if we must to describe the structure. This avoids introducing structure to the definitions that does not exist in the original program, making round-trips and interactions with other OQ3 consumers more straightforwards. It's better not to use advanced features that don't map to hardware particularly well when it's not necessary. On the technical side, all bits are now properly tracked in the symbol table. Previously, there was a lot of code duplication, internal state tracking and magic inferences that attempted to "guess" how a qubit/clbit should be referred to. Instead, we just properly add them as variables to the symbol table, which also drastically reduces the number of objects that effectively reserve names that the user may not use.
One or more of the the following people are requested to review this:
|
Pull Request Test Coverage Report for Build 5245029921
💛 - Coveralls |
alias_classical_registers: bool = None, | ||
allow_aliasing: bool = None, |
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.
alias_classical_registers: bool = None, | |
allow_aliasing: bool = None, | |
alias_classical_registers: Optional[bool, None] = None, | |
allow_aliasing: Optional[bool, None] = None, |
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'm never super sure about this type of annotation. I mean, technically yes Optional[bool]
(or Union[bool, None]
) is correct for what the values are considered, but logically a caller should never pass None
. It's just an implementation detail we use to track whether the option was manually specified during the switchover period. It'll stop accepting None
once that period's over and just have the default False
.
I can go either way on this - I don't know what type-hints users typically prefer.
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.
Please proceed with your preference
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.
Thanks @jakelishman , LGTM
A minor point. Why is this line |
It's just a bit of an idiosyncrasy - there's no meaning. It so happens that |
I'm having difficulty understanding the main point(s) of this PR. What is the problem with aliasing and "trying to guess how a qubit/clbit should be referred to" ? Putting the summary at the top: I don't see that aliasing is essential if you want to collect loose bits as the existing code does (but I may be missing something). I do see that the PR may allow a more faithful round trip. The example given in the opening comment doesn't make it clear (to me at least) that the change is better. from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, qasm3
from qiskit.circuit import Qubit, Clbit
n_qubits = 3
n_clbits = 3
use_register = False
if use_register:
qubits = QuantumRegister(n_qubits, "qmem")
clbits = ClassicalRegister(n_clbits, "a")
else:
qubits = [Qubit() for _ in range(n_qubits)]
clbits = [Clbit() for _ in range(n_clbits)]
qc = QuantumCircuit(qubits, clbits)
qc.h(0)
for i in range(1, n_qubits):
qc.cx(0, i)
qc.measure(range(n_qubits), range(n_qubits)) # assume enough clbits
print(qasm3.dumps(qc)) what is the semantic difference when Is the following an instance of the unwanted aliasing? qubit[3] _all_qubits;
let qmem = _all_qubits[0:2]; Can you avoid aliasing by outputting something like this ? qubit[3] user_supplied_register_name_1;
qubit[3] _loose_qubits_1;
qubit[3] user_supplied_register_name_2; One possible advantage that I can see of this PR is the following. If the OC3 representation doesn't collect "scalar" qubits into a register, then the round-trip representation in qiskit can be more faithful to the original. Maybe that's worth preserving. EDIT: The following may be an example of the guess work or arbitrariness in referring to bits. If I have use a single list of qubits |
It's mostly about the OQ3 reflecting the structure of the Qiskit program more closely, which allows better round-tripping. That's originally why Ian had requested I do this; The other problem is that a bit can be in more than one register. The original code always used aliasing so that there only needed to be a single path, but that quickly needed modifying when it became clear that hardware was never going to support the concept of aliasing classical registers, so the To speak to the edit: whatever names you give your bits in Python space are irrelevant, because they're not stored in the |
"let first_four = {_qubit0, _qubit1, _qubit2, _qubit3};", | ||
"let last_five = {_qubit5, _qubit6, _qubit7, _qubit8, _qubit9};", | ||
"let alternate = {first_four[0], first_four[2], _qubit4, last_five[1], last_five[3]};", | ||
"let sporadic = {alternate[2], alternate[1], last_five[4]};", |
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 looks like a bit is referred to by its most recently created alias. Is this an implementation detail? Is it just an artifact of the implementation?
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.
Mostly an implementation detail - I (vaguely) commented on the behaviour here: https://github.com/Qiskit/qiskit-terra/blob/6160ef6ea49f243052a3e5445706e0f8e2dfb6f8/qiskit/qasm3/exporter.py#L769-L775
I tried to say the same thing. The identifiers used in Python are not preserved as identifiers in OC3 (I don't know how to do this.) But whether the bit is in a Qiskit register or not can be preserved. I think this part of the structure you are talking about. |
An answer to my question above:
In fact you have simplified this for input |
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 as well.
Summary
This removes spurious
_loose_bit
"registers" from the OpenQASM 3 output, and instead emits loose bits with individualbit
andqubit
declarations. Non-overlapping registers are emitted using regularbit[n]
andqubit[n]
definitions when possible, and we only resort to aliasing if we must to describe the structure.This avoids introducing structure to the definitions that does not exist in the original program, making round-trips and interactions with other OQ3 consumers more straightforwards. It's better not to use advanced features that don't map to hardware particularly well when it's not necessary.
On the technical side, all bits are now properly tracked in the symbol table. Previously, there was a lot of code duplication, internal state tracking and magic inferences that attempted to "guess" how a qubit/clbit should be referred to. Instead, we just properly add them as variables to the symbol table, which also drastically reduces the number of objects that effectively reserve names that the user may not use.
Details and comments
As an example, given this code:
the output before this PR was:
and now it is the more natural