Skip to content

Commit 34e9f03

Browse files
mtreinishCryorisalexanderivrii
authored
Add CommutativeCancellation standalone transpiler pass to C API (Qiskit#14831)
* Add CommutativeCancellation standalone transpiler pass to C API This commit adds a standalone transpiler pass function to the C API for running the `CommutativeCancellation` transpiler pass. This pass mutates the dag in place if there are any gates to be cancelled. Fixes Qiskit#14446 * Apply suggestions from code review Co-authored-by: Julien Gacon <gaconju@gmail.com> * Use ExitCode instead of returning raw int * Move approximation_degree check before dag conversion * Update example with valid C * Fix rust test * Apply suggestions from code review Co-authored-by: Julien Gacon <gaconju@gmail.com> --------- Co-authored-by: Julien Gacon <gaconju@gmail.com> Co-authored-by: Alexander Ivrii <alexi@il.ibm.com>
1 parent 4bf1e26 commit 34e9f03

File tree

5 files changed

+246
-0
lines changed

5 files changed

+246
-0
lines changed

crates/cext/src/exit_codes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub enum CInputError {
2727

2828
/// Integer exit codes returned to C.
2929
#[repr(u32)]
30+
#[derive(PartialEq, Eq, Debug)]
3031
pub enum ExitCode {
3132
/// Success.
3233
Success = 0,
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// This code is part of Qiskit.
2+
//
3+
// (C) Copyright IBM 2025
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 http://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 crate::exit_codes::ExitCode;
14+
use crate::pointers::{const_ptr_as_ref, mut_ptr_as_ref};
15+
16+
use qiskit_circuit::circuit_data::CircuitData;
17+
use qiskit_circuit::converters::dag_to_circuit;
18+
use qiskit_circuit::dag_circuit::DAGCircuit;
19+
use qiskit_transpiler::commutation_checker::get_standard_commutation_checker;
20+
use qiskit_transpiler::passes::cancel_commutations;
21+
use qiskit_transpiler::target::Target;
22+
23+
/// @ingroup QkTranspilerPasses
24+
/// Run the CommutativeCancellation transpiler pass on a circuit.
25+
///
26+
/// This pass cancels the redundant (self-adjoint) gates through commutation relations.
27+
///
28+
/// @param circuit A pointer to the circuit to run CommutativeCancellation on. This circuit
29+
/// pointer to will be updated with the modified circuit if the pass is able to remove any gates.
30+
/// @param target This pass will attempt to accumulate all Z rotations into either
31+
/// an RZ, P or U1 gate, depending on which is already used in the circuit. If none
32+
/// is present in the circuit, this (optional) target argument is used as fallback to
33+
/// decide which gate to use. If none of RZ, P or U1 are in the circuit or the target,
34+
/// single-qubit Z rotations will not be optimized.
35+
/// @param approximation_degree The approximation degree used when
36+
/// analyzing commutations. Must be within ``(0, 1]``.
37+
/// @returns The integer return code where 0 represents no error and 1 is
38+
/// used to indicate an error was encountered during the execution of the pass.
39+
///
40+
/// # Example
41+
///
42+
/// ```c
43+
/// QkCircuit *qc = qk_circuit_new(4, 0);
44+
/// uint32_t cx_qargs[2] = {0, 1};
45+
/// qk_circuit_gate(qc, QkGate_CX, cx_qargs, NULL);
46+
/// qk_circuit_gate(qc, QkGate_Z, (uint32_t[]){0}, NULL);
47+
/// qk_circuit_gate(qc, QkGate_CX, cx_qargs, NULL);
48+
/// qk_transpiler_pass_standalone_commutative_cancellation(qc, NULL, 1.0);
49+
/// ```
50+
///
51+
/// # Safety
52+
///
53+
/// Behavior is undefined if ``circuit`` or ``target`` is not a valid, ``QkCircuit`` and ``QkTarget``.
54+
/// ``QkCircuit`` is not expected to be null and behavior is undefined if it is.
55+
#[no_mangle]
56+
#[cfg(feature = "cbinding")]
57+
pub unsafe extern "C" fn qk_transpiler_pass_standalone_commutative_cancellation(
58+
circuit: *mut CircuitData,
59+
target: *const Target,
60+
approximation_degree: f64,
61+
) -> ExitCode {
62+
// SAFETY: Per documentation, the pointer is non-null and aligned.
63+
let circuit = unsafe { mut_ptr_as_ref(circuit) };
64+
let target = if target.is_null() {
65+
None
66+
} else {
67+
// SAFETY: Per documentation, the pointer is non-null and aligned.
68+
Some(unsafe { const_ptr_as_ref(target) })
69+
};
70+
if !(0.0..=1.0).contains(&approximation_degree) {
71+
panic!("Invalid value provided for approximation degree, only NAN or values between 0.0 and 1.0 inclusive are valid");
72+
}
73+
let mut dag = match DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) {
74+
Ok(dag) => dag,
75+
Err(_) => panic!("Internal circuit -> DAG conversion failed"),
76+
};
77+
let mut commutation_checker = get_standard_commutation_checker();
78+
let basis = target.map(|t| t.operation_names().map(|n| n.to_string()).collect());
79+
if cancel_commutations(
80+
&mut dag,
81+
&mut commutation_checker,
82+
basis,
83+
approximation_degree,
84+
)
85+
.is_err()
86+
{
87+
return ExitCode::TranspilerError;
88+
}
89+
let out_circuit = match dag_to_circuit(&dag, false) {
90+
Ok(qc) => qc,
91+
Err(_) => panic!("Internal DAG -> circuit conversion failed"),
92+
};
93+
*circuit = out_circuit;
94+
ExitCode::Success
95+
}
96+
97+
#[cfg(test)]
98+
mod tests {
99+
use super::*;
100+
use qiskit_circuit::bit::ShareableQubit;
101+
use qiskit_circuit::circuit_data::CircuitData;
102+
use qiskit_circuit::operations::StandardGate;
103+
use qiskit_circuit::Qubit;
104+
105+
#[test]
106+
fn test_commutative_cancellation() {
107+
let mut qc = CircuitData::new(
108+
Some((0..2).map(|_| ShareableQubit::new_anonymous()).collect()),
109+
None,
110+
None,
111+
0,
112+
(0.).into(),
113+
)
114+
.unwrap();
115+
qc.push_standard_gate(StandardGate::CX, &[], &[Qubit(0), Qubit(1)])
116+
.unwrap();
117+
qc.push_standard_gate(StandardGate::Z, &[], &[Qubit(0)])
118+
.unwrap();
119+
qc.push_standard_gate(StandardGate::CX, &[], &[Qubit(0), Qubit(1)])
120+
.unwrap();
121+
let result = unsafe {
122+
qk_transpiler_pass_standalone_commutative_cancellation(&mut qc, std::ptr::null(), 1.0)
123+
};
124+
assert_eq!(result, ExitCode::Success);
125+
assert_eq!(qc.__len__(), 1);
126+
let Some(gate) = qc.data()[0].op.try_standard_gate() else {
127+
panic!("Not a standard gate");
128+
};
129+
assert_eq!(StandardGate::Z, gate);
130+
}
131+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
// copyright notice, and modified files need to carry a notice indicating
1111
// that they have been altered from the originals.
1212

13+
pub mod commutative_cancellation;
1314
pub mod elide_permutations;
1415
pub mod gate_direction;
1516
pub mod inverse_cancellation;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
features_c:
3+
- |
4+
Added a new standalone transpiler pass function
5+
``qk_transpiler_pass_standalone_commutative_cancellation`` which is for
6+
running the :class:`.CommutativeCancellation` transpiler pass. Calling this
7+
function in C is equivalent to running the pass in Python like::
8+
9+
CommutativeCancellation(..)(circuit)
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// This code is part of Qiskit.
2+
//
3+
// (C) Copyright IBM 2025.
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 http://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+
#include "common.h"
14+
#include <complex.h>
15+
#include <qiskit.h>
16+
#include <stdbool.h>
17+
#include <stddef.h>
18+
#include <stdint.h>
19+
#include <stdio.h>
20+
#include <string.h>
21+
22+
int test_commutative_cancellation_target(void) {
23+
const uint32_t num_qubits = 5;
24+
QkTarget *target = qk_target_new(num_qubits);
25+
qk_target_add_instruction(target, qk_target_entry_new(QkGate_Z));
26+
qk_target_add_instruction(target, qk_target_entry_new(QkGate_SX));
27+
qk_target_add_instruction(target, qk_target_entry_new(QkGate_CX));
28+
29+
int result = Ok;
30+
31+
QkCircuit *qc = qk_circuit_new(2, 0);
32+
uint32_t cx_qargs[2] = {
33+
0,
34+
1,
35+
};
36+
uint32_t rz_qargs[1] = {
37+
0,
38+
};
39+
double rz_params[1] = {
40+
3.14159,
41+
};
42+
qk_circuit_gate(qc, QkGate_CX, cx_qargs, NULL);
43+
qk_circuit_gate(qc, QkGate_RZ, rz_qargs, rz_params);
44+
qk_circuit_gate(qc, QkGate_CX, cx_qargs, NULL);
45+
46+
result = qk_transpiler_pass_standalone_commutative_cancellation(qc, target, 1.0);
47+
if (result != 0) {
48+
printf("Running the pass failed");
49+
goto cleanup;
50+
}
51+
52+
if (qk_circuit_num_instructions(qc) != 1) {
53+
result = EqualityError;
54+
printf("The gates weren't removed by this circuit");
55+
}
56+
cleanup:
57+
qk_circuit_free(qc);
58+
qk_target_free(target);
59+
return result;
60+
}
61+
62+
int test_commutative_cancellation_no_target(void) {
63+
int result = Ok;
64+
65+
QkCircuit *qc = qk_circuit_new(2, 0);
66+
uint32_t cx_qargs[2] = {
67+
0,
68+
1,
69+
};
70+
uint32_t rz_qargs[1] = {
71+
0,
72+
};
73+
double rz_params[1] = {
74+
3.14159,
75+
};
76+
qk_circuit_gate(qc, QkGate_CX, cx_qargs, NULL);
77+
qk_circuit_gate(qc, QkGate_RZ, rz_qargs, rz_params);
78+
qk_circuit_gate(qc, QkGate_CX, cx_qargs, NULL);
79+
80+
result = qk_transpiler_pass_standalone_commutative_cancellation(qc, NULL, 1.0);
81+
if (result != 0) {
82+
printf("Running the pass failed");
83+
goto cleanup;
84+
}
85+
86+
if (qk_circuit_num_instructions(qc) != 1) {
87+
result = EqualityError;
88+
printf("The gates weren't removed by this circuit");
89+
}
90+
cleanup:
91+
qk_circuit_free(qc);
92+
return result;
93+
}
94+
95+
int test_commutative_cancellation(void) {
96+
int num_failed = 0;
97+
num_failed += RUN_TEST(test_commutative_cancellation_target);
98+
num_failed += RUN_TEST(test_commutative_cancellation_no_target);
99+
100+
fflush(stderr);
101+
fprintf(stderr, "=== Number of failed subtests: %i\n", num_failed);
102+
103+
return num_failed;
104+
}

0 commit comments

Comments
 (0)