Skip to content

Commit

Permalink
Port HighLevelSynthesis pass to rust (#13813)
Browse files Browse the repository at this point in the history
* Moving all internal fields into a separate class

* making all the inner functions to be global

* moving handling annotated operations to plugin

The interface is now quite ugly, and we also have an extra isinstance check for
each gate, we will see if it can be removed.

* improving control-modified synthesis plugin

* Changing internal functions to work with QuantumCircuits instead of DAGCircuits

* renaming

* Removing unnecessary argument use_ancillas

This argument is not needed as the qubit tracker already restricts
allowed ancilla qubits.

* progress

* minor

* removing context

* minor

* removing unused argument

* removing an obsolete statement

* minor cleanup

* minor

* improvements to run method

* cleanup

* another pass over the run method + black

* minor cleanup

* pass over synthesize_operation

* more cleanup

* pass over HLS::run

* cleanup

* pass over annotated plugin

* cleanup

* improving comment

* fixing pylint

* remove print statements in tests

* fmt

* fix + test for controlling circuits with nontrivial phase

* adding release notes

* adding test function docstring

* minor

* Finalizing data class contruction

* adding error message

* Adding method op_names to HighLevelSynthesisPluginManager

This is used to simplify the check whether a given node/operation can be skipped.

* updating old usages of equiv library

* fixing lint errors after merge

* slightly simplifying the arguments to instruction_supported method

* Fixing definition method for PyGate and PyInstruction

* docstring fixes

* applying Julien's suggestion

* adding intern

* replacing data by _data (accidentally removed in cleanup)

* porting main functionality to rust

* Adding missing functionality

* passing empty dict to dag_to_circuit when None

* Adding definition for UnitaryGate

Even when HighLevelSynthesis runs directly after UnitarySynthesis, it may happen that
certain definitions involve UnitaryGates (for instance, this is the case for Isometry),
in which case the default behavior of HighLevelSynthesis should be to query the
definition of UnitaryGates if they are not in the basis.

* Replacing from_circuit_data by clone_empty_like

Going from QuantumCircuit/DAGCircuit to CircuitData discards information,
such as information about registers, input variables and more. The previously
tried approach of constructing DAGCircuit/QuantumCircuitData in rust space by
calling _from_circuit_data and manually fixing registers was not complete.
Instead we now call clone_empty_like both on DAGCircuit and QuantumCircuit.

* making target of type Option<Py<Target>>
and avoiding expensive cloning

* changing type of equivalence library as well

* adding pickling support

this actually requires dill since HLSConfig includes a lambda function
for comparing circuits obtained with different synthesis plugins,
but fortunately it's all already supported

* Passing Bound<HighLevelSynthesisData> to functions

This avoids cloning data when calling Python

* Using Bound<QubitTracker> in functions

This avoid having to clone it when calling the Python space

* Removing the pub keywork in front of HLS structs, members and internal functions

* Adding function to return a definition for a given operation.

The main functionality is to create some default definition for unitary gates, for all other
operation types we can simply use the definition method.

* pylint

* clippy

* Adding tests for HLS tracking global phase.

With various conversions of DAGCircuit to QuantumCircuitData to CircuitData in Rust,
it's best to make sure that the global phases appearing in circuits/control-flow subcircuits
are tracked correctly

* cleanup

* removing unnecessary changes to other files

* improving Rust HLS interface; skipping synthesis of already supported ops when calls from annotated plugin

* clippy

* updating code after merging with main

* finalize merge with main

* using pyo3 get/set

* avoding extra casts u32 -> usize when possible

* avoiding multiple calls to data.borrow()

* changing QubitTracker from being a Python class

* applying Eli's review comments

* also applying renames on the python side

* Applying more of Eli's comments

* renaming use_qubit_indices to use_physical_indices

* Changing hls_op_names to a set for further efficiency

* adding an even faster check whether HLS can be skipped

* improving transpiler errors (with user in mind)

* improving comment

* further improving all_instructions_supported

* addressing more review comments

* include the upgrade reno comment

* adding a comment with detailed explanation on how we track qubits when synthesizing the base operation of an annotated operation
  • Loading branch information
alexanderivrii authored Mar 4, 2025
1 parent 47e8c98 commit 96ebf2a
Show file tree
Hide file tree
Showing 13 changed files with 1,296 additions and 666 deletions.
892 changes: 835 additions & 57 deletions crates/accelerate/src/high_level_synthesis.rs

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions crates/circuit/src/circuit_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1143,6 +1143,29 @@ impl CircuitData {
Ok(())
}

/// Append a packed operation to this CircuitData
pub fn push_packed_operation(
&mut self,
operation: PackedOperation,
params: &[Param],
qargs: &[Qubit],
cargs: &[Clbit],
) -> PyResult<()> {
let params = (!params.is_empty()).then(|| Box::new(params.iter().cloned().collect()));
let qubits = self.qargs_interner.insert(qargs);
let clbits = self.cargs_interner.insert(cargs);
self.data.push(PackedInstruction {
op: operation,
qubits,
clbits,
params,
label: None,
#[cfg(feature = "cache_pygates")]
py_op: OnceLock::new(),
});
Ok(())
}

/// Add the entries from the `PackedInstruction` at the given index to the internal parameter
/// table.
fn track_instruction_parameters(
Expand Down
2 changes: 1 addition & 1 deletion crates/circuit/src/dag_circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,7 @@ impl DAGCircuit {
/// Args:
/// angle (float, :class:`.ParameterExpression`): The phase angle.
#[setter]
fn set_global_phase(&mut self, angle: Param) -> PyResult<()> {
pub fn set_global_phase(&mut self, angle: Param) -> PyResult<()> {
match angle {
Param::Float(angle) => {
self.global_phase = Param::Float(angle.rem_euclid(2. * PI));
Expand Down
4 changes: 4 additions & 0 deletions crates/circuit/src/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ pub static XX_EMBODIMENTS: ImportOnceCell =
ImportOnceCell::new("qiskit.synthesis.two_qubit.xx_decompose", "XXEmbodiments");
pub static NUMPY_COPY_ONLY_IF_NEEDED: ImportOnceCell =
ImportOnceCell::new("qiskit._numpy_compat", "COPY_ONLY_IF_NEEDED");
pub static HLS_SYNTHESIZE_OP_USING_PLUGINS: ImportOnceCell = ImportOnceCell::new(
"qiskit.transpiler.passes.synthesis.high_level_synthesis",
"_synthesize_op_using_plugins",
);

/// A mapping from the enum variant in crate::operations::StandardGate to the python
/// module path and class name to import it. This is used to populate the conversion table
Expand Down
1 change: 1 addition & 0 deletions crates/circuit/src/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2799,6 +2799,7 @@ impl Operation for UnitaryGate {
fn definition(&self, _params: &[Param]) -> Option<CircuitData> {
None
}

fn standard_gate(&self) -> Option<StandardGate> {
None
}
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ sk = "qiskit.transpiler.passes.synthesis.solovay_kitaev_synthesis:SolovayKitaevS
"Multiplier.cumulative_h18" = "qiskit.transpiler.passes.synthesis.hls_plugins:MultiplierSynthesisH18"
"PauliEvolution.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:PauliEvolutionSynthesisDefault"
"PauliEvolution.rustiq" = "qiskit.transpiler.passes.synthesis.hls_plugins:PauliEvolutionSynthesisRustiq"
"annotated.default" = "qiskit.transpiler.passes.synthesis.hls_plugins:AnnotatedSynthesisDefault"

[project.entry-points."qiskit.transpiler.init"]
default = "qiskit.transpiler.preset_passmanagers.builtin_plugins:DefaultInitPassManager"
Expand Down
3 changes: 1 addition & 2 deletions qiskit/converters/dag_to_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ def dag_to_circuit(dag, copy_operations=True):
)
for var in dag.iter_declared_vars():
circuit.add_uninitialized_var(var)
circuit.metadata = dag.metadata

circuit.metadata = dag.metadata or {}
circuit._data = circuit_data

circuit._duration = dag.duration
Expand Down
Loading

0 comments on commit 96ebf2a

Please sign in to comment.