Skip to content
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

Merged
merged 10 commits into from
Oct 30, 2024

Conversation

alexanderivrii
Copy link
Contributor

@alexanderivrii alexanderivrii commented Sep 30, 2024

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:

inner1 = QuantumCircuit(4)
inner1.h(0)
inner1.cz(0, 2)
qc = QuantumCircuit(6)
qc.append(inner1.to_instruction(), [1, 2, 3, 4])
pass_ = HighLevelSynthesis(basis_gates=["cx", "u"])
qct = pass_(qc)

Even though the inner circuit inner1 is defined over the qubits 1, 2, 3, 4 in the main circuit qc, the qubits 1 and 3 in the inner circuit (corresponding to the qubits 2 and 4 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

inner = QuantumCircuit(6)
inner.mcx([0, 1, 2, 3, 4], 5)
custom_gate = inner.to_gate()
qc = QuantumCircuit(10)
qc.append(custom_gate, [3, 4, 5, 6, 7, 0])
basis_gates = ["u", "cx"]
tqc = HighLevelSynthesis(basis_gates=basis_gates)(qc)

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 class QubitContext 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 by QubitTracker. 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.

@alexanderivrii alexanderivrii added the mod: transpiler Issues and PRs related to Transpiler label Sep 30, 2024
@alexanderivrii alexanderivrii added this to the 1.3.0 milestone Sep 30, 2024
@qiskit-bot
Copy link
Collaborator

One or more of the following people are relevant to this code:

  • @Qiskit/terra-core

@coveralls
Copy link

coveralls commented Sep 30, 2024

Pull Request Test Coverage Report for Build 11593752645

Details

  • 168 of 193 (87.05%) changed or added relevant lines in 2 files are covered.
  • 20 unchanged lines in 5 files lost coverage.
  • Overall coverage decreased (-0.008%) to 88.684%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/transpiler/passes/synthesis/qubit_tracker.py 58 70 82.86%
qiskit/transpiler/passes/synthesis/high_level_synthesis.py 110 123 89.43%
Files with Coverage Reduction New Missed Lines %
crates/accelerate/src/two_qubit_decompose.rs 1 92.09%
crates/accelerate/src/unitary_synthesis.rs 1 92.2%
crates/qasm2/src/expr.rs 1 94.02%
crates/qasm2/src/lex.rs 5 92.23%
crates/qasm2/src/parse.rs 12 97.15%
Totals Coverage Status
Change from base Build 11592501902: -0.008%
Covered Lines: 75573
Relevant Lines: 85216

💛 - Coveralls

@raynelfss raynelfss self-assigned this Oct 22, 2024
Copy link
Contributor

@raynelfss raynelfss left a 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.

qiskit/transpiler/passes/synthesis/high_level_synthesis.py Outdated Show resolved Hide resolved
qiskit/transpiler/passes/synthesis/high_level_synthesis.py Outdated Show resolved Hide resolved
qiskit/transpiler/passes/synthesis/high_level_synthesis.py Outdated Show resolved Hide resolved
qiskit/transpiler/passes/synthesis/high_level_synthesis.py Outdated Show resolved Hide resolved
"""

if dag.num_qubits() != context.num_qubits():
raise TranspilerError("HighLevelSynthesis internal error.")
Copy link
Contributor

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.

Copy link
Contributor Author

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.")
Copy link
Contributor

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.")
Copy link
Contributor

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.

qiskit/transpiler/passes/synthesis/high_level_synthesis.py Outdated Show resolved Hide resolved
Comment on lines +685 to +686
if isinstance(synthesized, DAGCircuit) and synthesized_context is None:
raise TranspilerError("HighLevelSynthesis internal error.")
Copy link
Contributor

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.

Copy link
Contributor Author

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.

@alexanderivrii
Copy link
Contributor Author

alexanderivrii commented Oct 24, 2024

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.

@alexanderivrii
Copy link
Contributor Author

Please note a small follow-up PR #13369 that ports QubitTracker and QubitContext to Rust.

Copy link
Contributor

@raynelfss raynelfss left a 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!

@raynelfss raynelfss added this pull request to the merge queue Oct 30, 2024
Merged via the queue into Qiskit:main with commit 7be05d7 Oct 30, 2024
17 checks passed
@ShellyGarion ShellyGarion added the Changelog: New Feature Include in the "Added" section of the changelog label Nov 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: New Feature Include in the "Added" section of the changelog mod: transpiler Issues and PRs related to Transpiler
Projects
Status: done
Development

Successfully merging this pull request may close these issues.

Improve ancilla detection in HighLevelSynthesis
5 participants