-
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
Improve qubit tracking in HighLevelSynthesis #13240
Improve qubit tracking in HighLevelSynthesis #13240
Conversation
One or more of the following people are relevant to this code:
|
Pull Request Test Coverage Report for Build 11593752645Details
💛 - Coveralls |
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 great work, thank you!
I had a couple of questions about the implementation of the QubitContext
and the refactoring of QubitTracker
. But the addition seems to make sense so far.
""" | ||
|
||
if dag.num_qubits() != context.num_qubits(): | ||
raise TranspilerError("HighLevelSynthesis internal error.") |
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.
Do we know any way that this condition would be reached? If so we might wanna put a more specific error message here.
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.
If there are no bugs, this should never-ever happen. However, this was very useful during PR implementation / debugging (as it allows to pinpoint the existence of a problem as early as possible), and I think keeping these checks would be good in case there are some bugs / unexpected corner-cases. I don't know if I can think of a more meaningful message than will help debugging, modulo the fact that something is going wrong.
else: | ||
# The for-loop terminates without reaching the break statement | ||
if dag.num_qubits() != context.num_qubits(): | ||
raise TranspilerError("HighLevelSynthesis internal error.") |
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.
Same here, could we be specific if we know what could cause this to happen, if ever.
if len(synthesized_nodes) == 0: | ||
if dag.num_qubits() != context.num_qubits(): | ||
raise TranspilerError("HighLevelSynthesis internal error.") |
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.
Same as previous comment, do we know what could cause this condition and could we give a more detailed error message.
if isinstance(synthesized, DAGCircuit) and synthesized_context is None: | ||
raise TranspilerError("HighLevelSynthesis internal error.") |
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.
We could be a little more specific about this error message if the reason for this not to work is known.
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.
Ok, I will try to see if I can think of better error messages... Or maybe I can differentiate these somehow, in one place say "something when horribly wrong", in another - "something went terribly wrong", etc.
Note: in 7432b6b I have disabled one of the newly added MCMT tests, I am trying to decide whether it points to a real bug or HLS is accidentally doing something too smart. Please do not merge the PR before that's taken care of. UPDATE: false alarm, the point of the test was to check that MCMT's default plugin would not use ancillas if not enough were available and thus resorted to a more naive definition consisting of two consecutive MCX gates. However, ha-ha, the MCX synthesis plugin was using these ancilla qubits. The fix in 1e10c0d simply disabled MCX synthesis algorithm from using ancillas. |
Please note a small follow-up PR #13369 that ports |
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 code here is mostly good, there are still some optimizations that can be made but since these additions are already being ported to rust by #13369 we can focus on adding those there. Great job @alexanderivrii!
Summary
Fixes #13239. Now for both examples in the referenced issue, HighLevelSynthesis produces a circuit with 24 CX-gates and 45 U-gates.
In addition, this also improves clean ancilla detection on following example:
Even though the inner circuit
inner1
is defined over the qubits1, 2, 3, 4
in the main circuitqc
, the qubits1
and3
in the inner circuit (corresponding to the qubits2
and4
in the main circuit) remain clean and can be used as clean ancilla qubits in the following gates.Details and comments
This PR is based on numerous discussions with @Cryoris.
Recalling the second example from the referenced issue
the tricky part is that the recursive call to
HighLevelSynthesis::_run
on the inner circuit should have access to the clean "global" qubits outside of the circuit's definition. To tackle this, we introduce a classQubitContext
which keeps the correspondence between the current DAG's qubits and the global qubits of the original circuit. The state of the global qubits (clean/dirty) is tracked byQubitTracker
. When an internal synthesis algorithm (here for the internal MCX gate) checks how many clean/dirty ancilla qubits are available, it does so taking all of the global qubits into account.However, this also means that synthesizing an internal DAG may output a DAG with more qubits. In particular (demonstrating possible complications) we can have an internal DAG with say 10 qubits that contains an MCX-gate over 8 qubits that can be synthesized by the appropriate synthesis algorithm to a circuit over 14 qubits (exploiting the global clean/dirty) qubits. Hence, our internal DAG must also grow in size (fortunately all the tracking is possible using the local-to-global correspondences of all the objects involved) which might mean that the DAG higher up in the chain might need to grow as well.
This PR also adds more HighLevelSynthesis tests exploring all these possible edge-cases.
In summary, this should handle any mixture of recursively defined circuits with annotated operations, custom gate definitions and HighLevelSynthesis plugins. For circuits containing control-flow ops, the extended functionality is not used (and will be delegated to another PR, if someone wants to tackle this). In other words, if we have a control-flow op over say 4 qubits in the bigger circuit, only these 4 qubits will be used when recursively processing the blocks in this op.
Update: An additional observation is that this also improves the synthesis of open-controlled MCX gates. In Qiskit, the name of an open-controlled gate is not of the form "mcx", but rather of the form "mcx_o17", where "17" is (the integer representation of) the control state. When processing this gate, HLS would not immediately call a synthesis plugin (since "mcx_o17" is not in the list of gate names for which plugins exist), but recursively process the definition of this gate, which is a quantum circuit consisting of a layer of X-gates, a closed-controlled MCX gate (called "mcx"), and the inverse layer of X-gates. During this recursion, HLS would now indeed call the synthesis plugin for the internal MCX-gate, and with this PR it would be able to use the ancilla qubits in the main circuit and outside of the internal open-controlled gate's definition.