Skip to content

Commit dd994d0

Browse files
authored
PBC transformations for C (#15756)
* Expose PBC operations to C * Fix docs & swap ZX order to be consistent with Pauli * Expose LitinskiTrafo to C * Add ConvertToPauliRotations to C * Extra sausage for win math * Fix compat with lates main * Shelly's comments * review comments - additional test if litinski is noop - update test names - remove some idle code
1 parent 9fe42d7 commit dd994d0

6 files changed

Lines changed: 496 additions & 1 deletion

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// This code is part of Qiskit.
2+
//
3+
// (C) Copyright IBM 2026
4+
//
5+
// This code is licensed under the Apache License, Version 2.0. You may
6+
// obtain a copy of this license in the LICENSE.txt file in the root directory
7+
// of this source tree or at https://www.apache.org/licenses/LICENSE-2.0.
8+
//
9+
// Any modifications or derivative works of this code must retain this
10+
// copyright notice, and modified files need to carry a notice indicating
11+
// that they have been altered from the originals.
12+
13+
use qiskit_circuit::{circuit_data::CircuitData, dag_circuit::DAGCircuit};
14+
use qiskit_transpiler::passes::py_convert_to_pauli_rotations;
15+
16+
use crate::pointers::mut_ptr_as_ref;
17+
18+
/// @ingroup QkTranspilerPassesStandalone
19+
/// Run the ``ConvertToPauliRotations`` pass in-place on a circuit.
20+
///
21+
/// This pass converts all standard gates (with less than 4 qubits) in the circuit into a sequence
22+
/// of ``QkPauliProductRotation`` gates and measurements into ``QkPauliProductMeasurement``
23+
/// instructions. Note that this pass panics if the circuit contains non-standard gates.
24+
/// The suggested workflow is to first transpile into a standard basis, keeping rotation gates
25+
/// (such as ``QkGate_RXX`` and others) intact where possible, and then call this pass.
26+
///
27+
/// @param circuit A pointer to the circuit on which to run the pass.
28+
///
29+
/// # Safety
30+
///
31+
/// Behavior is undefined if ``circuit`` is not valid, non-null pointers to a ``QkCircuit``.
32+
#[unsafe(no_mangle)]
33+
pub unsafe extern "C" fn qk_transpiler_pass_standalone_convert_to_pauli_rotations(
34+
circuit: *mut CircuitData,
35+
) {
36+
// SAFETY: The user guarantees the pointer is safe to read.
37+
let circuit = unsafe { mut_ptr_as_ref(circuit) };
38+
let dag = match DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) {
39+
Ok(dag) => dag,
40+
Err(_) => panic!("Internal Circuit -> DAG conversion failed"),
41+
};
42+
let out = py_convert_to_pauli_rotations(&dag).expect("Failed running PBC conversion.");
43+
// If a DAG is returned, the circuit has been modified. Else just leave it as is.
44+
*circuit = CircuitData::from_dag_ref(&out).expect("Internal DAG -> Circuit conversion failed");
45+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// This code is part of Qiskit.
2+
//
3+
// (C) Copyright IBM 2026
4+
//
5+
// This code is licensed under the Apache License, Version 2.0. You may
6+
// obtain a copy of this license in the LICENSE.txt file in the root directory
7+
// of this source tree or at https://www.apache.org/licenses/LICENSE-2.0.
8+
//
9+
// Any modifications or derivative works of this code must retain this
10+
// copyright notice, and modified files need to carry a notice indicating
11+
// that they have been altered from the originals.
12+
13+
use qiskit_circuit::{circuit_data::CircuitData, dag_circuit::DAGCircuit};
14+
use qiskit_transpiler::passes::run_litinski_transformation;
15+
16+
use crate::pointers::mut_ptr_as_ref;
17+
18+
/// @ingroup QkTranspilerPassesStandalone
19+
/// Run the ``LitinskiTransformation`` pass in-place on a circuit.
20+
///
21+
/// This pass commutes all Clifford gates to the end of the circuit, converting Pauli rotation
22+
/// gates into ``QkPauliProductRotation`` gates and measurements into ``QkPauliProductMeasurement``
23+
/// instructions. Note that this pass currently only supports circuits that have ``QkGate_T``,
24+
/// ``QkGate_Tdg`` or ``QkGate_RZ`` gates as non-Cliffords and panics otherwise. The suggested
25+
/// workflow is to first transpile into a Clifford+RZ basis and then call this pass.
26+
///
27+
/// @param circuit A pointer to the circuit on which to run the pass.
28+
/// @param fix_clifford If ``true``, leave the Clifford gates at the end of the circuit. If
29+
/// ``false`` they are omitted.
30+
///
31+
/// # Safety
32+
///
33+
/// Behavior is undefined if ``circuit`` is not valid, non-null pointers to a ``QkCircuit``.
34+
#[unsafe(no_mangle)]
35+
pub unsafe extern "C" fn qk_transpiler_pass_standalone_litinski_transformation(
36+
circuit: *mut CircuitData,
37+
fix_clifford: bool,
38+
) {
39+
// SAFETY: The user guarantees the pointer is safe to read.
40+
let circuit = unsafe { mut_ptr_as_ref(circuit) };
41+
let dag = DAGCircuit::from_circuit_data(circuit, false, None, None, None, None)
42+
.expect("Internal Circuit -> DAG conversion failed");
43+
44+
let maybe_out = run_litinski_transformation(&dag, fix_clifford, false, true)
45+
.expect("Failed running Litinski transformation");
46+
// If a DAG is returned, the circuit has been modified. Else just leave it as is.
47+
if let Some(out) = maybe_out {
48+
*circuit =
49+
CircuitData::from_dag_ref(&out).expect("Internal DAG -> Circuit conversion failed")
50+
};
51+
}

crates/cext/src/transpiler/passes/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
pub mod basis_translator;
1414
pub mod commutative_cancellation;
1515
pub mod consolidate_blocks;
16+
pub mod convert_to_pauli_rotations;
1617
pub mod elide_permutations;
1718
pub mod gate_direction;
1819
pub mod inverse_cancellation;
20+
pub mod litinski_transformation;
1921
pub mod optimize_1q_sequences;
2022
pub mod remove_diagonal_gates_before_measure;
2123
pub mod remove_identity_equiv;

crates/transpiler/src/passes/convert_to_pauli_rotations.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ fn generate_pauli_product_rotation_gate(paulis: &[BitTerm], angle: Param) -> Pau
480480
/// the pass.
481481
#[pyfunction]
482482
#[pyo3(name = "convert_to_pauli_rotations")]
483-
pub fn py_convert_to_pauli_rotations(dag: &mut DAGCircuit) -> PyResult<DAGCircuit> {
483+
pub fn py_convert_to_pauli_rotations(dag: &DAGCircuit) -> PyResult<DAGCircuit> {
484484
let mut new_dag = dag.copy_empty_like(VarsMode::Alike, BlocksMode::Drop)?;
485485

486486
// Iterate over nodes in the DAG and collect nodes
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
features_c:
3+
- Added :c:func:`qk_transpiler_pass_standalone_litinski_transformation` to apply a Litinski
4+
transformation to a ``QkCircuit`` in the C API. This is the equivalent of Python's
5+
:class:`.LitinskiTransformation` pass.
6+
- Added :c:func:`qk_transpiler_pass_standalone_convert_to_pauli_rotations` to convert a
7+
``QkCircuit`` made up of standard gates and measurements into Pauli-based computation format
8+
using ``QkPauliProductRotation`` and ``QkPauliProductMeasurement`` instructions.
9+
This is the equivalent of Python's :class:`.ConvertToPauliRotations` pass.

0 commit comments

Comments
 (0)