diff --git a/crates/cext/src/transpiler/angle_bound_registry.rs b/crates/cext/src/transpiler/angle_bound_registry.rs new file mode 100644 index 000000000000..076a699be7da --- /dev/null +++ b/crates/cext/src/transpiler/angle_bound_registry.rs @@ -0,0 +1,88 @@ +#![allow(clippy::missing_safety_doc)] + +use std::ffi::CStr; +use std::os::raw::{c_char, c_int}; + +use crate::pointers::const_ptr_as_ref; + +use qiskit_circuit::PhysicalQubit; +use qiskit_circuit::dag_circuit::DAGCircuit; +use qiskit_transpiler::angle_bound_registry::WrapAngleRegistry; + +/// Return codes: +/// 0 -> success (a DAG was returned in out_dag) +/// 1 -> not found (no wrapper for this name) +/// -1 -> error while executing wrapper (exception) +#[unsafe(no_mangle)] +#[cfg(feature = "cbinding")] +pub unsafe extern "C" fn qk_wrap_angle_registry_substitute( + reg: *const WrapAngleRegistry, + name: *const c_char, + angles: *const f64, + num_angles: u32, + qubits: *const u32, + num_qubits: u32, + out_dag: *mut *mut DAGCircuit, +) -> c_int { + if reg.is_null() || name.is_null() || out_dag.is_null() { + return -1; + } + + let reg = unsafe { const_ptr_as_ref(reg) }; + + let cstr = unsafe { CStr::from_ptr(name) }; + let name_str = match cstr.to_str() { + Ok(s) => s, + Err(_) => return -1, + }; + + let angles_slice: &[f64] = if angles.is_null() || num_angles == 0 { + &[] + } else { + unsafe { std::slice::from_raw_parts(angles, num_angles as usize) } + }; + + let qubits_slice: Vec = if qubits.is_null() || num_qubits == 0 { + Vec::new() + } else { + let raw = unsafe { std::slice::from_raw_parts(qubits, num_qubits as usize) }; + raw.iter().map(|&x| PhysicalQubit(x)).collect() + }; + + match reg.substitute_angle_bounds(name_str, angles_slice, &qubits_slice) { + Ok(opt_dag) => { + if let Some(dag) = opt_dag { + let boxed = Box::new(dag); + let raw = Box::into_raw(boxed); + unsafe { + *out_dag = raw; + } + 0 + } else { + 1 + } + } + Err(py_err) => { + let _ = py_err; + -1 + } + } +} + +/// Create a new WrapAngleRegistry and return pointer to it. +#[unsafe(no_mangle)] +#[cfg(feature = "cbinding")] +pub extern "C" fn qk_wrap_angle_registry_new() -> *mut WrapAngleRegistry { + Box::into_raw(Box::new(WrapAngleRegistry::new())) +} + +#[unsafe(no_mangle)] +#[cfg(feature = "cbinding")] +pub unsafe extern "C" fn qk_wrap_angle_registry_free(reg: *mut WrapAngleRegistry) { + if reg.is_null() { + return; + } + unsafe { + let _ = Box::from_raw(reg); + }; +} diff --git a/crates/cext/src/transpiler/mod.rs b/crates/cext/src/transpiler/mod.rs index 8de02879c09c..724a02a5752f 100644 --- a/crates/cext/src/transpiler/mod.rs +++ b/crates/cext/src/transpiler/mod.rs @@ -10,6 +10,7 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +pub mod angle_bound_registry; pub mod neighbors; pub mod passes; pub mod target; diff --git a/crates/cext/src/transpiler/passes/mod.rs b/crates/cext/src/transpiler/passes/mod.rs index b18bc2b7bccb..470eba9752d1 100644 --- a/crates/cext/src/transpiler/passes/mod.rs +++ b/crates/cext/src/transpiler/passes/mod.rs @@ -23,3 +23,4 @@ pub mod sabre_layout; pub mod split_2q_unitaries; pub mod unitary_synthesis; pub mod vf2; +pub mod wrap_angles; diff --git a/crates/cext/src/transpiler/passes/wrap_angles.rs b/crates/cext/src/transpiler/passes/wrap_angles.rs new file mode 100644 index 000000000000..f1b0156a4192 --- /dev/null +++ b/crates/cext/src/transpiler/passes/wrap_angles.rs @@ -0,0 +1,59 @@ +use crate::pointers::{const_ptr_as_ref, mut_ptr_as_ref}; + +use qiskit_circuit::circuit_data::CircuitData; +use qiskit_circuit::converters::dag_to_circuit; +use qiskit_circuit::dag_circuit::DAGCircuit; + +use qiskit_transpiler::angle_bound_registry::WrapAngleRegistry; +use qiskit_transpiler::passes::run_wrap_angles; +use qiskit_transpiler::target::Target; + +/// Run the WrapAngles transpiler pass on a circuit. +/// +/// This pass applies angle-bounds substitutions from a target's angle bounds using the provided +/// wrap-angle registry. The pass will scan the circuit and replace gates that violate the target's +/// angle bounds using the registry substitution callbacks. The function modifies `circuit` in place. +/// +/// @param circuit A pointer to the circuit to run WrapAngles on. The circuit is changed in-place if +/// substitutions are performed. In case of modifications the original circuit's allocations will be +/// replaced by the converted circuit produced from the modified DAG. +/// +/// @param target A pointer to a `Target` describing hardware constraints (angle bounds). +/// +/// @param bounds_registry A pointer to a `WrapAngleRegistry` which provides substitution callbacks. +/// +/// @return 0 on success; negative values indicate an error. +/// # Safety +/// - `circuit`, `target`, and `bounds_registry` must be valid, non-null, and properly aligned. +/// - `circuit` must point to a valid `CircuitData` instance that can be safely mutated. +/// - Behavior is undefined if the pointers passed are invalid or not properly aligned. +#[unsafe(no_mangle)] +#[cfg(feature = "cbinding")] +pub unsafe extern "C" fn qk_transpiler_pass_standalone_wrap_angles( + circuit: *mut CircuitData, + target: *const Target, + bounds_registry: *const WrapAngleRegistry, +) -> i32 { + let circuit = unsafe { mut_ptr_as_ref(circuit) }; + let target = unsafe { const_ptr_as_ref(target) }; + let registry = unsafe { const_ptr_as_ref(bounds_registry) }; + + // Convert circuit to DAG + let mut dag = match DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) { + Ok(d) => d, + Err(_) => return -4, + }; + + // Run the pass; run_wrap_angles returns a PyResult<()>, so map errors to error code. + if run_wrap_angles(&mut dag, target, registry).is_err() { + return -5; + } + + let out_circuit = match dag_to_circuit(&dag, false) { + Ok(c) => c, + Err(_) => return -6, + }; + + *circuit = out_circuit; + 0 +} \ No newline at end of file diff --git a/test/c/test_angle_bounds.c b/test/c/test_angle_bounds.c new file mode 100644 index 000000000000..bb1aa8031c39 --- /dev/null +++ b/test/c/test_angle_bounds.c @@ -0,0 +1,47 @@ +#include "common.h" +#include +#include +#include + +static int test_basic_wrapangles_linkage(void) { + printf("Running basic WrapAngles linkage test...\n"); + + QkCircuit *qc = qk_circuit_new(0, 0); + + QkTarget *target = qk_target_new(0); + if (!target) { + fprintf(stderr, "Failed to allocate Target\n"); + qk_circuit_free(qc); + return 1; + } + + QkWrapAngleRegistry* registry = qk_wrap_angle_registry_new(); + if (!registry) { + fprintf(stderr, "Failed to allocate WrapAngleRegistry\n"); + qk_target_free(target); + qk_circuit_free(qc); + return 1; + } + + int rc = qk_transpiler_pass_standalone_wrap_angles(qc, target, registry); + printf("Return code: %d\n", rc); + + qk_wrap_angle_registry_free(registry); + qk_target_free(target); + qk_circuit_free(qc); + + if (rc != 0) { + fprintf(stderr, "Expected 0, got %d\n", rc); + return 1; + } + + return Ok; +} + +int test_angle_bounds(void) { + int num_failed = 0; + num_failed += RUN_TEST(test_basic_wrapangles_linkage); + fprintf(stderr, "=== Number of failed subtests: %i\n", num_failed); + fflush(stderr); + return num_failed; +}