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

Improvements to HighLevelSynthesis transpiler pass #13605

Open
wants to merge 33 commits into
base: main
Choose a base branch
from

Conversation

alexanderivrii
Copy link
Contributor

@alexanderivrii alexanderivrii commented Dec 25, 2024

Summary

This PR reimplements the internals of the HighLevelSynthesis transpiler pass, still in Python. The pass is now arguably much simpler and cleaner.

Features:

  • It moves the handling of annotated operations into a separate plugin, addressing Move handling of AnnotatedOperations in HighLevelSynthesis to a plugin #13565.
  • If the HighLevelSynthesis transpiler pass needs to do something nontrivial, then except for the top-level circuit_to_dag and dag_to_circuit conversions, all the internal recursive functions work only with QuantumCircuits, greatly simplifying the API and avoiding constant back-and-forth conversions.
  • It will be now much easier to port the main functionality to Rust.

The PR is based on multiple discussions with @Cryoris.

Since most of the changes are internal implementation details, the only thing I am emphasizing in the release notes is the plugin interface for annotated operations.

Details and comments

One particular implementation detail that has changed significantly is how we reason about ancilla qubits in combination with the highly recursive nature of the pass. Take for example a quantum circuit containing a custom gate whose definition involves another custom gate whose definition involves an MCX gate that we can synthesize more efficiently using the auxiliary qubits from the global circuit. This was already possible, and to support this we have introduced the QubitContext class that represents a mapping from the current (internal) circuit's qubits to the global qubits (i.e. qubits of the original circuit). If synthesizing an operation required more auxiliary variables, these mappings were extended to include these new variables. With this PR, all the internal functions directly work with the global qubits instead of these mappings. For instance, the internal _synthesize_operation function now receives an Operation and a list of global qubits it's defined on and returns.a QuantumCircuit and a possibly extended list of global qubits on which the new quantum circuit is defined. This makes the booktracking significantly simpler. (In particular, the internal QubitContext class will probably be deleted, or repurposed to speed up certain internal computations).

A lot of this refactoring was needed to turn handling of annotated operations into a plugin. The way this was done before (and is pretty much the same way it's still done now) is first to synthesize the base operation of such an annotated operation using the full power of recursive synthesis, and then apply control, power and inverse modifiers to the obtained circuit. However, synthesizing the base operation requires the internal data and options of the HighLevelSynthesis pass itself, the qubits on which the base operation is defined, and the qubit tracker -- the last two are needed to allow using ancilla qubits. This motivated making the function _run, _synthesize_operation, etc. as global functions rather than belonging to a specific HighLevelSynthesis object (alternatively we could make these staticfunctions). In addition, this flattening also simplifies moving these function to Rust in a followup.

Alternatively, above we could have instantiate a new HighLevelSynthesis pass from within the current pass, however the initialization (i.e. HighLevelSynthesis__init__ ) incurs a certain overhead in the (getting the lists of available plugins, etc.) . Instead, all the immutable entries are stored in the new HLSData struct which is passed from one recursive function to another (and also to the plugin for annotated operations using the plugin_args dict).

Follow-up plans:

There are several follow-up items that will be handled in follow-up PRs.

  • Port the main functionality of the pass to Rust
  • Rethink the input/output API for plugins (while preserving backward-compatibility). Ideally, I would now like each plugin method to take in a triple (operation, global qubits on which this operation is defined, state of the qubits) and return either None or the synthesized result in the form of another triple (synthesized circuit, possibly extended list of global qubits on which this circuit is defined, possibly updated state of the qubits). Right now all the plugins only know how many clean/dirty ancilla qubits are available, but not what these qubits actually are, and only return the synthesized quantum circuit, without specifying ancilla qubits actually used. This might be important if we want to resynthesize a quantum circuit using ancilla qubits, when the connectivity map is provided, and the choice of an actual ancilla qubit actually matters.
  • Somewhere along the way, we should rethink the API for the qubit tracking mechanism. Right now I am trying to avoid copying it when unnecessary (i.e. changing the state of qubits in-place), but maybe an API where the function both accepts and returns a qubit tracker might be cleaner (and in Rust, we can possibly avoid copying, i.e. just moving).

@alexanderivrii alexanderivrii requested review from ShellyGarion and a team as code owners December 25, 2024 10:33
@qiskit-bot
Copy link
Collaborator

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

  • @Cryoris
  • @Qiskit/terra-core
  • @ajavadia

@alexanderivrii alexanderivrii added the mod: transpiler Issues and PRs related to Transpiler label Dec 25, 2024
@alexanderivrii alexanderivrii added this to the 2.0.0 milestone Dec 25, 2024
Comment on lines +1109 to +1110
transpiled_circuit = HighLevelSynthesis(basis_gates=["cx", "u"])(circuit)
self.assertEqual(transpiled_circuit.count_ops().keys(), {"cx", "u"})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually fixes a problem: previously the circuit was not fully synthesized, now it is.

@coveralls
Copy link

coveralls commented Dec 25, 2024

Pull Request Test Coverage Report for Build 12493914300

Details

  • 225 of 259 (86.87%) changed or added relevant lines in 3 files are covered.
  • 51 unchanged lines in 5 files lost coverage.
  • Overall coverage decreased (-0.06%) to 88.887%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/transpiler/passes/synthesis/hls_plugins.py 54 62 87.1%
qiskit/transpiler/passes/synthesis/high_level_synthesis.py 168 178 94.38%
crates/accelerate/src/high_level_synthesis.rs 3 19 15.79%
Files with Coverage Reduction New Missed Lines %
crates/accelerate/src/two_qubit_decompose.rs 1 92.13%
crates/accelerate/src/unitary_synthesis.rs 1 92.2%
crates/qasm2/src/lex.rs 2 92.48%
crates/qasm2/src/parse.rs 12 96.69%
crates/accelerate/src/high_level_synthesis.rs 35 53.99%
Totals Coverage Status
Change from base Build 12420636821: -0.06%
Covered Lines: 79389
Relevant Lines: 89315

💛 - Coveralls

@@ -143,7 +142,7 @@ def test_plugins(self):
"""Test setting the HLS plugins for the modular adder."""

# all gates with the plugins we check, including an expected operation
plugins = [("cumulative_h18", "ccircuit-.*"), ("qft_r17", "qft")]
plugins = [("cumulative_h18", "ch"), ("qft_r17", "mcphase")]
Copy link
Contributor Author

@alexanderivrii alexanderivrii Dec 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When simplifying the code for handling controlled-annotated operations (in particular wrapping a single operation into a circuit so that we only need to consider circuits and not both circuits and operations), I have changed the code on how a control is applied to a circuit: instead of first converting the circuit into a gate and applying control to this gate, we create a new quantum circuit with the controlled version of each gate. This makes sure that we do not deteriorate the results in the case of a single iteration (and may actually improve the results in the case of a circuit). Hmm, now that I am writing this, they may be a slight problem when controlling a circuit that has a global phase, I will need to fix this.

UPDATE: that was indeed a bug, which is fixed in c5f0f0e.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
mod: transpiler Issues and PRs related to Transpiler
Projects
Status: To do
Development

Successfully merging this pull request may close these issues.

4 participants