From 09c0b7c6f9500b81f816692afe9712c626c92eff Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 8 Jul 2025 18:07:37 -0400 Subject: [PATCH 01/20] Add SabreLayout to the C API This commit adds a standalone transpiler pass function to the C API for running the SabreLayout transpiler pass. This function returns a new result type QkSabreLayoutResult that contains the output circuit along with the layouts from running the pass. Fixes #14449 --- crates/cext/cbindgen.toml | 1 + crates/cext/src/transpiler/passes/mod.rs | 1 + .../src/transpiler/passes/sabre_layout.rs | 256 ++++++++++++++++++ crates/transpiler/src/passes/sabre/mod.rs | 4 +- docs/cdoc/index.h | 4 + docs/cdoc/index.rst | 1 + docs/cdoc/qk-sabre-layout-result.rst | 17 ++ test/c/test_sabre_layout.c | 236 ++++++++++++++++ 8 files changed, 519 insertions(+), 1 deletion(-) create mode 100644 crates/cext/src/transpiler/passes/sabre_layout.rs create mode 100644 docs/cdoc/qk-sabre-layout-result.rst create mode 100644 test/c/test_sabre_layout.c diff --git a/crates/cext/cbindgen.toml b/crates/cext/cbindgen.toml index e8c7aa62d1fe..b261a045e6fc 100644 --- a/crates/cext/cbindgen.toml +++ b/crates/cext/cbindgen.toml @@ -54,3 +54,4 @@ prefix_with_name = true "TargetEntry" = "QkTargetEntry" "VF2LayoutResult" = "QkVF2LayoutResult" "ElidePermutationsResult" = "QkElidePermutationsResult" +"SabreLayoutResult" = "QkSabreLayoutResult" diff --git a/crates/cext/src/transpiler/passes/mod.rs b/crates/cext/src/transpiler/passes/mod.rs index aea5b6c29d5a..ca4cbfe05d4d 100644 --- a/crates/cext/src/transpiler/passes/mod.rs +++ b/crates/cext/src/transpiler/passes/mod.rs @@ -1,4 +1,5 @@ pub mod elide_permutations; pub mod remove_diagonal_gates_before_measure; pub mod remove_identity_equiv; +pub mod sabre_layout; pub mod vf2; diff --git a/crates/cext/src/transpiler/passes/sabre_layout.rs b/crates/cext/src/transpiler/passes/sabre_layout.rs new file mode 100644 index 000000000000..49a50f9d4f2e --- /dev/null +++ b/crates/cext/src/transpiler/passes/sabre_layout.rs @@ -0,0 +1,256 @@ +// (C) Copyright IBM 2025 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +use crate::pointers::const_ptr_as_ref; + +use qiskit_circuit::circuit_data::CircuitData; +use qiskit_circuit::converters::dag_to_circuit; +use qiskit_circuit::dag_circuit::DAGCircuit; +use qiskit_circuit::nlayout::NLayout; +use qiskit_circuit::VirtualQubit; +use qiskit_transpiler::passes::sabre::heuristic; +use qiskit_transpiler::passes::sabre::sabre_layout_and_routing; +use qiskit_transpiler::target::Target; + +/// The result from ``qk_transpiler_pass_standalone_sabre_layout()`` +pub struct SabreLayoutResult { + circuit: CircuitData, + initial_layout: NLayout, + final_layout: NLayout, +} + +/// @ingroup QkSabreLayoutResult +/// Get the circuit from sabre layout result +/// +/// @param result a pointer to the result of the pass. +/// +/// @returns A pointer to the output circuit +/// +/// # Safety +/// +/// Behavior is undefined if ``result`` is not a valid, non-null pointer to a +/// ``QkSabreLayoutResult``. The pointer to the returned circuit is owned by +/// the result object, it should not be passed to ``qk_circuit_free()`` as it +/// will be freed by ``qk_sabre_layout_result_free()``. +#[no_mangle] +#[cfg(feature = "cbinding")] +pub unsafe extern "C" fn qk_sabre_layout_result_circuit( + result: *const SabreLayoutResult, +) -> *const CircuitData { + let result = unsafe { const_ptr_as_ref(result) }; + (&result.circuit) as *const _ +} + +/// @ingroup QkSabreLayoutResult +/// Get the number of qubits in the initial layout +/// +/// @param result a pointer to the result +/// +/// @returns The number of qubits in the initial layout +/// +/// # Safety +/// +/// Behavior is undefined if ``result`` is not a valid, non-null pointer to a +/// ``QkSabreLayoutResult``. +#[no_mangle] +#[cfg(feature = "cbinding")] +pub unsafe extern "C" fn qk_sabre_layout_result_initial_layout_num_qubits( + result: *const SabreLayoutResult, +) -> u32 { + let result = unsafe { const_ptr_as_ref(result) }; + result.initial_layout.num_qubits() as u32 +} + +/// @ingroup QkSabreLayoutResult +/// Get the physical qubit for a given virtual qubit from the initial layout +/// +/// @param result a pointer to the result +/// @param qubit the virtual qubit to get the physical qubit. This must +/// be a valid qubit in the circuit +/// +/// @returns The physical qubit mapped to in the initial layout +/// +/// # Safety +/// +/// Behavior is undefined if ``result`` is not a valid, non-null pointer to a +/// ``QkSabreLayoutResult``. +#[no_mangle] +#[cfg(feature = "cbinding")] +pub unsafe extern "C" fn qk_sabre_layout_result_map_virtual_qubit_initial_layout( + result: *const SabreLayoutResult, + qubit: u32, +) -> u32 { + let result = unsafe { const_ptr_as_ref(result) }; + result + .initial_layout + .virtual_to_physical(VirtualQubit::new(qubit)) + .0 +} + +/// @ingroup QkSabreLayoutResult +/// Get the number of qubits in the final layout +/// +/// @param result a pointer to the result +/// +/// @returns The number of qubits in the final layout +/// +/// # Safety +/// +/// Behavior is undefined if ``result`` is not a valid, non-null pointer to a +/// ``QkSabreLayoutResult``. +#[no_mangle] +#[cfg(feature = "cbinding")] +pub unsafe extern "C" fn qk_sabre_layout_result_final_layout_num_qubits( + result: *const SabreLayoutResult, +) -> u32 { + let result = unsafe { const_ptr_as_ref(result) }; + result.final_layout.num_qubits() as u32 +} + +/// @ingroup QkSabreLayoutResult +/// Get the output position for a qubit in the circuit from the final layout +/// +/// @param result a pointer to the result +/// @param qubit the qubit to get the final position of after the permutations +/// from the inserted swaps in the circuit. This must be a valid qubit in the circuit +/// +/// @returns The final position of the qubit mapped in the final layout +/// +/// # Safety +/// +/// Behavior is undefined if ``result`` is not a valid, non-null pointer to a +/// ``QkSabreLayoutResult``. +#[no_mangle] +#[cfg(feature = "cbinding")] +pub unsafe extern "C" fn qk_sabre_layout_result_map_qubit_final_layout( + result: *const SabreLayoutResult, + qubit: u32, +) -> u32 { + let result = unsafe { const_ptr_as_ref(result) }; + result + .final_layout + .virtual_to_physical(VirtualQubit::new(qubit)) + .0 +} + +/// @ingroup QkSabreLayoutResult +/// Free a ``QkSabreLayoutResult`` object +/// +/// @param result a pointer to the result to free +/// +/// # Safety +/// +/// Behavior is undefined if ``layout`` is not a valid, non-null pointer to a +/// ``QkSabreLayoutResult``. +#[no_mangle] +#[cfg(feature = "cbinding")] +pub unsafe extern "C" fn qk_sabre_layout_result_free(result: *mut SabreLayoutResult) { + if !result.is_null() { + if !result.is_aligned() { + panic!("Attempted to free a non-aligned pointer.") + } + // SAFETY: We have verified the pointer is non-null and aligned, so + // it should be readable by Box. + unsafe { + let _ = Box::from_raw(result); + } + } +} + +/// @ingroup QkTranspilerPasses +/// Run the SabreLayout transpiler pass on a circuit. +/// +/// The SabreLayout pass chooses a layout via an iterative bidirectional routing of the input +/// circuit. +/// +/// Starting with a random initial Layout, the algorithm does a full routing of the circuit to end up with a final_layout. +/// This final_layout is then used as the initial_layout for routing the reverse circuit. The algorithm iterates a number +/// of times until it finds an initial_layout that reduces full routing cost. +/// +/// This method exploits the reversibility of quantum circuits, and tries to include global circuit information in the +/// choice of initial_layout. +/// +/// This pass will run both layout and routing and will transform the circuit so that the layout is applied to the input +/// (meaning that the output circuit will have ancilla qubits allocated for unused qubits on the coupling map and the +/// qubits will be reordered to match the mapped physical qubits) and then routing will be applied. This is done because +/// the pass will run parallel seed trials with different random seeds for selecting the random initial layout and then +/// selecting the routed output which results in the least number of swap gates needed. This final +/// swap calculation is the same as performing a final routing, so it's more efficient to apply it +/// after computing it. +/// +/// # References +/// +/// [1] Henry Zou and Matthew Treinish and Kevin Hartman and Alexander Ivrii and Jake Lishman. +/// "LightSABRE: A Lightweight and Enhanced SABRE Algorithm" +/// [arXiv:2409.08368](https://doi.org/10.48550/arXiv.2409.08368) +/// +/// [2] Li, Gushu, Yufei Ding, and Yuan Xie. "Tackling the qubit mapping problem +/// for NISQ-era quantum devices." ASPLOS 2019. +/// [`arXiv:1809.02573](https://arxiv.org/pdf/1809.02573.pdf) +/// +/// @param circuit A pointer to the circuit to run SabreLayout on +/// +/// @return QkElidePermutationsResult object that contains the results of the pass +/// +/// # Safety +/// +/// Behavior is undefined if ``circuit`` or ``target`` is not a valid, non-null pointer to a ``QkCircuit`` and ``QkTarget``. +#[no_mangle] +#[cfg(feature = "cbinding")] +pub unsafe extern "C" fn qk_transpiler_pass_standalone_sabre_layout( + circuit: *const CircuitData, + target: *const Target, + max_iterations: usize, + num_swap_trials: usize, + num_random_trials: usize, + seed: i64, +) -> *mut SabreLayoutResult { + // SAFETY: Per documentation, the pointer is non-null and aligned. + let circuit = unsafe { const_ptr_as_ref(circuit) }; + let target = unsafe { const_ptr_as_ref(target) }; + let mut dag = match DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) { + Ok(dag) => dag, + Err(e) => panic!("{}", e), + }; + let seed = if seed < 0 { None } else { Some(seed as u64) }; + let heuristic = heuristic::Heuristic::new( + Some(heuristic::BasicHeuristic::new( + 1.0, + heuristic::SetScaling::Constant, + )), + Some(heuristic::LookaheadHeuristic::new( + 0.5, + 20, + heuristic::SetScaling::Size, + )), + Some(heuristic::DecayHeuristic::new(0.001, 5)), + Some(10 * target.num_qubits.unwrap() as usize), + 1e-10, + ); + let (result, initial_layout, final_layout) = match sabre_layout_and_routing( + &mut dag, + target, + &heuristic, + max_iterations, + num_swap_trials, + num_random_trials, + seed, + Vec::new(), + false, + ) { + Ok(result) => result, + Err(e) => panic!("{}", e), + }; + Box::into_raw(Box::new(SabreLayoutResult { + circuit: dag_to_circuit(&result, false).expect("Conversion to output circuit failed"), + initial_layout, + final_layout, + })) +} diff --git a/crates/transpiler/src/passes/sabre/mod.rs b/crates/transpiler/src/passes/sabre/mod.rs index 8a6ba2241df3..e3e0f8c82cd9 100644 --- a/crates/transpiler/src/passes/sabre/mod.rs +++ b/crates/transpiler/src/passes/sabre/mod.rs @@ -12,7 +12,7 @@ mod dag; mod distance; -mod heuristic; +pub mod heuristic; mod layer; mod layout; mod neighbors; @@ -21,6 +21,8 @@ mod route; use pyo3::prelude::*; use pyo3::wrap_pyfunction; +pub use layout::sabre_layout_and_routing; + pub fn sabre(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(route::sabre_routing))?; m.add_wrapped(wrap_pyfunction!(layout::sabre_layout_and_routing))?; diff --git a/docs/cdoc/index.h b/docs/cdoc/index.h index 2c9694341901..4041dbc7d4a4 100644 --- a/docs/cdoc/index.h +++ b/docs/cdoc/index.h @@ -45,3 +45,7 @@ /** * @defgroup QkElidePermutationsResult QkElidePermutationsResult */ + +/** + * @defgroup QkSabreLayoutResult QkSabreLayoutResult + */ diff --git a/docs/cdoc/index.rst b/docs/cdoc/index.rst index 7532be16d5b7..505bbcfddc45 100644 --- a/docs/cdoc/index.rst +++ b/docs/cdoc/index.rst @@ -66,3 +66,4 @@ results transpiling circuits from Python via the C API. qk-transpiler-passes qk-vf2-layout-result qk-elide-permutations-result + qk-sabre-layout-result diff --git a/docs/cdoc/qk-sabre-layout-result.rst b/docs/cdoc/qk-sabre-layout-result.rst new file mode 100644 index 000000000000..6b79f9c15dbf --- /dev/null +++ b/docs/cdoc/qk-sabre-layout-result.rst @@ -0,0 +1,17 @@ +=================== +QkSabreLayoutResult +=================== + +.. code-block:: c + typedef struct QkSabreLayoutResult QkSabreLayoutResult + +When running the ``qk_transpiler_pass_standalone_sabre_layout`` function it returns a +modified circuit and the initial and final layout from running the pass. This object +contains the outcome of the transpiler pass. + +Functions +========= + +.. doxygengroup:: QkSabreLayoutResult + :members: + :content-only: diff --git a/test/c/test_sabre_layout.c b/test/c/test_sabre_layout.c new file mode 100644 index 000000000000..fa95a9ce9925 --- /dev/null +++ b/test/c/test_sabre_layout.c @@ -0,0 +1,236 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2025. +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +#include "common.h" +#include +#include +#include +#include +#include +#include +#include +#include + +bool compare_circuits(QkCircuit *res, QkCircuit *expected) { + if (qk_circuit_num_instructions(res) != qk_circuit_num_instructions(expected)) { + printf("Number of instructions in circuit is mismatched"); + return false; + } + QkCircuitInstruction *res_inst = malloc(sizeof(QkCircuitInstruction)); + QkCircuitInstruction *expected_inst = malloc(sizeof(QkCircuitInstruction)); + for (size_t i = 0; i < qk_circuit_num_instructions(res); i++) { + qk_circuit_get_instruction(res, i, res_inst); + qk_circuit_get_instruction(expected, i, expected_inst); + int result = strcmp(res_inst->name, expected_inst->name); + if (result != 0) { + printf("Gate %d have different gates %s was found and expected %s", i, res_inst->name, + expected_inst->name); + qk_circuit_instruction_clear(res_inst); + qk_circuit_instruction_clear(expected_inst); + free(res_inst); + free(expected_inst); + return false; + } + if (res_inst->num_qubits != expected_inst->num_qubits) { + printf("Gate %d have different number of qubits %d was found and expected %d", i, + res_inst->num_qubits, expected_inst->num_qubits); + qk_circuit_instruction_clear(res_inst); + qk_circuit_instruction_clear(expected_inst); + free(res_inst); + free(expected_inst); + return false; + } + for (uint32_t j = 0; j < res_inst->num_qubits; j++) { + if (res_inst->qubits[j] != expected_inst->qubits[j]) { + printf("Qubit %d for gate %d are different %d was found and expected %d", j, i, + res_inst->qubits[j] != expected_inst->qubits[j]); + qk_circuit_instruction_clear(res_inst); + qk_circuit_instruction_clear(expected_inst); + free(res_inst); + free(expected_inst); + return false; + } + } + if (res_inst->num_clbits != expected_inst->num_clbits) { + printf("Gate %d have different number of clbits %d was found and expected %d", i, + res_inst->num_clbits, expected_inst->num_clbits); + qk_circuit_instruction_clear(res_inst); + qk_circuit_instruction_clear(expected_inst); + free(res_inst); + free(expected_inst); + return false; + } + for (uint32_t j = 0; j < res_inst->num_clbits; j++) { + if (res_inst->clbits[j] != expected_inst->clbits[j]) { + printf("Clbit %d for gate %d are different %d was found and expected %d", j, i, + res_inst->clbits[j] != expected_inst->clbits[j]); + qk_circuit_instruction_clear(res_inst); + qk_circuit_instruction_clear(expected_inst); + free(res_inst); + free(expected_inst); + return false; + } + } + if (res_inst->num_params != expected_inst->num_params) { + printf("Gate %d have different number of params %d was found and expected %d", i, + res_inst->num_params, expected_inst->num_params); + qk_circuit_instruction_clear(res_inst); + qk_circuit_instruction_clear(expected_inst); + free(res_inst); + free(expected_inst); + return false; + } + for (uint32_t j = 0; j < res_inst->num_params; j++) { + if (res_inst->params[j] != expected_inst->params[j]) { + printf("Parameter %d for gate %d are different %d was found and expected %d", j, i, + res_inst->params[j] != expected_inst->params[j]); + qk_circuit_instruction_clear(res_inst); + qk_circuit_instruction_clear(expected_inst); + free(res_inst); + free(expected_inst); + return false; + } + } + qk_circuit_instruction_clear(res_inst); + qk_circuit_instruction_clear(expected_inst); + } + free(res_inst); + free(expected_inst); + return true; +} + +/** + * Test running sabre layout that performs no transformation. + */ +int test_sabre_layout_no_change(void) { + const uint32_t num_qubits = 5; + // Let's create a target with one qubit for now + QkTarget *target = qk_target_new(num_qubits); + int result = Ok; + + // Add an X Gate. + // This operation is global, no property map is provided + QkExitCode result_x = qk_target_add_instruction(target, qk_target_entry_new(QkGate_X)); + if (result_x != QkExitCode_Success) { + printf("Unexpected error occurred when adding a global X gate."); + result = EqualityError; + goto cleanup; + } + + // Re-add same gate, check if it fails + QkExitCode result_x_readded = qk_target_add_instruction(target, qk_target_entry_new(QkGate_X)); + if (result_x_readded != QkExitCode_TargetInstAlreadyExists) { + printf("The addition of a repeated gate did not fail as expected."); + result = EqualityError; + goto cleanup; + } + + // Number of qubits of the target should not change. + uint32_t current_num_qubits = qk_target_num_qubits(target); + + size_t current_size = qk_target_num_instructions(target); + if (current_size != 1) { + printf("The size of this target is not correct: Expected 1, got %zu", current_size); + result = EqualityError; + goto cleanup; + } + + // Add a CX Gate. + // Create prop_map for the instruction + // Add property for (0, 1) + QkTargetEntry *cx_entry = qk_target_entry_new(QkGate_CX); + for (uint32_t i = 0; i < current_num_qubits - 1; i++) { + uint32_t qargs[2] = {i, i + 1}; + double inst_error = 0.0090393 * (current_num_qubits - i); + double inst_duration = 0.020039; + + QkExitCode result_cx_props = + qk_target_entry_add_property(cx_entry, qargs, 2, inst_duration, inst_error); + if (result_cx_props != QkExitCode_Success) { + printf("Unexpected error occurred when adding property to a CX gate entry."); + result = EqualityError; + goto cleanup; + } + } + + QkExitCode result_cx = qk_target_add_instruction(target, cx_entry); + if (result_cx != QkExitCode_Success) { + printf("Unexpected error occurred when adding a CX gate."); + result = EqualityError; + goto cleanup; + } + + QkCircuit *qc = qk_circuit_new(5, 0); + for (uint32_t i = 0; i < qk_circuit_num_qubits(qc) - 1; i++) { + uint32_t qargs[2] = {i, i + 1}; + for (uint32_t j = 0; j < i + 1; j++) { + qk_circuit_gate(qc, QkGate_CX, qargs, NULL); + } + } + QkSabreLayoutResult *layout_result = + qk_transpiler_pass_standalone_sabre_layout(qc, target, 4, 20, 20, 42); + bool circuit_eq = compare_circuits(qk_sabre_layout_result_circuit(layout_result), qc); + if (!circuit_eq) { + result = EqualityError; + goto layout_cleanup; + } + uint32_t init_layout_size = qk_sabre_layout_result_initial_layout_num_qubits(layout_result); + if (init_layout_size != 5) { + printf("Initial Layout is not the correct size, expected 5 qubit the layout contains %d", + init_layout_size); + result = EqualityError; + goto layout_cleanup; + } + for (uint32_t i = 0; i < init_layout_size; i++) { + uint32_t init_layout = + qk_sabre_layout_result_map_virtual_qubit_initial_layout(layout_result, i); + if (init_layout != i) { + printf("Initial layout maps qubit %d to %d, expected %d instead", i, init_layout, i); + result = EqualityError; + goto layout_cleanup; + } + } + uint32_t final_layout_size = qk_sabre_layout_result_final_layout_num_qubits(layout_result); + if (final_layout_size != 5) { + printf("Final Layout is not the correct size, expected 5 qubit the layout contains %d", + final_layout_size); + result = EqualityError; + goto layout_cleanup; + } + for (uint32_t i = 0; i < final_layout_size; i++) { + uint32_t final_layout = + qk_sabre_layout_result_map_virtual_qubit_initial_layout(layout_result, i); + if (final_layout != i) { + printf("Final layout maps qubit %d to %d, expected %d instead", i, final_layout, i); + result = EqualityError; + goto layout_cleanup; + } + } + +layout_cleanup: + qk_sabre_layout_result_free(layout_result); +circuit_cleanup: + qk_circuit_free(qc); +cleanup: + qk_target_free(target); + return result; +} + +int test_sabre_layout(void) { + int num_failed = 0; + num_failed += RUN_TEST(test_sabre_layout_no_change); + + fflush(stderr); + fprintf(stderr, "=== Number of failed subtests: %i\n", num_failed); + + return num_failed; +} From 9435002589057c67cedc69600406f4691d0ce897 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 9 Jul 2025 11:25:34 -0400 Subject: [PATCH 02/20] Improve testing --- .../src/transpiler/passes/sabre_layout.rs | 8 +- test/c/test_sabre_layout.c | 217 +++++++++++++++++- 2 files changed, 215 insertions(+), 10 deletions(-) diff --git a/crates/cext/src/transpiler/passes/sabre_layout.rs b/crates/cext/src/transpiler/passes/sabre_layout.rs index 49a50f9d4f2e..35f78ab2e679 100644 --- a/crates/cext/src/transpiler/passes/sabre_layout.rs +++ b/crates/cext/src/transpiler/passes/sabre_layout.rs @@ -245,11 +245,15 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_sabre_layout( Vec::new(), false, ) { - Ok(result) => result, + Ok(res) => res, + Err(e) => panic!("{}", e), + }; + let out_circuit = match dag_to_circuit(&result, false) { + Ok(qc) => qc, Err(e) => panic!("{}", e), }; Box::into_raw(Box::new(SabreLayoutResult { - circuit: dag_to_circuit(&result, false).expect("Conversion to output circuit failed"), + circuit: out_circuit, initial_layout, final_layout, })) diff --git a/test/c/test_sabre_layout.c b/test/c/test_sabre_layout.c index fa95a9ce9925..75dd998b12a1 100644 --- a/test/c/test_sabre_layout.c +++ b/test/c/test_sabre_layout.c @@ -20,6 +20,30 @@ #include #include +void print_circuit(QkCircuit *qc) { + size_t num_instructions = qk_circuit_num_instructions(qc); + QkCircuitInstruction *inst = malloc(sizeof(QkCircuitInstruction)); + for (size_t i = 0; i < num_instructions; i++) { + qk_circuit_get_instruction(qc, i, inst); + printf("%s: qubits: (", inst->name); + for (uint32_t j = 0; j < inst->num_qubits; j++) { + printf("%d,", inst->qubits[j]); + } + printf(")"); + uint32_t num_clbits = inst->num_clbits; + if (num_clbits > 0) { + printf(", clbits: ("); + for (uint32_t j = 0; j < num_clbits; j++) { + printf("%d,", inst->clbits[j]); + } + printf(")"); + } + printf("\n"); + qk_circuit_instruction_clear(inst); + } + free(inst); +} + bool compare_circuits(QkCircuit *res, QkCircuit *expected) { if (qk_circuit_num_instructions(res) != qk_circuit_num_instructions(expected)) { printf("Number of instructions in circuit is mismatched"); @@ -51,8 +75,12 @@ bool compare_circuits(QkCircuit *res, QkCircuit *expected) { } for (uint32_t j = 0; j < res_inst->num_qubits; j++) { if (res_inst->qubits[j] != expected_inst->qubits[j]) { - printf("Qubit %d for gate %d are different %d was found and expected %d", j, i, + printf("Qubit %d for gate %d are different %d was found and expected %d\n", j, i, res_inst->qubits[j] != expected_inst->qubits[j]); + printf("Expected circuit instructions:\n"); + print_circuit(expected); + printf("Result circuit:\n"); + print_circuit(res); qk_circuit_instruction_clear(res_inst); qk_circuit_instruction_clear(expected_inst); free(res_inst); @@ -108,10 +136,170 @@ bool compare_circuits(QkCircuit *res, QkCircuit *expected) { return true; } +/** + * Test running sabre layout that requires layout and routing + */ +int test_sabre_layout_applies_layout(void) { + const uint32_t num_qubits = 5; + // Let's create a target with one qubit for now + QkTarget *target = qk_target_new(num_qubits); + int result = Ok; + + // Add an X Gate. + // This operation is global, no property map is provided + QkExitCode result_x = qk_target_add_instruction(target, qk_target_entry_new(QkGate_X)); + if (result_x != QkExitCode_Success) { + printf("Unexpected error occurred when adding a global X gate."); + result = EqualityError; + goto cleanup; + } + + // Re-add same gate, check if it fails + QkExitCode result_x_readded = qk_target_add_instruction(target, qk_target_entry_new(QkGate_X)); + if (result_x_readded != QkExitCode_TargetInstAlreadyExists) { + printf("The addition of a repeated gate did not fail as expected."); + result = EqualityError; + goto cleanup; + } + + // Number of qubits of the target should not change. + uint32_t current_num_qubits = qk_target_num_qubits(target); + + size_t current_size = qk_target_num_instructions(target); + if (current_size != 1) { + printf("The size of this target is not correct: Expected 1, got %zu", current_size); + result = EqualityError; + goto cleanup; + } + + // Add a CX Gate. + // Create prop_map for the instruction + // Add property for (0, 1) + QkTargetEntry *cx_entry = qk_target_entry_new(QkGate_CX); + for (uint32_t i = 0; i < current_num_qubits - 1; i++) { + uint32_t qargs[2] = {i, i + 1}; + double inst_error = 0.0090393 * (current_num_qubits - i); + double inst_duration = 0.020039; + + QkExitCode result_cx_props = + qk_target_entry_add_property(cx_entry, qargs, 2, inst_duration, inst_error); + if (result_cx_props != QkExitCode_Success) { + printf("Unexpected error occurred when adding property to a CX gate entry."); + result = EqualityError; + goto cleanup; + } + } + + QkExitCode result_cx = qk_target_add_instruction(target, cx_entry); + if (result_cx != QkExitCode_Success) { + printf("Unexpected error occurred when adding a CX gate."); + result = EqualityError; + goto cleanup; + } + + QkCircuit *qc = qk_circuit_new(5, 0); + for (uint32_t i = 0; i < qk_circuit_num_qubits(qc) - 1; i++) { + uint32_t qargs[2] = {0, i + 1}; + qk_circuit_gate(qc, QkGate_CX, qargs, NULL); + } + QkSabreLayoutResult *layout_result = + qk_transpiler_pass_standalone_sabre_layout(qc, target, 4, 20, 20, 2025); + QkCircuit *result_circuit = qk_sabre_layout_result_circuit(layout_result); + + QkOpCounts op_counts = qk_circuit_count_ops(result_circuit); + if (op_counts.len != 2) { + printf("More than 2 types of gates in circuit, circuit's instructions are:\n"); + print_circuit(result_circuit); + result = EqualityError; + goto layout_cleanup; + } + for (int i = 0; i < op_counts.len; i++) { + int swap_gate = strcmp(op_counts.data[i].name, "swap"); + int cx_gate = strcmp(op_counts.data[i].name, "cx"); + if (cx_gate != 0 && swap_gate != 0) { + printf("Gate type of %s found in the circuit which isn't expected"); + result = EqualityError; + goto layout_cleanup; + } + if (swap_gate == 0 && op_counts.data[i].count != 2) { + printf("Unexpected number of swaps %d found in the circuit."); + result = EqualityError; + goto layout_cleanup; + } + } + uint32_t expected_initial_layout[5] = {1, 0, 2, 3, 4}; + uint32_t init_layout_size = qk_sabre_layout_result_initial_layout_num_qubits(layout_result); + if (init_layout_size != 5) { + printf("Initial Layout is not the correct size, expected 5 qubit the layout contains %d", + init_layout_size); + result = EqualityError; + goto layout_cleanup; + } + for (uint32_t i = 0; i < init_layout_size; i++) { + uint32_t init_layout = + qk_sabre_layout_result_map_virtual_qubit_initial_layout(layout_result, i); + if (init_layout != expected_initial_layout[i]) { + printf("Initial layout maps qubit %d to %d, expected %d instead", i, init_layout, + expected_initial_layout[i]); + result = EqualityError; + goto layout_cleanup; + } + } + + uint32_t expected_final_layout[5] = {3, 0, 1, 2, 4}; + uint32_t final_layout_size = qk_sabre_layout_result_final_layout_num_qubits(layout_result); + if (final_layout_size != 5) { + printf("Final Layout is not the correct size, expected 5 qubit the layout contains %d", + final_layout_size); + result = EqualityError; + goto layout_cleanup; + } + for (uint32_t i = 0; i < final_layout_size; i++) { + uint32_t final_layout = qk_sabre_layout_result_map_qubit_final_layout(layout_result, i); + if (final_layout != expected_final_layout[i]) { + printf("Final layout maps qubit %d to %d, expected %d instead", i, final_layout, + expected_final_layout[i]); + result = EqualityError; + goto layout_cleanup; + } + } + QkCircuit *expected_circuit = qk_circuit_new(5, 0); + uint32_t qargs[2] = {1, 0}; + qk_circuit_gate(expected_circuit, QkGate_CX, qargs, NULL); + qargs[0] = 1; + qargs[1] = 2; + qk_circuit_gate(expected_circuit, QkGate_CX, qargs, NULL); + qargs[0] = 2; + qargs[1] = 1; + qk_circuit_gate(expected_circuit, QkGate_Swap, qargs, NULL); + qargs[0] = 2; + qargs[1] = 3; + qk_circuit_gate(expected_circuit, QkGate_CX, qargs, NULL); + qargs[0] = 3; + qargs[1] = 2; + qk_circuit_gate(expected_circuit, QkGate_Swap, qargs, NULL); + qargs[0] = 3; + qargs[1] = 4; + qk_circuit_gate(expected_circuit, QkGate_CX, qargs, NULL); + bool compare_result = compare_circuits(result_circuit, expected_circuit); + if (!compare_result) { + result = EqualityError; + goto layout_cleanup; + } + +layout_cleanup: + qk_sabre_layout_result_free(layout_result); +circuit_cleanup: + qk_circuit_free(qc); +cleanup: + qk_target_free(target); + return result; +} + /** * Test running sabre layout that performs no transformation. */ -int test_sabre_layout_no_change(void) { +int test_sabre_layout_no_swap(void) { const uint32_t num_qubits = 5; // Let's create a target with one qubit for now QkTarget *target = qk_target_new(num_qubits); @@ -178,7 +366,15 @@ int test_sabre_layout_no_change(void) { } QkSabreLayoutResult *layout_result = qk_transpiler_pass_standalone_sabre_layout(qc, target, 4, 20, 20, 42); - bool circuit_eq = compare_circuits(qk_sabre_layout_result_circuit(layout_result), qc); + QkCircuit *expected_circuit = qk_circuit_new(5, 0); + for (uint32_t i = 0; i < qk_circuit_num_qubits(qc) - 1; i++) { + uint32_t qargs[2] = {4 - i, 4 - i - 1}; + for (uint32_t j = 0; j < i + 1; j++) { + qk_circuit_gate(expected_circuit, QkGate_CX, qargs, NULL); + } + } + bool circuit_eq = + compare_circuits(qk_sabre_layout_result_circuit(layout_result), expected_circuit); if (!circuit_eq) { result = EqualityError; goto layout_cleanup; @@ -190,11 +386,13 @@ int test_sabre_layout_no_change(void) { result = EqualityError; goto layout_cleanup; } + uint32_t expected_initial_layout[5] = {4, 3, 2, 1, 0}; for (uint32_t i = 0; i < init_layout_size; i++) { uint32_t init_layout = qk_sabre_layout_result_map_virtual_qubit_initial_layout(layout_result, i); - if (init_layout != i) { - printf("Initial layout maps qubit %d to %d, expected %d instead", i, init_layout, i); + if (init_layout != expected_initial_layout[i]) { + printf("Initial layout maps qubit %d to %d, expected %d instead", i, init_layout, + expected_initial_layout[i]); result = EqualityError; goto layout_cleanup; } @@ -206,11 +404,13 @@ int test_sabre_layout_no_change(void) { result = EqualityError; goto layout_cleanup; } + uint32_t expected_final_layout[5] = {4, 3, 2, 1, 0}; for (uint32_t i = 0; i < final_layout_size; i++) { uint32_t final_layout = qk_sabre_layout_result_map_virtual_qubit_initial_layout(layout_result, i); - if (final_layout != i) { - printf("Final layout maps qubit %d to %d, expected %d instead", i, final_layout, i); + if (final_layout != expected_final_layout[i]) { + printf("Final layout maps qubit %d to %d, expected %d instead", i, final_layout, + expected_final_layout[i]); result = EqualityError; goto layout_cleanup; } @@ -227,7 +427,8 @@ int test_sabre_layout_no_change(void) { int test_sabre_layout(void) { int num_failed = 0; - num_failed += RUN_TEST(test_sabre_layout_no_change); + num_failed += RUN_TEST(test_sabre_layout_no_swap); + num_failed += RUN_TEST(test_sabre_layout_applies_layout); fflush(stderr); fprintf(stderr, "=== Number of failed subtests: %i\n", num_failed); From 94b80bc46f8af2dd60aa50b378b62ecad1be29d5 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 18 Aug 2025 15:13:36 -0400 Subject: [PATCH 03/20] Fix tests --- .../src/transpiler/passes/sabre_layout.rs | 10 +- test/c/test_sabre_layout.c | 137 ++++-------------- 2 files changed, 32 insertions(+), 115 deletions(-) diff --git a/crates/cext/src/transpiler/passes/sabre_layout.rs b/crates/cext/src/transpiler/passes/sabre_layout.rs index 35f78ab2e679..01ca3ae4efc9 100644 --- a/crates/cext/src/transpiler/passes/sabre_layout.rs +++ b/crates/cext/src/transpiler/passes/sabre_layout.rs @@ -8,7 +8,7 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use crate::pointers::const_ptr_as_ref; +use crate::pointers::{const_ptr_as_ref, mut_ptr_as_ref}; use qiskit_circuit::circuit_data::CircuitData; use qiskit_circuit::converters::dag_to_circuit; @@ -42,10 +42,10 @@ pub struct SabreLayoutResult { #[no_mangle] #[cfg(feature = "cbinding")] pub unsafe extern "C" fn qk_sabre_layout_result_circuit( - result: *const SabreLayoutResult, -) -> *const CircuitData { - let result = unsafe { const_ptr_as_ref(result) }; - (&result.circuit) as *const _ + result: *mut SabreLayoutResult, +) -> *mut CircuitData { + let result = unsafe { mut_ptr_as_ref(result) }; + (&mut result.circuit) as *mut _ } /// @ingroup QkSabreLayoutResult diff --git a/test/c/test_sabre_layout.c b/test/c/test_sabre_layout.c index 75dd998b12a1..695bc48412ee 100644 --- a/test/c/test_sabre_layout.c +++ b/test/c/test_sabre_layout.c @@ -20,31 +20,30 @@ #include #include -void print_circuit(QkCircuit *qc) { +void print_circuit(const QkCircuit *qc) { size_t num_instructions = qk_circuit_num_instructions(qc); - QkCircuitInstruction *inst = malloc(sizeof(QkCircuitInstruction)); + QkCircuitInstruction inst; for (size_t i = 0; i < num_instructions; i++) { - qk_circuit_get_instruction(qc, i, inst); - printf("%s: qubits: (", inst->name); - for (uint32_t j = 0; j < inst->num_qubits; j++) { - printf("%d,", inst->qubits[j]); + qk_circuit_get_instruction(qc, i, &inst); + printf("%s: qubits: (", inst.name); + for (uint32_t j = 0; j < inst.num_qubits; j++) { + printf("%d,", inst.qubits[j]); } printf(")"); - uint32_t num_clbits = inst->num_clbits; + uint32_t num_clbits = inst.num_clbits; if (num_clbits > 0) { printf(", clbits: ("); for (uint32_t j = 0; j < num_clbits; j++) { - printf("%d,", inst->clbits[j]); + printf("%d,", inst.clbits[j]); } printf(")"); } printf("\n"); - qk_circuit_instruction_clear(inst); + qk_circuit_instruction_clear(&inst); } - free(inst); } -bool compare_circuits(QkCircuit *res, QkCircuit *expected) { +bool compare_circuits(const QkCircuit *res, const QkCircuit *expected) { if (qk_circuit_num_instructions(res) != qk_circuit_num_instructions(expected)) { printf("Number of instructions in circuit is mismatched"); return false; @@ -140,62 +139,21 @@ bool compare_circuits(QkCircuit *res, QkCircuit *expected) { * Test running sabre layout that requires layout and routing */ int test_sabre_layout_applies_layout(void) { - const uint32_t num_qubits = 5; - // Let's create a target with one qubit for now - QkTarget *target = qk_target_new(num_qubits); int result = Ok; - // Add an X Gate. - // This operation is global, no property map is provided - QkExitCode result_x = qk_target_add_instruction(target, qk_target_entry_new(QkGate_X)); - if (result_x != QkExitCode_Success) { - printf("Unexpected error occurred when adding a global X gate."); - result = EqualityError; - goto cleanup; - } - - // Re-add same gate, check if it fails - QkExitCode result_x_readded = qk_target_add_instruction(target, qk_target_entry_new(QkGate_X)); - if (result_x_readded != QkExitCode_TargetInstAlreadyExists) { - printf("The addition of a repeated gate did not fail as expected."); - result = EqualityError; - goto cleanup; - } - - // Number of qubits of the target should not change. - uint32_t current_num_qubits = qk_target_num_qubits(target); - - size_t current_size = qk_target_num_instructions(target); - if (current_size != 1) { - printf("The size of this target is not correct: Expected 1, got %zu", current_size); - result = EqualityError; - goto cleanup; - } + const uint32_t num_qubits = 5; + QkTarget *target = qk_target_new(num_qubits); - // Add a CX Gate. - // Create prop_map for the instruction - // Add property for (0, 1) + qk_target_add_instruction(target, qk_target_entry_new(QkGate_U)); QkTargetEntry *cx_entry = qk_target_entry_new(QkGate_CX); - for (uint32_t i = 0; i < current_num_qubits - 1; i++) { + for (uint32_t i = 0; i < num_qubits - 1; i++) { uint32_t qargs[2] = {i, i + 1}; - double inst_error = 0.0090393 * (current_num_qubits - i); + double inst_error = 0.0090393 * (num_qubits - i); double inst_duration = 0.020039; - - QkExitCode result_cx_props = - qk_target_entry_add_property(cx_entry, qargs, 2, inst_duration, inst_error); - if (result_cx_props != QkExitCode_Success) { - printf("Unexpected error occurred when adding property to a CX gate entry."); - result = EqualityError; - goto cleanup; - } + qk_target_entry_add_property(cx_entry, qargs, 2, inst_duration, inst_error); } - QkExitCode result_cx = qk_target_add_instruction(target, cx_entry); - if (result_cx != QkExitCode_Success) { - printf("Unexpected error occurred when adding a CX gate."); - result = EqualityError; - goto cleanup; - } + qk_target_add_instruction(target, cx_entry); QkCircuit *qc = qk_circuit_new(5, 0); for (uint32_t i = 0; i < qk_circuit_num_qubits(qc) - 1; i++) { @@ -300,62 +258,21 @@ int test_sabre_layout_applies_layout(void) { * Test running sabre layout that performs no transformation. */ int test_sabre_layout_no_swap(void) { - const uint32_t num_qubits = 5; - // Let's create a target with one qubit for now - QkTarget *target = qk_target_new(num_qubits); int result = Ok; - // Add an X Gate. - // This operation is global, no property map is provided - QkExitCode result_x = qk_target_add_instruction(target, qk_target_entry_new(QkGate_X)); - if (result_x != QkExitCode_Success) { - printf("Unexpected error occurred when adding a global X gate."); - result = EqualityError; - goto cleanup; - } - - // Re-add same gate, check if it fails - QkExitCode result_x_readded = qk_target_add_instruction(target, qk_target_entry_new(QkGate_X)); - if (result_x_readded != QkExitCode_TargetInstAlreadyExists) { - printf("The addition of a repeated gate did not fail as expected."); - result = EqualityError; - goto cleanup; - } - - // Number of qubits of the target should not change. - uint32_t current_num_qubits = qk_target_num_qubits(target); - - size_t current_size = qk_target_num_instructions(target); - if (current_size != 1) { - printf("The size of this target is not correct: Expected 1, got %zu", current_size); - result = EqualityError; - goto cleanup; - } - - // Add a CX Gate. - // Create prop_map for the instruction - // Add property for (0, 1) + const uint32_t num_qubits = 5; + QkTarget *target = qk_target_new(num_qubits); + qk_target_add_instruction(target, qk_target_entry_new(QkGate_U)); QkTargetEntry *cx_entry = qk_target_entry_new(QkGate_CX); - for (uint32_t i = 0; i < current_num_qubits - 1; i++) { + for (uint32_t i = 0; i < num_qubits - 1; i++) { uint32_t qargs[2] = {i, i + 1}; - double inst_error = 0.0090393 * (current_num_qubits - i); + double inst_error = 0.0090393 * (num_qubits - i); double inst_duration = 0.020039; - QkExitCode result_cx_props = - qk_target_entry_add_property(cx_entry, qargs, 2, inst_duration, inst_error); - if (result_cx_props != QkExitCode_Success) { - printf("Unexpected error occurred when adding property to a CX gate entry."); - result = EqualityError; - goto cleanup; - } + qk_target_entry_add_property(cx_entry, qargs, 2, inst_duration, inst_error); } - QkExitCode result_cx = qk_target_add_instruction(target, cx_entry); - if (result_cx != QkExitCode_Success) { - printf("Unexpected error occurred when adding a CX gate."); - result = EqualityError; - goto cleanup; - } + qk_target_add_instruction(target, cx_entry); QkCircuit *qc = qk_circuit_new(5, 0); for (uint32_t i = 0; i < qk_circuit_num_qubits(qc) - 1; i++) { @@ -368,7 +285,7 @@ int test_sabre_layout_no_swap(void) { qk_transpiler_pass_standalone_sabre_layout(qc, target, 4, 20, 20, 42); QkCircuit *expected_circuit = qk_circuit_new(5, 0); for (uint32_t i = 0; i < qk_circuit_num_qubits(qc) - 1; i++) { - uint32_t qargs[2] = {4 - i, 4 - i - 1}; + uint32_t qargs[2] = {i, i + 1}; for (uint32_t j = 0; j < i + 1; j++) { qk_circuit_gate(expected_circuit, QkGate_CX, qargs, NULL); } @@ -386,7 +303,7 @@ int test_sabre_layout_no_swap(void) { result = EqualityError; goto layout_cleanup; } - uint32_t expected_initial_layout[5] = {4, 3, 2, 1, 0}; + uint32_t expected_initial_layout[5] = {0, 1, 2, 3, 4}; for (uint32_t i = 0; i < init_layout_size; i++) { uint32_t init_layout = qk_sabre_layout_result_map_virtual_qubit_initial_layout(layout_result, i); @@ -404,7 +321,7 @@ int test_sabre_layout_no_swap(void) { result = EqualityError; goto layout_cleanup; } - uint32_t expected_final_layout[5] = {4, 3, 2, 1, 0}; + uint32_t expected_final_layout[5] = {0, 1, 2, 3, 4}; for (uint32_t i = 0; i < final_layout_size; i++) { uint32_t final_layout = qk_sabre_layout_result_map_virtual_qubit_initial_layout(layout_result, i); From ac2792a69ddea8af3125f2213e4d1cd33a171b68 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 19 Aug 2025 06:57:51 -0400 Subject: [PATCH 04/20] Fix docs --- docs/cdoc/qk-sabre-layout-result.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/cdoc/qk-sabre-layout-result.rst b/docs/cdoc/qk-sabre-layout-result.rst index 6b79f9c15dbf..fc23bdba1831 100644 --- a/docs/cdoc/qk-sabre-layout-result.rst +++ b/docs/cdoc/qk-sabre-layout-result.rst @@ -3,6 +3,7 @@ QkSabreLayoutResult =================== .. code-block:: c + typedef struct QkSabreLayoutResult QkSabreLayoutResult When running the ``qk_transpiler_pass_standalone_sabre_layout`` function it returns a From b26658fe7482d84ce93a63177f6e9c27dcd68e5b Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 19 Aug 2025 07:11:11 -0400 Subject: [PATCH 05/20] Finish API docs for standalone function --- crates/cext/src/transpiler/passes/sabre_layout.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/cext/src/transpiler/passes/sabre_layout.rs b/crates/cext/src/transpiler/passes/sabre_layout.rs index 01ca3ae4efc9..f2fd1d8930e5 100644 --- a/crates/cext/src/transpiler/passes/sabre_layout.rs +++ b/crates/cext/src/transpiler/passes/sabre_layout.rs @@ -185,6 +185,11 @@ pub unsafe extern "C" fn qk_sabre_layout_result_free(result: *mut SabreLayoutRes /// swap calculation is the same as performing a final routing, so it's more efficient to apply it /// after computing it. /// +/// This function is multithreaded and will launch a thread pool with threads equal to the number +/// of CPUs by default. You can tune the number of threads with the ``RAYON_NUM_THREADS`` +/// environment variable. For example, setting ``RAYON_NUM_THREADS=4`` would limit the thread pool +/// to 4 threads. +/// /// # References /// /// [1] Henry Zou and Matthew Treinish and Kevin Hartman and Alexander Ivrii and Jake Lishman. @@ -196,6 +201,16 @@ pub unsafe extern "C" fn qk_sabre_layout_result_free(result: *mut SabreLayoutRes /// [`arXiv:1809.02573](https://arxiv.org/pdf/1809.02573.pdf) /// /// @param circuit A pointer to the circuit to run SabreLayout on +/// @param target A pointer to the target to run SabreLayout on +/// @param max_iterations The number of forward-backward iterations in the +/// sabre routing algorithm +/// @param num_swap_trials The number of trials to run of the sabre routing +/// algorithm for each iteration. When > 1 the trial that routing trial +/// that results in the output with the fewest swap gates will be selected. +/// @param num_random_trials The number of random layout trials to run. The trial +/// that results in the output with the fewest swap gates will be selected. +/// @param seed A seed value for the pRNG used internally. If the value is negative +/// the RNG will be seeded from system entropy. /// /// @return QkElidePermutationsResult object that contains the results of the pass /// From 0aa1f10b3d2d4c84e5f01d9c4eb7d5991e8ffd69 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 19 Aug 2025 18:02:00 -0400 Subject: [PATCH 06/20] Pivot interface to use TranspileLayout This commit reworks the C interface for sabre layout to use TranspileLayout for the return type remove the Sabre result type. It was largely duplicative and it's better to just use the same interface. --- .../src/transpiler/passes/sabre_layout.rs | 195 +++--------------- test/c/test_sabre_layout.c | 93 +++------ 2 files changed, 58 insertions(+), 230 deletions(-) diff --git a/crates/cext/src/transpiler/passes/sabre_layout.rs b/crates/cext/src/transpiler/passes/sabre_layout.rs index f2fd1d8930e5..fec40342adaa 100644 --- a/crates/cext/src/transpiler/passes/sabre_layout.rs +++ b/crates/cext/src/transpiler/passes/sabre_layout.rs @@ -13,156 +13,11 @@ 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_circuit::nlayout::NLayout; -use qiskit_circuit::VirtualQubit; +use qiskit_circuit::Qubit; use qiskit_transpiler::passes::sabre::heuristic; use qiskit_transpiler::passes::sabre::sabre_layout_and_routing; use qiskit_transpiler::target::Target; - -/// The result from ``qk_transpiler_pass_standalone_sabre_layout()`` -pub struct SabreLayoutResult { - circuit: CircuitData, - initial_layout: NLayout, - final_layout: NLayout, -} - -/// @ingroup QkSabreLayoutResult -/// Get the circuit from sabre layout result -/// -/// @param result a pointer to the result of the pass. -/// -/// @returns A pointer to the output circuit -/// -/// # Safety -/// -/// Behavior is undefined if ``result`` is not a valid, non-null pointer to a -/// ``QkSabreLayoutResult``. The pointer to the returned circuit is owned by -/// the result object, it should not be passed to ``qk_circuit_free()`` as it -/// will be freed by ``qk_sabre_layout_result_free()``. -#[no_mangle] -#[cfg(feature = "cbinding")] -pub unsafe extern "C" fn qk_sabre_layout_result_circuit( - result: *mut SabreLayoutResult, -) -> *mut CircuitData { - let result = unsafe { mut_ptr_as_ref(result) }; - (&mut result.circuit) as *mut _ -} - -/// @ingroup QkSabreLayoutResult -/// Get the number of qubits in the initial layout -/// -/// @param result a pointer to the result -/// -/// @returns The number of qubits in the initial layout -/// -/// # Safety -/// -/// Behavior is undefined if ``result`` is not a valid, non-null pointer to a -/// ``QkSabreLayoutResult``. -#[no_mangle] -#[cfg(feature = "cbinding")] -pub unsafe extern "C" fn qk_sabre_layout_result_initial_layout_num_qubits( - result: *const SabreLayoutResult, -) -> u32 { - let result = unsafe { const_ptr_as_ref(result) }; - result.initial_layout.num_qubits() as u32 -} - -/// @ingroup QkSabreLayoutResult -/// Get the physical qubit for a given virtual qubit from the initial layout -/// -/// @param result a pointer to the result -/// @param qubit the virtual qubit to get the physical qubit. This must -/// be a valid qubit in the circuit -/// -/// @returns The physical qubit mapped to in the initial layout -/// -/// # Safety -/// -/// Behavior is undefined if ``result`` is not a valid, non-null pointer to a -/// ``QkSabreLayoutResult``. -#[no_mangle] -#[cfg(feature = "cbinding")] -pub unsafe extern "C" fn qk_sabre_layout_result_map_virtual_qubit_initial_layout( - result: *const SabreLayoutResult, - qubit: u32, -) -> u32 { - let result = unsafe { const_ptr_as_ref(result) }; - result - .initial_layout - .virtual_to_physical(VirtualQubit::new(qubit)) - .0 -} - -/// @ingroup QkSabreLayoutResult -/// Get the number of qubits in the final layout -/// -/// @param result a pointer to the result -/// -/// @returns The number of qubits in the final layout -/// -/// # Safety -/// -/// Behavior is undefined if ``result`` is not a valid, non-null pointer to a -/// ``QkSabreLayoutResult``. -#[no_mangle] -#[cfg(feature = "cbinding")] -pub unsafe extern "C" fn qk_sabre_layout_result_final_layout_num_qubits( - result: *const SabreLayoutResult, -) -> u32 { - let result = unsafe { const_ptr_as_ref(result) }; - result.final_layout.num_qubits() as u32 -} - -/// @ingroup QkSabreLayoutResult -/// Get the output position for a qubit in the circuit from the final layout -/// -/// @param result a pointer to the result -/// @param qubit the qubit to get the final position of after the permutations -/// from the inserted swaps in the circuit. This must be a valid qubit in the circuit -/// -/// @returns The final position of the qubit mapped in the final layout -/// -/// # Safety -/// -/// Behavior is undefined if ``result`` is not a valid, non-null pointer to a -/// ``QkSabreLayoutResult``. -#[no_mangle] -#[cfg(feature = "cbinding")] -pub unsafe extern "C" fn qk_sabre_layout_result_map_qubit_final_layout( - result: *const SabreLayoutResult, - qubit: u32, -) -> u32 { - let result = unsafe { const_ptr_as_ref(result) }; - result - .final_layout - .virtual_to_physical(VirtualQubit::new(qubit)) - .0 -} - -/// @ingroup QkSabreLayoutResult -/// Free a ``QkSabreLayoutResult`` object -/// -/// @param result a pointer to the result to free -/// -/// # Safety -/// -/// Behavior is undefined if ``layout`` is not a valid, non-null pointer to a -/// ``QkSabreLayoutResult``. -#[no_mangle] -#[cfg(feature = "cbinding")] -pub unsafe extern "C" fn qk_sabre_layout_result_free(result: *mut SabreLayoutResult) { - if !result.is_null() { - if !result.is_aligned() { - panic!("Attempted to free a non-aligned pointer.") - } - // SAFETY: We have verified the pointer is non-null and aligned, so - // it should be readable by Box. - unsafe { - let _ = Box::from_raw(result); - } - } -} +use qiskit_transpiler::transpile_layout::TranspileLayout; /// @ingroup QkTranspilerPasses /// Run the SabreLayout transpiler pass on a circuit. @@ -212,7 +67,8 @@ pub unsafe extern "C" fn qk_sabre_layout_result_free(result: *mut SabreLayoutRes /// @param seed A seed value for the pRNG used internally. If the value is negative /// the RNG will be seeded from system entropy. /// -/// @return QkElidePermutationsResult object that contains the results of the pass +/// @return The transpile layout that describes the layout and output permutation caused +/// by the pass /// /// # Safety /// @@ -220,20 +76,18 @@ pub unsafe extern "C" fn qk_sabre_layout_result_free(result: *mut SabreLayoutRes #[no_mangle] #[cfg(feature = "cbinding")] pub unsafe extern "C" fn qk_transpiler_pass_standalone_sabre_layout( - circuit: *const CircuitData, + circuit: *mut CircuitData, target: *const Target, max_iterations: usize, num_swap_trials: usize, num_random_trials: usize, seed: i64, -) -> *mut SabreLayoutResult { +) -> *mut TranspileLayout { // SAFETY: Per documentation, the pointer is non-null and aligned. - let circuit = unsafe { const_ptr_as_ref(circuit) }; + let circuit = unsafe { mut_ptr_as_ref(circuit) }; let target = unsafe { const_ptr_as_ref(target) }; - let mut dag = match DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) { - Ok(dag) => dag, - Err(e) => panic!("{}", e), - }; + let mut dag = DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) + .expect("Internal circuit to DAG conversion failed."); let seed = if seed < 0 { None } else { Some(seed as u64) }; let heuristic = heuristic::Heuristic::new( Some(heuristic::BasicHeuristic::new( @@ -249,7 +103,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_sabre_layout( Some(10 * target.num_qubits.unwrap() as usize), 1e-10, ); - let (result, initial_layout, final_layout) = match sabre_layout_and_routing( + let (result, initial_layout, final_layout) = sabre_layout_and_routing( &mut dag, target, &heuristic, @@ -259,17 +113,20 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_sabre_layout( seed, Vec::new(), false, - ) { - Ok(res) => res, - Err(e) => panic!("{}", e), - }; - let out_circuit = match dag_to_circuit(&result, false) { - Ok(qc) => qc, - Err(e) => panic!("{}", e), - }; - Box::into_raw(Box::new(SabreLayoutResult { - circuit: out_circuit, - initial_layout, - final_layout, - })) + ) + .unwrap(); + let out_circuit = + dag_to_circuit(&result, false).expect("Internal DAG to circuit conversion failed"); + *circuit = out_circuit; + Box::into_raw(Box::new(TranspileLayout::new( + Some(initial_layout), + Some( + final_layout + .iter_virtual() + .map(|(_virt, phys)| Qubit(phys.0)) + .collect(), + ), + result.qubits().objects().clone(), + circuit.num_qubits() as u32, + ))) } diff --git a/test/c/test_sabre_layout.c b/test/c/test_sabre_layout.c index 695bc48412ee..25ab4a058529 100644 --- a/test/c/test_sabre_layout.c +++ b/test/c/test_sabre_layout.c @@ -160,14 +160,13 @@ int test_sabre_layout_applies_layout(void) { uint32_t qargs[2] = {0, i + 1}; qk_circuit_gate(qc, QkGate_CX, qargs, NULL); } - QkSabreLayoutResult *layout_result = + QkTranspileLayout *layout_result = qk_transpiler_pass_standalone_sabre_layout(qc, target, 4, 20, 20, 2025); - QkCircuit *result_circuit = qk_sabre_layout_result_circuit(layout_result); - QkOpCounts op_counts = qk_circuit_count_ops(result_circuit); + QkOpCounts op_counts = qk_circuit_count_ops(qc); if (op_counts.len != 2) { printf("More than 2 types of gates in circuit, circuit's instructions are:\n"); - print_circuit(result_circuit); + print_circuit(qc); result = EqualityError; goto layout_cleanup; } @@ -186,37 +185,24 @@ int test_sabre_layout_applies_layout(void) { } } uint32_t expected_initial_layout[5] = {1, 0, 2, 3, 4}; - uint32_t init_layout_size = qk_sabre_layout_result_initial_layout_num_qubits(layout_result); - if (init_layout_size != 5) { - printf("Initial Layout is not the correct size, expected 5 qubit the layout contains %d", - init_layout_size); - result = EqualityError; - goto layout_cleanup; - } - for (uint32_t i = 0; i < init_layout_size; i++) { - uint32_t init_layout = - qk_sabre_layout_result_map_virtual_qubit_initial_layout(layout_result, i); - if (init_layout != expected_initial_layout[i]) { - printf("Initial layout maps qubit %d to %d, expected %d instead", i, init_layout, - expected_initial_layout[i]); + uint32_t result_initial_layout[5]; + qk_transpile_layout_initial_layout(layout_result, false, result_initial_layout); + for (uint32_t i = 0; i < 5; i++) { + if (result_initial_layout[i] != expected_initial_layout[i]) { + printf("Initial layout maps qubit %d to %d, expected %d instead", i, + result_initial_layout[i], expected_initial_layout[i]); result = EqualityError; goto layout_cleanup; } } uint32_t expected_final_layout[5] = {3, 0, 1, 2, 4}; - uint32_t final_layout_size = qk_sabre_layout_result_final_layout_num_qubits(layout_result); - if (final_layout_size != 5) { - printf("Final Layout is not the correct size, expected 5 qubit the layout contains %d", - final_layout_size); - result = EqualityError; - goto layout_cleanup; - } - for (uint32_t i = 0; i < final_layout_size; i++) { - uint32_t final_layout = qk_sabre_layout_result_map_qubit_final_layout(layout_result, i); - if (final_layout != expected_final_layout[i]) { - printf("Final layout maps qubit %d to %d, expected %d instead", i, final_layout, - expected_final_layout[i]); + uint32_t result_final_layout[5]; + qk_transpile_layout_output_permutation(layout_result, result_final_layout); + for (uint32_t i = 0; i < 5; i++) { + if (result_final_layout[i] != expected_final_layout[i]) { + printf("Final layout maps qubit %d to %d, expected %d instead", i, + result_final_layout[i], expected_final_layout[i]); result = EqualityError; goto layout_cleanup; } @@ -239,14 +225,14 @@ int test_sabre_layout_applies_layout(void) { qargs[0] = 3; qargs[1] = 4; qk_circuit_gate(expected_circuit, QkGate_CX, qargs, NULL); - bool compare_result = compare_circuits(result_circuit, expected_circuit); + bool compare_result = compare_circuits(qc, expected_circuit); if (!compare_result) { result = EqualityError; goto layout_cleanup; } layout_cleanup: - qk_sabre_layout_result_free(layout_result); + qk_transpile_layout_free(layout_result); circuit_cleanup: qk_circuit_free(qc); cleanup: @@ -281,7 +267,7 @@ int test_sabre_layout_no_swap(void) { qk_circuit_gate(qc, QkGate_CX, qargs, NULL); } } - QkSabreLayoutResult *layout_result = + QkTranspileLayout *layout_result = qk_transpiler_pass_standalone_sabre_layout(qc, target, 4, 20, 20, 42); QkCircuit *expected_circuit = qk_circuit_new(5, 0); for (uint32_t i = 0; i < qk_circuit_num_qubits(qc) - 1; i++) { @@ -290,51 +276,36 @@ int test_sabre_layout_no_swap(void) { qk_circuit_gate(expected_circuit, QkGate_CX, qargs, NULL); } } - bool circuit_eq = - compare_circuits(qk_sabre_layout_result_circuit(layout_result), expected_circuit); + bool circuit_eq = compare_circuits(qc, expected_circuit); if (!circuit_eq) { result = EqualityError; goto layout_cleanup; } - uint32_t init_layout_size = qk_sabre_layout_result_initial_layout_num_qubits(layout_result); - if (init_layout_size != 5) { - printf("Initial Layout is not the correct size, expected 5 qubit the layout contains %d", - init_layout_size); - result = EqualityError; - goto layout_cleanup; - } uint32_t expected_initial_layout[5] = {0, 1, 2, 3, 4}; - for (uint32_t i = 0; i < init_layout_size; i++) { - uint32_t init_layout = - qk_sabre_layout_result_map_virtual_qubit_initial_layout(layout_result, i); - if (init_layout != expected_initial_layout[i]) { - printf("Initial layout maps qubit %d to %d, expected %d instead", i, init_layout, - expected_initial_layout[i]); + uint32_t result_initial_layout[5]; + qk_transpile_layout_initial_layout(layout_result, false, result_initial_layout); + for (uint32_t i = 0; i < 5; i++) { + if (result_initial_layout[i] != expected_initial_layout[i]) { + printf("Initial layout maps qubit %d to %d, expected %d instead", i, + result_initial_layout[i], expected_initial_layout[i]); result = EqualityError; goto layout_cleanup; } } - uint32_t final_layout_size = qk_sabre_layout_result_final_layout_num_qubits(layout_result); - if (final_layout_size != 5) { - printf("Final Layout is not the correct size, expected 5 qubit the layout contains %d", - final_layout_size); - result = EqualityError; - goto layout_cleanup; - } uint32_t expected_final_layout[5] = {0, 1, 2, 3, 4}; - for (uint32_t i = 0; i < final_layout_size; i++) { - uint32_t final_layout = - qk_sabre_layout_result_map_virtual_qubit_initial_layout(layout_result, i); - if (final_layout != expected_final_layout[i]) { - printf("Final layout maps qubit %d to %d, expected %d instead", i, final_layout, - expected_final_layout[i]); + uint32_t result_final_layout[5]; + qk_transpile_layout_output_permutation(layout_result, result_final_layout); + for (uint32_t i = 0; i < 5; i++) { + if (result_final_layout[i] != expected_final_layout[i]) { + printf("Final layout maps qubit %d to %d, expected %d instead", i, + result_final_layout[i], expected_final_layout[i]); result = EqualityError; goto layout_cleanup; } } layout_cleanup: - qk_sabre_layout_result_free(layout_result); + qk_transpile_layout_free(layout_result); circuit_cleanup: qk_circuit_free(qc); cleanup: From 05725baf319e99b410e53b1af021d38d430dc3ec Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 19 Aug 2025 19:43:28 -0400 Subject: [PATCH 07/20] Remove stale references to QkSabreLayoutResult in docs --- docs/cdoc/index.rst | 1 - docs/cdoc/qk-sabre-layout-result.rst | 18 ------------------ 2 files changed, 19 deletions(-) delete mode 100644 docs/cdoc/qk-sabre-layout-result.rst diff --git a/docs/cdoc/index.rst b/docs/cdoc/index.rst index 66c19c3b1263..8d7ac0f346b7 100644 --- a/docs/cdoc/index.rst +++ b/docs/cdoc/index.rst @@ -68,4 +68,3 @@ results transpiling circuits from Python via the C API. qk-transpiler-passes qk-vf2-layout-result qk-elide-permutations-result - qk-sabre-layout-result diff --git a/docs/cdoc/qk-sabre-layout-result.rst b/docs/cdoc/qk-sabre-layout-result.rst deleted file mode 100644 index fc23bdba1831..000000000000 --- a/docs/cdoc/qk-sabre-layout-result.rst +++ /dev/null @@ -1,18 +0,0 @@ -=================== -QkSabreLayoutResult -=================== - -.. code-block:: c - - typedef struct QkSabreLayoutResult QkSabreLayoutResult - -When running the ``qk_transpiler_pass_standalone_sabre_layout`` function it returns a -modified circuit and the initial and final layout from running the pass. This object -contains the outcome of the transpiler pass. - -Functions -========= - -.. doxygengroup:: QkSabreLayoutResult - :members: - :content-only: From 6d95b87cbf14fba5993c6dc4390ca68dffd1f83e Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 19 Aug 2025 22:03:26 -0400 Subject: [PATCH 08/20] Fix test memory leaks --- test/c/test_sabre_layout.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/c/test_sabre_layout.c b/test/c/test_sabre_layout.c index 25ab4a058529..efb77f6cc179 100644 --- a/test/c/test_sabre_layout.c +++ b/test/c/test_sabre_layout.c @@ -228,10 +228,11 @@ int test_sabre_layout_applies_layout(void) { bool compare_result = compare_circuits(qc, expected_circuit); if (!compare_result) { result = EqualityError; - goto layout_cleanup; } + qk_circuit_free(expected_circuit); layout_cleanup: + qk_opcounts_free(op_counts); qk_transpile_layout_free(layout_result); circuit_cleanup: qk_circuit_free(qc); @@ -305,6 +306,7 @@ int test_sabre_layout_no_swap(void) { } layout_cleanup: + qk_circuit_free(expected_circuit); qk_transpile_layout_free(layout_result); circuit_cleanup: qk_circuit_free(qc); From dcc672b1767d67aec1eb76312b5eab35bdb0594a Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 20 Aug 2025 08:06:03 -0400 Subject: [PATCH 09/20] Make input to pass function an options struct --- Cargo.lock | 2 + crates/cext/Cargo.toml | 2 + .../src/transpiler/passes/sabre_layout.rs | 59 +++++++++++++------ docs/cdoc/index.h | 4 ++ docs/cdoc/index.rst | 1 + docs/cdoc/qk-sabre-layout-options.rst | 18 ++++++ test/c/test_sabre_layout.c | 10 ++-- 7 files changed, 74 insertions(+), 22 deletions(-) create mode 100644 docs/cdoc/qk-sabre-layout-options.rst diff --git a/Cargo.lock b/Cargo.lock index a6d1c9b8699f..1f30e75e2f6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1500,6 +1500,8 @@ dependencies = [ "qiskit-circuit", "qiskit-quantum-info", "qiskit-transpiler", + "rand 0.9.2", + "rand_pcg 0.9.0", "smallvec", "thiserror 2.0.15", ] diff --git a/crates/cext/Cargo.toml b/crates/cext/Cargo.toml index 2f1e69ecbf68..4112c762c6d7 100644 --- a/crates/cext/Cargo.toml +++ b/crates/cext/Cargo.toml @@ -28,6 +28,8 @@ smallvec.workspace = true ndarray.workspace = true nalgebra.workspace = true hashbrown.workspace = true +rand.workspace = true +rand_pcg.workspace = true [build-dependencies] cbindgen = "0.29" diff --git a/crates/cext/src/transpiler/passes/sabre_layout.rs b/crates/cext/src/transpiler/passes/sabre_layout.rs index fec40342adaa..d3ff810d3215 100644 --- a/crates/cext/src/transpiler/passes/sabre_layout.rs +++ b/crates/cext/src/transpiler/passes/sabre_layout.rs @@ -9,6 +9,8 @@ // that they have been altered from the originals. use crate::pointers::{const_ptr_as_ref, mut_ptr_as_ref}; +use rand::{Rng, SeedableRng}; +use rand_pcg::Pcg64Mcg; use qiskit_circuit::circuit_data::CircuitData; use qiskit_circuit::converters::dag_to_circuit; @@ -19,6 +21,38 @@ use qiskit_transpiler::passes::sabre::sabre_layout_and_routing; use qiskit_transpiler::target::Target; use qiskit_transpiler::transpile_layout::TranspileLayout; +/// The options for running ``qk_transpiler_pass_standalone_sabre_layout``. This struct is used +/// as an input to control the behavior of the +#[repr(C)] +pub struct QkSabreLayoutOptions { + /// The number of forward-backward iterations in the sabre routing algorithm + max_iterations: usize, + /// The number of trials to run of the sabre routing algorithm for each iteration. When > 1 the + /// trial that routing trial that results in the output with the fewest swap gates will be + /// selected. + num_swap_trials: usize, + /// The number of random layout trials to run. The trial that results in the output with the + /// fewest swap gates will be selected. + num_random_trials: usize, + /// A seed value for the pRNG used internally. + seed: u64, +} + +/// @ingroup QkSabreLayoutOptions +/// +/// Build a default sabre layout options object This builds a sabre layout with ``max_iterations`` +/// set to 4, both ``num_swap_trials`` and ``num_random_trials`` set to 20, and the seed selected +/// by a RNG seeded from system entropy. +#[no_mangle] +pub extern "C" fn qk_sabre_layout_options_default() -> QkSabreLayoutOptions { + QkSabreLayoutOptions { + max_iterations: 4, + num_swap_trials: 20, + num_random_trials: 20, + seed: Pcg64Mcg::from_os_rng().random(), + } +} + /// @ingroup QkTranspilerPasses /// Run the SabreLayout transpiler pass on a circuit. /// @@ -57,15 +91,7 @@ use qiskit_transpiler::transpile_layout::TranspileLayout; /// /// @param circuit A pointer to the circuit to run SabreLayout on /// @param target A pointer to the target to run SabreLayout on -/// @param max_iterations The number of forward-backward iterations in the -/// sabre routing algorithm -/// @param num_swap_trials The number of trials to run of the sabre routing -/// algorithm for each iteration. When > 1 the trial that routing trial -/// that results in the output with the fewest swap gates will be selected. -/// @param num_random_trials The number of random layout trials to run. The trial -/// that results in the output with the fewest swap gates will be selected. -/// @param seed A seed value for the pRNG used internally. If the value is negative -/// the RNG will be seeded from system entropy. +/// @param options A pointer to the options for SabreLayout /// /// @return The transpile layout that describes the layout and output permutation caused /// by the pass @@ -78,17 +104,14 @@ use qiskit_transpiler::transpile_layout::TranspileLayout; pub unsafe extern "C" fn qk_transpiler_pass_standalone_sabre_layout( circuit: *mut CircuitData, target: *const Target, - max_iterations: usize, - num_swap_trials: usize, - num_random_trials: usize, - seed: i64, + options: *const QkSabreLayoutOptions, ) -> *mut TranspileLayout { // SAFETY: Per documentation, the pointer is non-null and aligned. let circuit = unsafe { mut_ptr_as_ref(circuit) }; let target = unsafe { const_ptr_as_ref(target) }; + let options = unsafe { const_ptr_as_ref(options) }; let mut dag = DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) .expect("Internal circuit to DAG conversion failed."); - let seed = if seed < 0 { None } else { Some(seed as u64) }; let heuristic = heuristic::Heuristic::new( Some(heuristic::BasicHeuristic::new( 1.0, @@ -107,10 +130,10 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_sabre_layout( &mut dag, target, &heuristic, - max_iterations, - num_swap_trials, - num_random_trials, - seed, + options.max_iterations, + options.num_swap_trials, + options.num_random_trials, + Some(options.seed), Vec::new(), false, ) diff --git a/docs/cdoc/index.h b/docs/cdoc/index.h index 289e22e0c3a6..10654473762d 100644 --- a/docs/cdoc/index.h +++ b/docs/cdoc/index.h @@ -49,3 +49,7 @@ /** * @defgroup QkTranspileLayout QkTranspileLayout */ + +/** + * @defgroup QkSabreLayoutOptions QkSabreLayoutOptions + */ diff --git a/docs/cdoc/index.rst b/docs/cdoc/index.rst index 8d7ac0f346b7..033971521249 100644 --- a/docs/cdoc/index.rst +++ b/docs/cdoc/index.rst @@ -68,3 +68,4 @@ results transpiling circuits from Python via the C API. qk-transpiler-passes qk-vf2-layout-result qk-elide-permutations-result + qk-sabre-layout-options diff --git a/docs/cdoc/qk-sabre-layout-options.rst b/docs/cdoc/qk-sabre-layout-options.rst new file mode 100644 index 000000000000..2002fcb64adf --- /dev/null +++ b/docs/cdoc/qk-sabre-layout-options.rst @@ -0,0 +1,18 @@ +==================== +QkSabreLayoutOptions +==================== + +When running the ``qk_transpiler_pass_standalone_sabre_layout`` function this type defines the options for running the pass. + +Data Types +========== + +.. doxygenstruct:: QkSabreLayoutOptions + :members: + +Functions +========= + +.. doxygengroup:: QkSabreLayoutOptions + :members: + :content-only: diff --git a/test/c/test_sabre_layout.c b/test/c/test_sabre_layout.c index efb77f6cc179..7619b37abdb1 100644 --- a/test/c/test_sabre_layout.c +++ b/test/c/test_sabre_layout.c @@ -160,8 +160,9 @@ int test_sabre_layout_applies_layout(void) { uint32_t qargs[2] = {0, i + 1}; qk_circuit_gate(qc, QkGate_CX, qargs, NULL); } - QkTranspileLayout *layout_result = - qk_transpiler_pass_standalone_sabre_layout(qc, target, 4, 20, 20, 2025); + QkSabreLayoutOptions options = qk_sabre_layout_options_default(); + options.seed = 2025; + QkTranspileLayout *layout_result = qk_transpiler_pass_standalone_sabre_layout(qc, target, &options); QkOpCounts op_counts = qk_circuit_count_ops(qc); if (op_counts.len != 2) { @@ -268,8 +269,9 @@ int test_sabre_layout_no_swap(void) { qk_circuit_gate(qc, QkGate_CX, qargs, NULL); } } - QkTranspileLayout *layout_result = - qk_transpiler_pass_standalone_sabre_layout(qc, target, 4, 20, 20, 42); + QkSabreLayoutOptions options = qk_sabre_layout_options_default(); + options.seed = 2025; + QkTranspileLayout *layout_result = qk_transpiler_pass_standalone_sabre_layout(qc, target, &options); QkCircuit *expected_circuit = qk_circuit_new(5, 0); for (uint32_t i = 0; i < qk_circuit_num_qubits(qc) - 1; i++) { uint32_t qargs[2] = {i, i + 1}; From be8e750e6308e370ff8847ebcc05369ad4fdd272 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 20 Aug 2025 08:53:02 -0400 Subject: [PATCH 10/20] Set number of input qubits correctly in output layout --- crates/cext/src/transpiler/passes/sabre_layout.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/cext/src/transpiler/passes/sabre_layout.rs b/crates/cext/src/transpiler/passes/sabre_layout.rs index d3ff810d3215..c4c816ed7af7 100644 --- a/crates/cext/src/transpiler/passes/sabre_layout.rs +++ b/crates/cext/src/transpiler/passes/sabre_layout.rs @@ -140,6 +140,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_sabre_layout( .unwrap(); let out_circuit = dag_to_circuit(&result, false).expect("Internal DAG to circuit conversion failed"); + let num_input_qubits = circuit.num_qubits() as u32; *circuit = out_circuit; Box::into_raw(Box::new(TranspileLayout::new( Some(initial_layout), @@ -150,6 +151,6 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_sabre_layout( .collect(), ), result.qubits().objects().clone(), - circuit.num_qubits() as u32, + num_input_qubits, ))) } From 6bf9a6e53a3a2ee327b3a86d1148cd2294488e84 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 20 Aug 2025 08:59:39 -0400 Subject: [PATCH 11/20] Move helper functions to common.c --- test/c/common.c | 117 +++++++++++++++++++++++++++++++++++ test/c/common.h | 3 + test/c/test_sabre_layout.c | 121 ++----------------------------------- 3 files changed, 124 insertions(+), 117 deletions(-) diff --git a/test/c/common.c b/test/c/common.c index a0dc33c37e66..c94a76feac14 100644 --- a/test/c/common.c +++ b/test/c/common.c @@ -11,7 +11,9 @@ // that they have been altered from the originals. #include "common.h" +#include #include +#include // A function to run a test function of a given name. This function will also // post-process the returned `TestResult` to product a minimal info message for @@ -36,3 +38,118 @@ int run(const char *name, int (*test_function)(void)) { return did_fail; } + +void print_circuit(const QkCircuit *qc) { + size_t num_instructions = qk_circuit_num_instructions(qc); + QkCircuitInstruction inst; + for (size_t i = 0; i < num_instructions; i++) { + qk_circuit_get_instruction(qc, i, &inst); + printf("%s: qubits: (", inst.name); + for (uint32_t j = 0; j < inst.num_qubits; j++) { + printf("%d,", inst.qubits[j]); + } + printf(")"); + uint32_t num_clbits = inst.num_clbits; + if (num_clbits > 0) { + printf(", clbits: ("); + for (uint32_t j = 0; j < num_clbits; j++) { + printf("%d,", inst.clbits[j]); + } + printf(")"); + } + printf("\n"); + qk_circuit_instruction_clear(&inst); + } +} + +bool compare_circuits(const QkCircuit *res, const QkCircuit *expected) { + if (qk_circuit_num_instructions(res) != qk_circuit_num_instructions(expected)) { + printf("Number of instructions in circuit is mismatched"); + return false; + } + QkCircuitInstruction *res_inst = malloc(sizeof(QkCircuitInstruction)); + QkCircuitInstruction *expected_inst = malloc(sizeof(QkCircuitInstruction)); + for (size_t i = 0; i < qk_circuit_num_instructions(res); i++) { + qk_circuit_get_instruction(res, i, res_inst); + qk_circuit_get_instruction(expected, i, expected_inst); + int result = strcmp(res_inst->name, expected_inst->name); + if (result != 0) { + printf("Gate %d have different gates %s was found and expected %s", i, res_inst->name, + expected_inst->name); + qk_circuit_instruction_clear(res_inst); + qk_circuit_instruction_clear(expected_inst); + free(res_inst); + free(expected_inst); + return false; + } + if (res_inst->num_qubits != expected_inst->num_qubits) { + printf("Gate %d have different number of qubits %d was found and expected %d", i, + res_inst->num_qubits, expected_inst->num_qubits); + qk_circuit_instruction_clear(res_inst); + qk_circuit_instruction_clear(expected_inst); + free(res_inst); + free(expected_inst); + return false; + } + for (uint32_t j = 0; j < res_inst->num_qubits; j++) { + if (res_inst->qubits[j] != expected_inst->qubits[j]) { + printf("Qubit %d for gate %d are different %d was found and expected %d\n", j, i, + res_inst->qubits[j] != expected_inst->qubits[j]); + printf("Expected circuit instructions:\n"); + print_circuit(expected); + printf("Result circuit:\n"); + print_circuit(res); + qk_circuit_instruction_clear(res_inst); + qk_circuit_instruction_clear(expected_inst); + free(res_inst); + free(expected_inst); + return false; + } + } + if (res_inst->num_clbits != expected_inst->num_clbits) { + printf("Gate %d have different number of clbits %d was found and expected %d", i, + res_inst->num_clbits, expected_inst->num_clbits); + qk_circuit_instruction_clear(res_inst); + qk_circuit_instruction_clear(expected_inst); + free(res_inst); + free(expected_inst); + return false; + } + for (uint32_t j = 0; j < res_inst->num_clbits; j++) { + if (res_inst->clbits[j] != expected_inst->clbits[j]) { + printf("Clbit %d for gate %d are different %d was found and expected %d", j, i, + res_inst->clbits[j] != expected_inst->clbits[j]); + qk_circuit_instruction_clear(res_inst); + qk_circuit_instruction_clear(expected_inst); + free(res_inst); + free(expected_inst); + return false; + } + } + if (res_inst->num_params != expected_inst->num_params) { + printf("Gate %d have different number of params %d was found and expected %d", i, + res_inst->num_params, expected_inst->num_params); + qk_circuit_instruction_clear(res_inst); + qk_circuit_instruction_clear(expected_inst); + free(res_inst); + free(expected_inst); + return false; + } + for (uint32_t j = 0; j < res_inst->num_params; j++) { + if (res_inst->params[j] != expected_inst->params[j]) { + printf("Parameter %d for gate %d are different %d was found and expected %d", j, i, + res_inst->params[j] != expected_inst->params[j]); + qk_circuit_instruction_clear(res_inst); + qk_circuit_instruction_clear(expected_inst); + free(res_inst); + free(expected_inst); + return false; + } + } + qk_circuit_instruction_clear(res_inst); + qk_circuit_instruction_clear(expected_inst); + } + free(res_inst); + free(expected_inst); + return true; +} diff --git a/test/c/common.h b/test/c/common.h index 8b10c407ebcc..8c3f68ed81c1 100644 --- a/test/c/common.h +++ b/test/c/common.h @@ -32,3 +32,6 @@ enum TestResult { // post-process the returned `TestResult` to product a minimal info message for // the developer running the test suite. int run(const char *name, int (*test_function)(void)); + +bool compare_circuits(const QkCircuit *res, const QkCircuit *expected); +void print_circuit(const QkCircuit *qc); diff --git a/test/c/test_sabre_layout.c b/test/c/test_sabre_layout.c index 7619b37abdb1..b9f6048ca438 100644 --- a/test/c/test_sabre_layout.c +++ b/test/c/test_sabre_layout.c @@ -20,121 +20,6 @@ #include #include -void print_circuit(const QkCircuit *qc) { - size_t num_instructions = qk_circuit_num_instructions(qc); - QkCircuitInstruction inst; - for (size_t i = 0; i < num_instructions; i++) { - qk_circuit_get_instruction(qc, i, &inst); - printf("%s: qubits: (", inst.name); - for (uint32_t j = 0; j < inst.num_qubits; j++) { - printf("%d,", inst.qubits[j]); - } - printf(")"); - uint32_t num_clbits = inst.num_clbits; - if (num_clbits > 0) { - printf(", clbits: ("); - for (uint32_t j = 0; j < num_clbits; j++) { - printf("%d,", inst.clbits[j]); - } - printf(")"); - } - printf("\n"); - qk_circuit_instruction_clear(&inst); - } -} - -bool compare_circuits(const QkCircuit *res, const QkCircuit *expected) { - if (qk_circuit_num_instructions(res) != qk_circuit_num_instructions(expected)) { - printf("Number of instructions in circuit is mismatched"); - return false; - } - QkCircuitInstruction *res_inst = malloc(sizeof(QkCircuitInstruction)); - QkCircuitInstruction *expected_inst = malloc(sizeof(QkCircuitInstruction)); - for (size_t i = 0; i < qk_circuit_num_instructions(res); i++) { - qk_circuit_get_instruction(res, i, res_inst); - qk_circuit_get_instruction(expected, i, expected_inst); - int result = strcmp(res_inst->name, expected_inst->name); - if (result != 0) { - printf("Gate %d have different gates %s was found and expected %s", i, res_inst->name, - expected_inst->name); - qk_circuit_instruction_clear(res_inst); - qk_circuit_instruction_clear(expected_inst); - free(res_inst); - free(expected_inst); - return false; - } - if (res_inst->num_qubits != expected_inst->num_qubits) { - printf("Gate %d have different number of qubits %d was found and expected %d", i, - res_inst->num_qubits, expected_inst->num_qubits); - qk_circuit_instruction_clear(res_inst); - qk_circuit_instruction_clear(expected_inst); - free(res_inst); - free(expected_inst); - return false; - } - for (uint32_t j = 0; j < res_inst->num_qubits; j++) { - if (res_inst->qubits[j] != expected_inst->qubits[j]) { - printf("Qubit %d for gate %d are different %d was found and expected %d\n", j, i, - res_inst->qubits[j] != expected_inst->qubits[j]); - printf("Expected circuit instructions:\n"); - print_circuit(expected); - printf("Result circuit:\n"); - print_circuit(res); - qk_circuit_instruction_clear(res_inst); - qk_circuit_instruction_clear(expected_inst); - free(res_inst); - free(expected_inst); - return false; - } - } - if (res_inst->num_clbits != expected_inst->num_clbits) { - printf("Gate %d have different number of clbits %d was found and expected %d", i, - res_inst->num_clbits, expected_inst->num_clbits); - qk_circuit_instruction_clear(res_inst); - qk_circuit_instruction_clear(expected_inst); - free(res_inst); - free(expected_inst); - return false; - } - for (uint32_t j = 0; j < res_inst->num_clbits; j++) { - if (res_inst->clbits[j] != expected_inst->clbits[j]) { - printf("Clbit %d for gate %d are different %d was found and expected %d", j, i, - res_inst->clbits[j] != expected_inst->clbits[j]); - qk_circuit_instruction_clear(res_inst); - qk_circuit_instruction_clear(expected_inst); - free(res_inst); - free(expected_inst); - return false; - } - } - if (res_inst->num_params != expected_inst->num_params) { - printf("Gate %d have different number of params %d was found and expected %d", i, - res_inst->num_params, expected_inst->num_params); - qk_circuit_instruction_clear(res_inst); - qk_circuit_instruction_clear(expected_inst); - free(res_inst); - free(expected_inst); - return false; - } - for (uint32_t j = 0; j < res_inst->num_params; j++) { - if (res_inst->params[j] != expected_inst->params[j]) { - printf("Parameter %d for gate %d are different %d was found and expected %d", j, i, - res_inst->params[j] != expected_inst->params[j]); - qk_circuit_instruction_clear(res_inst); - qk_circuit_instruction_clear(expected_inst); - free(res_inst); - free(expected_inst); - return false; - } - } - qk_circuit_instruction_clear(res_inst); - qk_circuit_instruction_clear(expected_inst); - } - free(res_inst); - free(expected_inst); - return true; -} - /** * Test running sabre layout that requires layout and routing */ @@ -162,7 +47,8 @@ int test_sabre_layout_applies_layout(void) { } QkSabreLayoutOptions options = qk_sabre_layout_options_default(); options.seed = 2025; - QkTranspileLayout *layout_result = qk_transpiler_pass_standalone_sabre_layout(qc, target, &options); + QkTranspileLayout *layout_result = + qk_transpiler_pass_standalone_sabre_layout(qc, target, &options); QkOpCounts op_counts = qk_circuit_count_ops(qc); if (op_counts.len != 2) { @@ -271,7 +157,8 @@ int test_sabre_layout_no_swap(void) { } QkSabreLayoutOptions options = qk_sabre_layout_options_default(); options.seed = 2025; - QkTranspileLayout *layout_result = qk_transpiler_pass_standalone_sabre_layout(qc, target, &options); + QkTranspileLayout *layout_result = + qk_transpiler_pass_standalone_sabre_layout(qc, target, &options); QkCircuit *expected_circuit = qk_circuit_new(5, 0); for (uint32_t i = 0; i < qk_circuit_num_qubits(qc) - 1; i++) { uint32_t qargs[2] = {i, i + 1}; From 87e98f195728f976554059b8953db516e03a8b92 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 20 Aug 2025 09:10:50 -0400 Subject: [PATCH 12/20] Use a stack variable instructions in compare_circuits() --- test/c/common.c | 92 ++++++++++++++++++++----------------------------- 1 file changed, 38 insertions(+), 54 deletions(-) diff --git a/test/c/common.c b/test/c/common.c index c94a76feac14..9b392d50e5bb 100644 --- a/test/c/common.c +++ b/test/c/common.c @@ -67,89 +67,73 @@ bool compare_circuits(const QkCircuit *res, const QkCircuit *expected) { printf("Number of instructions in circuit is mismatched"); return false; } - QkCircuitInstruction *res_inst = malloc(sizeof(QkCircuitInstruction)); - QkCircuitInstruction *expected_inst = malloc(sizeof(QkCircuitInstruction)); + QkCircuitInstruction res_inst; + QkCircuitInstruction expected_inst; for (size_t i = 0; i < qk_circuit_num_instructions(res); i++) { - qk_circuit_get_instruction(res, i, res_inst); - qk_circuit_get_instruction(expected, i, expected_inst); - int result = strcmp(res_inst->name, expected_inst->name); + qk_circuit_get_instruction(res, i, &res_inst); + qk_circuit_get_instruction(expected, i, &expected_inst); + int result = strcmp(res_inst.name, expected_inst.name); if (result != 0) { - printf("Gate %d have different gates %s was found and expected %s", i, res_inst->name, - expected_inst->name); - qk_circuit_instruction_clear(res_inst); - qk_circuit_instruction_clear(expected_inst); - free(res_inst); - free(expected_inst); + printf("Gate %d have different gates %s was found and expected %s", i, res_inst.name, + expected_inst.name); + qk_circuit_instruction_clear(&res_inst); + qk_circuit_instruction_clear(&expected_inst); return false; } - if (res_inst->num_qubits != expected_inst->num_qubits) { + if (res_inst.num_qubits != expected_inst.num_qubits) { printf("Gate %d have different number of qubits %d was found and expected %d", i, - res_inst->num_qubits, expected_inst->num_qubits); - qk_circuit_instruction_clear(res_inst); - qk_circuit_instruction_clear(expected_inst); - free(res_inst); - free(expected_inst); + res_inst.num_qubits, expected_inst.num_qubits); + qk_circuit_instruction_clear(&res_inst); + qk_circuit_instruction_clear(&expected_inst); return false; } - for (uint32_t j = 0; j < res_inst->num_qubits; j++) { - if (res_inst->qubits[j] != expected_inst->qubits[j]) { + for (uint32_t j = 0; j < res_inst.num_qubits; j++) { + if (res_inst.qubits[j] != expected_inst.qubits[j]) { printf("Qubit %d for gate %d are different %d was found and expected %d\n", j, i, - res_inst->qubits[j] != expected_inst->qubits[j]); + res_inst.qubits[j] != expected_inst.qubits[j]); printf("Expected circuit instructions:\n"); print_circuit(expected); printf("Result circuit:\n"); print_circuit(res); - qk_circuit_instruction_clear(res_inst); - qk_circuit_instruction_clear(expected_inst); - free(res_inst); - free(expected_inst); + qk_circuit_instruction_clear(&res_inst); + qk_circuit_instruction_clear(&expected_inst); return false; } } - if (res_inst->num_clbits != expected_inst->num_clbits) { + if (res_inst.num_clbits != expected_inst.num_clbits) { printf("Gate %d have different number of clbits %d was found and expected %d", i, - res_inst->num_clbits, expected_inst->num_clbits); - qk_circuit_instruction_clear(res_inst); - qk_circuit_instruction_clear(expected_inst); - free(res_inst); - free(expected_inst); + res_inst.num_clbits, expected_inst.num_clbits); + qk_circuit_instruction_clear(&res_inst); + qk_circuit_instruction_clear(&expected_inst); return false; } - for (uint32_t j = 0; j < res_inst->num_clbits; j++) { - if (res_inst->clbits[j] != expected_inst->clbits[j]) { + for (uint32_t j = 0; j < res_inst.num_clbits; j++) { + if (res_inst.clbits[j] != expected_inst.clbits[j]) { printf("Clbit %d for gate %d are different %d was found and expected %d", j, i, - res_inst->clbits[j] != expected_inst->clbits[j]); - qk_circuit_instruction_clear(res_inst); - qk_circuit_instruction_clear(expected_inst); - free(res_inst); - free(expected_inst); + res_inst.clbits[j] != expected_inst.clbits[j]); + qk_circuit_instruction_clear(&res_inst); + qk_circuit_instruction_clear(&expected_inst); return false; } } - if (res_inst->num_params != expected_inst->num_params) { + if (res_inst.num_params != expected_inst.num_params) { printf("Gate %d have different number of params %d was found and expected %d", i, - res_inst->num_params, expected_inst->num_params); - qk_circuit_instruction_clear(res_inst); - qk_circuit_instruction_clear(expected_inst); - free(res_inst); - free(expected_inst); + res_inst.num_params, expected_inst.num_params); + qk_circuit_instruction_clear(&res_inst); + qk_circuit_instruction_clear(&expected_inst); return false; } - for (uint32_t j = 0; j < res_inst->num_params; j++) { - if (res_inst->params[j] != expected_inst->params[j]) { + for (uint32_t j = 0; j < res_inst.num_params; j++) { + if (res_inst.params[j] != expected_inst.params[j]) { printf("Parameter %d for gate %d are different %d was found and expected %d", j, i, - res_inst->params[j] != expected_inst->params[j]); - qk_circuit_instruction_clear(res_inst); - qk_circuit_instruction_clear(expected_inst); - free(res_inst); - free(expected_inst); + res_inst.params[j] != expected_inst.params[j]); + qk_circuit_instruction_clear(&res_inst); + qk_circuit_instruction_clear(&expected_inst); return false; } } - qk_circuit_instruction_clear(res_inst); - qk_circuit_instruction_clear(expected_inst); + qk_circuit_instruction_clear(&res_inst); + qk_circuit_instruction_clear(&expected_inst); } - free(res_inst); - free(expected_inst); return true; } From cb8960d8d91968111c8969261cb93f26a03147d3 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 20 Aug 2025 17:44:54 -0400 Subject: [PATCH 13/20] Fix output_permutation in the return --- .../src/transpiler/passes/sabre_layout.rs | 19 ++++++++++++------- test/c/test_sabre_layout.c | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/cext/src/transpiler/passes/sabre_layout.rs b/crates/cext/src/transpiler/passes/sabre_layout.rs index c4c816ed7af7..a5cb9040df54 100644 --- a/crates/cext/src/transpiler/passes/sabre_layout.rs +++ b/crates/cext/src/transpiler/passes/sabre_layout.rs @@ -15,7 +15,7 @@ use rand_pcg::Pcg64Mcg; use qiskit_circuit::circuit_data::CircuitData; use qiskit_circuit::converters::dag_to_circuit; use qiskit_circuit::dag_circuit::DAGCircuit; -use qiskit_circuit::Qubit; +use qiskit_circuit::{PhysicalQubit, Qubit}; use qiskit_transpiler::passes::sabre::heuristic; use qiskit_transpiler::passes::sabre::sabre_layout_and_routing; use qiskit_transpiler::target::Target; @@ -142,14 +142,19 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_sabre_layout( dag_to_circuit(&result, false).expect("Internal DAG to circuit conversion failed"); let num_input_qubits = circuit.num_qubits() as u32; *circuit = out_circuit; + let out_final_layout = (0..result.num_qubits() as u32) + .map(|ref q| { + Qubit( + final_layout + .virtual_to_physical(initial_layout.physical_to_virtual(PhysicalQubit(*q))) + .0, + ) + }) + .collect(); + Box::into_raw(Box::new(TranspileLayout::new( Some(initial_layout), - Some( - final_layout - .iter_virtual() - .map(|(_virt, phys)| Qubit(phys.0)) - .collect(), - ), + Some(out_final_layout), result.qubits().objects().clone(), num_input_qubits, ))) diff --git a/test/c/test_sabre_layout.c b/test/c/test_sabre_layout.c index b9f6048ca438..5518e7428ae9 100644 --- a/test/c/test_sabre_layout.c +++ b/test/c/test_sabre_layout.c @@ -83,7 +83,7 @@ int test_sabre_layout_applies_layout(void) { } } - uint32_t expected_final_layout[5] = {3, 0, 1, 2, 4}; + uint32_t expected_final_layout[5] = {0, 3, 1, 2, 4}; uint32_t result_final_layout[5]; qk_transpile_layout_output_permutation(layout_result, result_final_layout); for (uint32_t i = 0; i < 5; i++) { From 8d54e2c546b9af8cdab06f0c03e2bd794180e1de Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 21 Aug 2025 19:23:42 -0400 Subject: [PATCH 14/20] Apply suggestions from code review Co-authored-by: Jake Lishman --- crates/cext/src/transpiler/passes/sabre_layout.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/cext/src/transpiler/passes/sabre_layout.rs b/crates/cext/src/transpiler/passes/sabre_layout.rs index a5cb9040df54..a5b643b161f0 100644 --- a/crates/cext/src/transpiler/passes/sabre_layout.rs +++ b/crates/cext/src/transpiler/passes/sabre_layout.rs @@ -22,7 +22,7 @@ use qiskit_transpiler::target::Target; use qiskit_transpiler::transpile_layout::TranspileLayout; /// The options for running ``qk_transpiler_pass_standalone_sabre_layout``. This struct is used -/// as an input to control the behavior of the +/// as an input to control the behavior of the layout and routing algorithms. #[repr(C)] pub struct QkSabreLayoutOptions { /// The number of forward-backward iterations in the sabre routing algorithm @@ -40,7 +40,7 @@ pub struct QkSabreLayoutOptions { /// @ingroup QkSabreLayoutOptions /// -/// Build a default sabre layout options object This builds a sabre layout with ``max_iterations`` +/// Build a default sabre layout options object. This builds a sabre layout with ``max_iterations`` /// set to 4, both ``num_swap_trials`` and ``num_random_trials`` set to 20, and the seed selected /// by a RNG seeded from system entropy. #[no_mangle] From 6209b8d2ebdccea02806741a2cb0fed6cecd5e18 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 21 Aug 2025 19:30:53 -0400 Subject: [PATCH 15/20] Update docstring for circuit arg --- crates/cext/src/transpiler/passes/sabre_layout.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/cext/src/transpiler/passes/sabre_layout.rs b/crates/cext/src/transpiler/passes/sabre_layout.rs index a5b643b161f0..01d4e842e9a0 100644 --- a/crates/cext/src/transpiler/passes/sabre_layout.rs +++ b/crates/cext/src/transpiler/passes/sabre_layout.rs @@ -89,7 +89,8 @@ pub extern "C" fn qk_sabre_layout_options_default() -> QkSabreLayoutOptions { /// for NISQ-era quantum devices." ASPLOS 2019. /// [`arXiv:1809.02573](https://arxiv.org/pdf/1809.02573.pdf) /// -/// @param circuit A pointer to the circuit to run SabreLayout on +/// @param circuit A pointer to the circuit to run SabreLayout on. The circuit +/// is modified in place and the original circuit's allocations are freed by this function. /// @param target A pointer to the target to run SabreLayout on /// @param options A pointer to the options for SabreLayout /// From 59d316d9e095855c7d0f66e5cbe5e7bab1cc491d Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 21 Aug 2025 19:31:11 -0400 Subject: [PATCH 16/20] Don't use PyErr debug or display --- crates/cext/src/transpiler/passes/sabre_layout.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/cext/src/transpiler/passes/sabre_layout.rs b/crates/cext/src/transpiler/passes/sabre_layout.rs index 01d4e842e9a0..69020edb4d4c 100644 --- a/crates/cext/src/transpiler/passes/sabre_layout.rs +++ b/crates/cext/src/transpiler/passes/sabre_layout.rs @@ -112,7 +112,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_sabre_layout( let target = unsafe { const_ptr_as_ref(target) }; let options = unsafe { const_ptr_as_ref(options) }; let mut dag = DAGCircuit::from_circuit_data(circuit, false, None, None, None, None) - .expect("Internal circuit to DAG conversion failed."); + .unwrap_or_else(|_| panic!("Internal circuit to DAG conversion failed.")); let heuristic = heuristic::Heuristic::new( Some(heuristic::BasicHeuristic::new( 1.0, @@ -138,9 +138,9 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_sabre_layout( Vec::new(), false, ) - .unwrap(); - let out_circuit = - dag_to_circuit(&result, false).expect("Internal DAG to circuit conversion failed"); + .unwrap_or_else(|_| panic!("Sabre layout failed.")); + let out_circuit = dag_to_circuit(&result, false) + .unwrap_or_else(|_| panic!("Internal DAG to circuit conversion failed")); let num_input_qubits = circuit.num_qubits() as u32; *circuit = out_circuit; let out_final_layout = (0..result.num_qubits() as u32) From 4ef73e455a7b1d73e58b470228fa1789be343bcf Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 21 Aug 2025 19:33:21 -0400 Subject: [PATCH 17/20] Add missing newlines to test failure strings --- test/c/common.c | 14 +++++++------- test/c/test_sabre_layout.c | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/test/c/common.c b/test/c/common.c index 9b392d50e5bb..338ec377213f 100644 --- a/test/c/common.c +++ b/test/c/common.c @@ -64,7 +64,7 @@ void print_circuit(const QkCircuit *qc) { bool compare_circuits(const QkCircuit *res, const QkCircuit *expected) { if (qk_circuit_num_instructions(res) != qk_circuit_num_instructions(expected)) { - printf("Number of instructions in circuit is mismatched"); + printf("Number of instructions in circuit is mismatched\n"); return false; } QkCircuitInstruction res_inst; @@ -74,14 +74,14 @@ bool compare_circuits(const QkCircuit *res, const QkCircuit *expected) { qk_circuit_get_instruction(expected, i, &expected_inst); int result = strcmp(res_inst.name, expected_inst.name); if (result != 0) { - printf("Gate %d have different gates %s was found and expected %s", i, res_inst.name, + printf("Gate %d have different gates %s was found and expected %s\n", i, res_inst.name, expected_inst.name); qk_circuit_instruction_clear(&res_inst); qk_circuit_instruction_clear(&expected_inst); return false; } if (res_inst.num_qubits != expected_inst.num_qubits) { - printf("Gate %d have different number of qubits %d was found and expected %d", i, + printf("Gate %d have different number of qubits %d was found and expected %d\n", i, res_inst.num_qubits, expected_inst.num_qubits); qk_circuit_instruction_clear(&res_inst); qk_circuit_instruction_clear(&expected_inst); @@ -101,7 +101,7 @@ bool compare_circuits(const QkCircuit *res, const QkCircuit *expected) { } } if (res_inst.num_clbits != expected_inst.num_clbits) { - printf("Gate %d have different number of clbits %d was found and expected %d", i, + printf("Gate %d have different number of clbits %d was found and expected %d\n", i, res_inst.num_clbits, expected_inst.num_clbits); qk_circuit_instruction_clear(&res_inst); qk_circuit_instruction_clear(&expected_inst); @@ -109,7 +109,7 @@ bool compare_circuits(const QkCircuit *res, const QkCircuit *expected) { } for (uint32_t j = 0; j < res_inst.num_clbits; j++) { if (res_inst.clbits[j] != expected_inst.clbits[j]) { - printf("Clbit %d for gate %d are different %d was found and expected %d", j, i, + printf("Clbit %d for gate %d are different %d was found and expected %d\n", j, i, res_inst.clbits[j] != expected_inst.clbits[j]); qk_circuit_instruction_clear(&res_inst); qk_circuit_instruction_clear(&expected_inst); @@ -117,7 +117,7 @@ bool compare_circuits(const QkCircuit *res, const QkCircuit *expected) { } } if (res_inst.num_params != expected_inst.num_params) { - printf("Gate %d have different number of params %d was found and expected %d", i, + printf("Gate %d have different number of params %d was found and expected %d\n", i, res_inst.num_params, expected_inst.num_params); qk_circuit_instruction_clear(&res_inst); qk_circuit_instruction_clear(&expected_inst); @@ -125,7 +125,7 @@ bool compare_circuits(const QkCircuit *res, const QkCircuit *expected) { } for (uint32_t j = 0; j < res_inst.num_params; j++) { if (res_inst.params[j] != expected_inst.params[j]) { - printf("Parameter %d for gate %d are different %d was found and expected %d", j, i, + printf("Parameter %d for gate %d are different %d was found and expected %d\n", j, i, res_inst.params[j] != expected_inst.params[j]); qk_circuit_instruction_clear(&res_inst); qk_circuit_instruction_clear(&expected_inst); diff --git a/test/c/test_sabre_layout.c b/test/c/test_sabre_layout.c index 5518e7428ae9..66ee3622151c 100644 --- a/test/c/test_sabre_layout.c +++ b/test/c/test_sabre_layout.c @@ -61,12 +61,12 @@ int test_sabre_layout_applies_layout(void) { int swap_gate = strcmp(op_counts.data[i].name, "swap"); int cx_gate = strcmp(op_counts.data[i].name, "cx"); if (cx_gate != 0 && swap_gate != 0) { - printf("Gate type of %s found in the circuit which isn't expected"); + printf("Gate type of %s found in the circuit which isn't expected\n"); result = EqualityError; goto layout_cleanup; } if (swap_gate == 0 && op_counts.data[i].count != 2) { - printf("Unexpected number of swaps %d found in the circuit."); + printf("Unexpected number of swaps %d found in the circuit.\n"); result = EqualityError; goto layout_cleanup; } @@ -76,7 +76,7 @@ int test_sabre_layout_applies_layout(void) { qk_transpile_layout_initial_layout(layout_result, false, result_initial_layout); for (uint32_t i = 0; i < 5; i++) { if (result_initial_layout[i] != expected_initial_layout[i]) { - printf("Initial layout maps qubit %d to %d, expected %d instead", i, + printf("Initial layout maps qubit %d to %d, expected %d instead\n", i, result_initial_layout[i], expected_initial_layout[i]); result = EqualityError; goto layout_cleanup; @@ -88,7 +88,7 @@ int test_sabre_layout_applies_layout(void) { qk_transpile_layout_output_permutation(layout_result, result_final_layout); for (uint32_t i = 0; i < 5; i++) { if (result_final_layout[i] != expected_final_layout[i]) { - printf("Final layout maps qubit %d to %d, expected %d instead", i, + printf("Final layout maps qubit %d to %d, expected %d instead\n", i, result_final_layout[i], expected_final_layout[i]); result = EqualityError; goto layout_cleanup; @@ -176,7 +176,7 @@ int test_sabre_layout_no_swap(void) { qk_transpile_layout_initial_layout(layout_result, false, result_initial_layout); for (uint32_t i = 0; i < 5; i++) { if (result_initial_layout[i] != expected_initial_layout[i]) { - printf("Initial layout maps qubit %d to %d, expected %d instead", i, + printf("Initial layout maps qubit %d to %d, expected %d instead\n", i, result_initial_layout[i], expected_initial_layout[i]); result = EqualityError; goto layout_cleanup; @@ -187,7 +187,7 @@ int test_sabre_layout_no_swap(void) { qk_transpile_layout_output_permutation(layout_result, result_final_layout); for (uint32_t i = 0; i < 5; i++) { if (result_final_layout[i] != expected_final_layout[i]) { - printf("Final layout maps qubit %d to %d, expected %d instead", i, + printf("Final layout maps qubit %d to %d, expected %d instead\n", i, result_final_layout[i], expected_final_layout[i]); result = EqualityError; goto layout_cleanup; From fd4e06d21fcddf93c63e8d01a29b773680ac857d Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 21 Aug 2025 19:34:30 -0400 Subject: [PATCH 18/20] Update error messages about output permutation test failures --- test/c/common.c | 4 ++-- test/c/test_sabre_layout.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/c/common.c b/test/c/common.c index 338ec377213f..c5d48ba9c240 100644 --- a/test/c/common.c +++ b/test/c/common.c @@ -125,8 +125,8 @@ bool compare_circuits(const QkCircuit *res, const QkCircuit *expected) { } for (uint32_t j = 0; j < res_inst.num_params; j++) { if (res_inst.params[j] != expected_inst.params[j]) { - printf("Parameter %d for gate %d are different %d was found and expected %d\n", j, i, - res_inst.params[j] != expected_inst.params[j]); + printf("Parameter %d for gate %d are different %d was found and expected %d\n", j, + i, res_inst.params[j] != expected_inst.params[j]); qk_circuit_instruction_clear(&res_inst); qk_circuit_instruction_clear(&expected_inst); return false; diff --git a/test/c/test_sabre_layout.c b/test/c/test_sabre_layout.c index 66ee3622151c..28f112552baa 100644 --- a/test/c/test_sabre_layout.c +++ b/test/c/test_sabre_layout.c @@ -88,7 +88,7 @@ int test_sabre_layout_applies_layout(void) { qk_transpile_layout_output_permutation(layout_result, result_final_layout); for (uint32_t i = 0; i < 5; i++) { if (result_final_layout[i] != expected_final_layout[i]) { - printf("Final layout maps qubit %d to %d, expected %d instead\n", i, + printf("Output permutation maps qubit %d to %d, expected %d instead\n", i, result_final_layout[i], expected_final_layout[i]); result = EqualityError; goto layout_cleanup; @@ -187,7 +187,7 @@ int test_sabre_layout_no_swap(void) { qk_transpile_layout_output_permutation(layout_result, result_final_layout); for (uint32_t i = 0; i < 5; i++) { if (result_final_layout[i] != expected_final_layout[i]) { - printf("Final layout maps qubit %d to %d, expected %d instead\n", i, + printf("Output permutation maps qubit %d to %d, expected %d instead\n", i, result_final_layout[i], expected_final_layout[i]); result = EqualityError; goto layout_cleanup; From df6b2e4d56e293b45faadee98ed87480793408b2 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Fri, 22 Aug 2025 00:46:55 +0100 Subject: [PATCH 19/20] Correct permutation variable names --- .../src/transpiler/passes/sabre_layout.rs | 4 ++-- test/c/test_sabre_layout.c | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/cext/src/transpiler/passes/sabre_layout.rs b/crates/cext/src/transpiler/passes/sabre_layout.rs index 69020edb4d4c..9da8264d9afe 100644 --- a/crates/cext/src/transpiler/passes/sabre_layout.rs +++ b/crates/cext/src/transpiler/passes/sabre_layout.rs @@ -143,7 +143,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_sabre_layout( .unwrap_or_else(|_| panic!("Internal DAG to circuit conversion failed")); let num_input_qubits = circuit.num_qubits() as u32; *circuit = out_circuit; - let out_final_layout = (0..result.num_qubits() as u32) + let out_permutation = (0..result.num_qubits() as u32) .map(|ref q| { Qubit( final_layout @@ -155,7 +155,7 @@ pub unsafe extern "C" fn qk_transpiler_pass_standalone_sabre_layout( Box::into_raw(Box::new(TranspileLayout::new( Some(initial_layout), - Some(out_final_layout), + Some(out_permutation), result.qubits().objects().clone(), num_input_qubits, ))) diff --git a/test/c/test_sabre_layout.c b/test/c/test_sabre_layout.c index 28f112552baa..6bd662fe7f84 100644 --- a/test/c/test_sabre_layout.c +++ b/test/c/test_sabre_layout.c @@ -83,13 +83,13 @@ int test_sabre_layout_applies_layout(void) { } } - uint32_t expected_final_layout[5] = {0, 3, 1, 2, 4}; - uint32_t result_final_layout[5]; - qk_transpile_layout_output_permutation(layout_result, result_final_layout); + uint32_t expected_permutation[5] = {0, 3, 1, 2, 4}; + uint32_t result_permutation[5]; + qk_transpile_layout_output_permutation(layout_result, result_permutation); for (uint32_t i = 0; i < 5; i++) { - if (result_final_layout[i] != expected_final_layout[i]) { + if (result_permutation[i] != expected_permutation[i]) { printf("Output permutation maps qubit %d to %d, expected %d instead\n", i, - result_final_layout[i], expected_final_layout[i]); + result_permutation[i], expected_permutation[i]); result = EqualityError; goto layout_cleanup; } @@ -182,13 +182,13 @@ int test_sabre_layout_no_swap(void) { goto layout_cleanup; } } - uint32_t expected_final_layout[5] = {0, 1, 2, 3, 4}; - uint32_t result_final_layout[5]; - qk_transpile_layout_output_permutation(layout_result, result_final_layout); + uint32_t expected_permutation[5] = {0, 1, 2, 3, 4}; + uint32_t result_permutation[5]; + qk_transpile_layout_output_permutation(layout_result, result_permutation); for (uint32_t i = 0; i < 5; i++) { - if (result_final_layout[i] != expected_final_layout[i]) { + if (result_permutation[i] != expected_permutation[i]) { printf("Output permutation maps qubit %d to %d, expected %d instead\n", i, - result_final_layout[i], expected_final_layout[i]); + result_permutation[i], expected_permutation[i]); result = EqualityError; goto layout_cleanup; } From 5775678b094d6b2aa4800f840a0a17b9cf0eb3a7 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Fri, 22 Aug 2025 01:17:24 +0100 Subject: [PATCH 20/20] Correct copyright header --- crates/cext/src/transpiler/passes/sabre_layout.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/cext/src/transpiler/passes/sabre_layout.rs b/crates/cext/src/transpiler/passes/sabre_layout.rs index 9da8264d9afe..16306dce3b0b 100644 --- a/crates/cext/src/transpiler/passes/sabre_layout.rs +++ b/crates/cext/src/transpiler/passes/sabre_layout.rs @@ -1,3 +1,5 @@ +// This code is part of Qiskit. +// // (C) Copyright IBM 2025 // // This code is licensed under the Apache License, Version 2.0. You may