Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 129 additions & 1 deletion crates/cext/src/transpiler/transpile_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ use qiskit_transpiler::target::Target;
use qiskit_transpiler::transpile;
use qiskit_transpiler::transpile_layout::TranspileLayout;
use qiskit_transpiler::transpiler::{
get_sabre_heuristic, init_stage, layout_stage, optimization_stage, translation_stage,
get_sabre_heuristic, init_stage, layout_stage, optimization_stage, routing_stage,
translation_stage,
};

use crate::exit_codes::ExitCode;
Expand Down Expand Up @@ -201,6 +202,133 @@ pub unsafe extern "C" fn qk_transpile_stage_init(
}
}

/// @ingroup QkTranspiler
/// Run the preset routing stage of the transpiler on a circuit
///
/// The Qiskit transpiler is a quantum circuit compiler that rewrites a given
/// input circuit to match the constraints of a QPU and optimizes the circuit
/// for execution. This function runs the third stage of the preset pass manager,
/// **routing**, which translates all the instructions in the circuit into
/// those supported by the target. You can refer to
/// @verbatim embed:rst:inline :ref:`transpiler-preset-stage-routing` @endverbatim for more details.
///
/// This function should only be used with circuits constructed
/// using Qiskit's C API. It makes assumptions on the circuit only using features exposed via C,
/// if you are in a mixed Python and C environment it is typically better to invoke the transpiler
/// via Python.
///
/// This function is multithreaded internally and will launch a thread pool
/// with threads equal to the number of CPUs reported by the operating system by default.
/// This will include logical cores on CPUs with simultaneous multithreading. 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.
///
/// @param dag A pointer to the circuit to run the transpiler on.
/// @param target A pointer to the target to compile the circuit for.
/// @param options A pointer to an options object that defines user options. If this is a null
/// pointer the default values will be used. See ``qk_transpile_default_options``
/// for more details on the default values.
/// @param layout A pointer to a pointer to a ``QkTranspileLayout`` object. Typically you will need
/// to run the `qk_transpile_stage_layout` prior to this function and that will provide a
/// `QkTranspileLayout` object with the initial layout set you want to take that output layout from
/// that function and use this as the input for this. If you don't have a layout object (e.g. you ran
/// your own layout pass). You can run ``qk_transpile_layout_generate_from_mapping`` to generate a trivial
/// layout (where virtual qubit 0 in the circuit is mapped to physical qubit 0 in the target,
/// 1->1, 2->2, etc) for the dag at it's current state. This will enable you to generate a layout
/// object for the routing stage if you generate your own layout. Note that while this makes a
/// valid layout object to track the permutation caused by routing it does not correctly reflect
/// the initial layout if your custom layout pass is not a trivial layout. You will need to track
/// the initial layout independently in this case.
/// @param error A pointer to a pointer with an nul terminated string with an error description.
/// If the transpiler fails a pointer to the string with the error description will be written
/// to this pointer. That pointer needs to be freed with ``qk_str_free``. This can be a null
/// pointer in which case the error will not be written out.
///
/// @returns The return code for the transpiler, ``QkExitCode_Success`` means success and all
/// other values indicate an error.
///
/// # Safety
///
/// Behavior is undefined if ``dag``, ``target``, or ``layout``, are not valid, non-null
/// pointers to a ``QkDag``, ``QkTarget``, or a ``QkTranspileLayout`` pointer
/// respectively. ``options`` must be a valid pointer a to a ``QkTranspileOptions`` or ``NULL``.
/// ``error`` must be a valid pointer to a ``char`` pointer or ``NULL``.
#[unsafe(no_mangle)]
#[cfg(feature = "cbinding")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small cleanup suggestion I could think of here is that several of the new :meth:qk_transpile_stage_* functions repeat the same pointer-conversion, options-handling, approximation-degree parsing, seed parsing, and error-reporting boilerplate. It may be worth considering internal helpers to centralize that logic and reduce duplication. This changed is not required for this PR however, as the overall functionality doesn't change. But the code reduction may improve maintainability and readability.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a fair comment, but I think since this PR is adding the last one in the series we should just finish following the pattern the other functions use. But if you want to push a PR on top of this one deduplicating the internals we can review that very easily if it makes things cleaner.

pub unsafe extern "C" fn qk_transpile_stage_routing(
dag: *mut DAGCircuit,
target: *const Target,
options: *const TranspileOptions,
layout: *mut TranspileLayout,
error: *mut *mut c_char,
) -> ExitCode {
// SAFETY: Per documentation, the pointers is non-null and aligned.
let dag = unsafe { mut_ptr_as_ref(dag) };
let target = unsafe { const_ptr_as_ref(target) };
let options = if options.is_null() {
&TranspileOptions::default()
} else {
// SAFETY: We checked the pointer is not null, then, per documentation, it is a valid
// and aligned pointer.
unsafe { const_ptr_as_ref(options) }
};
let seed = if options.seed < 0 {
None
} else {
Some(options.seed as u64)
};
let sabre_heuristic = match get_sabre_heuristic(target) {
Ok(val) => val,
Err(e) => {
if !error.is_null() {
unsafe {
// Right now we return a backtrace of the error. This at least gives a hint as to
// which pass failed when we have rust errors normalized we can actually have error
// messages which are user facing. But most likely this will be a PyErr and panic
// when trying to extract the string.
*error = CString::new(format!(
"Transpilation failed with this backtrace: {}",
e.backtrace()
))
.unwrap()
.into_raw();
}
}
return ExitCode::TranspilerError;
}
};
// SAFETY: Per the documentation this is a valid pointer to a transpile layout
let out_layout = unsafe { mut_ptr_as_ref(layout) };
match routing_stage(
dag,
target,
options.optimization_level.into(),
seed,
&sabre_heuristic,
out_layout,
) {
Err(e) => {
if !error.is_null() {
// Right now we return a backtrace of the error. This at least gives a hint as to
// which pass failed when we have rust errors normalized we can actually have error
// messages which are user facing. But most likely this will be a PyErr and panic
// when trying to extract the string.
let out_string = CString::new(format!(
"Transpilation failed with this backtrace: {}",
e.backtrace()
))
.unwrap()
.into_raw();
unsafe {
*error = out_string;
}
}
ExitCode::TranspilerError
}
Ok(_) => ExitCode::Success,
}
}

/// @ingroup QkTranspiler
/// Run the preset optimization stage of the transpiler on a circuit
///
Expand Down
64 changes: 64 additions & 0 deletions crates/cext/src/transpiler/transpile_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
// that they have been altered from the originals.

use crate::pointers::const_ptr_as_ref;
use qiskit_circuit::dag_circuit::DAGCircuit;
use qiskit_circuit::nlayout::{NLayout, PhysicalQubit};
use qiskit_transpiler::target::Target;
use qiskit_transpiler::transpile_layout::TranspileLayout;

#[cfg(feature = "python_binding")]
Expand Down Expand Up @@ -223,6 +226,67 @@ pub unsafe extern "C" fn qk_transpile_layout_final_layout(
}
}

/// @ingroup QkTranspileLayout
/// Generate a ``QkTranspileLayout`` from a initial layout mapping
///
/// This will generate a ``QkTranspileLayout`` with the initial layout set (and no ouptput
/// permutation) from a provided mapping. The intent of this function is to enable creating
/// a custom layout pass that also creates a ``QkTranspileLayout`` that you can use with
/// subsequent stage functions such as ``qk_transpile_stage_routing``.
///
/// @param original_dag: A pointer to the original dag prior to running a custom layout pass. This
/// dag must have fewer than or the same number of qubits as ``target``.
/// @param target: A pointer to the target that layout was run on. This target must have fixed
/// number of qubits set.
/// @param qubit_mapping: A pointer to the layout mapping array. This array must have the same
/// number of elements as there are qubits in target and each element is a unique integer and
/// the all must fall in the range of 0 to ``num_qubits`` where ``num_qubits`` is the
/// number of qubits indicated in the provided value for ``target``.
/// The first elements represent the virtual qubits in ``original_dag`` and the value
/// represents the physical qubit in the target which the virtual qubit is mapped too.
/// For example an array of ``[1, 0, 2]`` would map virtual qubit 0 -> physical qubit 1,
/// virtual qubit 1 -> physical qubit 0, and virtual qubit 2 -> physical qubit 2. For elements
/// that are not in the original dag these are treated as ancilla qubits, but still must be
/// mapped to a physical qubit. This array will be copied into the output ``QkTranspileLayout``
/// so you must still free it after calling this function.
///
/// @returns The QkTranspileLayout object with the initial layout set
///
/// # Safety
/// Behavior is undefined if ``original_dag`` and target ``target`` are not a valid, aligned,
/// non-null pointer to a ``QkDag`` or a ``QkTarget`` respectively. ``qubit_mapping`` must be a
/// valid pointer to a contiguous array of ``uint32_t`` with enough space for the number of qubits
/// indicated in ``target``.
#[unsafe(no_mangle)]
#[cfg(feature = "cbinding")]
pub unsafe extern "C" fn qk_transpile_layout_generate_from_mapping(
original_dag: *const DAGCircuit,
target: *const Target,
qubit_mapping: *const u32,
) -> *mut TranspileLayout {
// SAFETY: Per the documentation these are valid pointers to the appropriate type
let dag = unsafe { const_ptr_as_ref(original_dag) };
let target = unsafe { const_ptr_as_ref(target) };
// SAFETY: Per the documentation this must be a valid pointer to a u32 array with
// target.num_qubits elements.
let virt_to_phys: Vec<PhysicalQubit> = unsafe {
std::slice::from_raw_parts(
qubit_mapping as *const PhysicalQubit,
target.num_qubits.unwrap() as usize,
)
}
.to_vec();
let initial_layout = NLayout::from_virtual_to_physical(virt_to_phys).unwrap();
let transpile_layout: TranspileLayout = TranspileLayout::new(
Some(initial_layout),
None,
dag.qubits().objects().to_owned(),
dag.num_qubits() as u32,
dag.qregs().to_vec(),
);
Box::into_raw(Box::new(transpile_layout))
}

/// @ingroup QkTranspileLayout
/// Free a ``QkTranspileLayout`` object
///
Expand Down
7 changes: 7 additions & 0 deletions releasenotes/notes/transpile_init_c-89e73e83589c3c36.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@ features_c:

* :c:func:`qk_transpile_stage_init`
* :c:func:`qk_transpile_stage_layout`
* :c:func:`qk_transpile_stage_routing`
* :c:func:`qk_transpile_stage_translation`
* :c:func:`qk_transpile_stage_optimization`

run the :ref:`transpiler-preset-stage-init`, :ref:`transpiler-preset-stage-layout`,
:ref:`transpiler-preset-stage-routing`,
:ref:`transpiler-preset-stage-translation`, and
:ref:`transpiler-preset-stage-optimization` respectively.
Comment on lines 6 to 16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to turn these into a table? Sort of like this:

Suggested change
* :c:func:`qk_transpile_stage_init`
* :c:func:`qk_transpile_stage_layout`
* :c:func:`qk_transpile_stage_routing`
* :c:func:`qk_transpile_stage_translation`
* :c:func:`qk_transpile_stage_optimization`
run the :ref:`transpiler-preset-stage-init`, :ref:`transpiler-preset-stage-layout`,
:ref:`transpiler-preset-stage-routing`,
:ref:`transpiler-preset-stage-translation`, and
:ref:`transpiler-preset-stage-optimization` respectively.
========================================= ===========================================
C function Transpiler Stage
========================================= ===========================================
:c:func:`qk_transpile_stage_init` :ref:`transpiler-preset-stage-init`
:c:func:`qk_transpile_stage_layout` :ref:`transpiler-preset-stage-layout`
:c:func:`qk_transpile_stage_routing` :ref:`transpiler-preset-stage-routing`
:c:func:`qk_transpile_stage_translation` :ref:`transpiler-preset-stage-translation`
:c:func:`qk_transpile_stage_optimization` :ref:`transpiler-preset-stage-optimization`
========================================= ===========================================

It would look like this:

Image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine either way. I went with a list because it was simpler. Either way we can always change it in the release note roundup for the final release.

These function are used to run these stages from the preset pass manager
on a ``QkDag`` object. The goal of these functions are to enable composable
transpilation workflows from C when combined with custom transpiler passes.
- |
Added a new function :c:func:`qk_transpile_layout_generate_from_mapping` which is
used to generate a custom :c:struct:`QkTranspileLayout` with an initial layout set
from a mapping array. The intent of this function is to enable creating a
layout object for custom layout transpiler passes.
81 changes: 81 additions & 0 deletions test/c/test_transpile_layout.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// 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 <complex.h>
#include <qiskit.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

static int test_transpile_layout_generate(void) {
int result = Ok;
QkTarget *target = qk_target_new(5);
QkDag *orig_dag = qk_dag_new();
QkQuantumRegister *qr = qk_quantum_register_new(3, "qr");
qk_dag_add_quantum_register(orig_dag, qr);
uint32_t layout_mapping[5] = {1, 4, 3, 2, 0};
QkTranspileLayout *layout =
qk_transpile_layout_generate_from_mapping(orig_dag, target, layout_mapping);
if (qk_transpile_layout_num_input_qubits(layout) != 3) {
fprintf(stderr, "Number of input qubits is %u, expected 3\n",
qk_transpile_layout_num_input_qubits(layout));
result = EqualityError;
goto cleanup;
}
if (qk_transpile_layout_num_output_qubits(layout) != 5) {
fprintf(stderr, "Number of output qubits is %u, expected 5\n",
qk_transpile_layout_num_input_qubits(layout));
result = EqualityError;
goto cleanup;
}
bool permutation_set = qk_transpile_layout_output_permutation(layout, NULL);
if (permutation_set) {
fprintf(stderr, "Generated unexpectedly has an output permutation");
goto cleanup;
}
uint32_t *output_mapping = malloc(sizeof(uint32_t) * 5);
bool layout_set = qk_transpile_layout_initial_layout(layout, false, output_mapping);
if (!layout_set) {
fprintf(stderr, "Generated layout doesn't have initial layout set\n");
result = EqualityError;
goto result_cleanup;
}
for (int i = 0; i < 5; i++) {
if (output_mapping[i] != layout_mapping[i]) {
fprintf(stderr, "Element %i does not match. Result: %u, Expected: %u\n", i,
output_mapping[i], layout_mapping[i]);
result = EqualityError;
goto result_cleanup;
}
}
result_cleanup:
free(output_mapping);

cleanup:
qk_transpile_layout_free(layout);
qk_dag_free(orig_dag);
qk_target_free(target);
return result;
}

int test_transpile_layout(void) {
int num_failed = 0;
num_failed += RUN_TEST(test_transpile_layout_generate);

fflush(stderr);
fprintf(stderr, "=== Number of failed subtests: %i\n", num_failed);

return num_failed;
}
49 changes: 49 additions & 0 deletions test/c/test_transpiler.c
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,54 @@ int test_layout_stage_empty(void) {
return result;
}

int test_routing_stage_empty(void) {
int result = Ok;
uint32_t num_qubits = 2048;
QkTarget *target = qk_target_new(num_qubits);
QkTargetEntry *cx_entry = qk_target_entry_new(QkGate_CX);
for (uint32_t i = 0; i < num_qubits - 1; i++) {
qk_target_entry_add_property(cx_entry, (uint32_t[]){i, i + 1}, 2, 0.001 * i, 0.002 * i);
}
qk_target_add_instruction(target, cx_entry);
qk_target_add_instruction(target, qk_target_entry_new(QkGate_U));

QkDag *dag = qk_dag_new();
QkQuantumRegister *qr = qk_quantum_register_new(1024, "qr");
qk_dag_add_quantum_register(dag, qr);
QkTranspileLayout **layout = malloc(sizeof(QkTranspileLayout *));
*layout = NULL;
// Run layout stage to populate the QkTranspileLayout
int compile_result = qk_transpile_stage_layout(dag, target, NULL, layout, NULL);
if (compile_result != 0) {
result = EqualityError;
printf("Running the layout stage failed\n");
goto cleanup;
}
compile_result = qk_transpile_stage_routing(dag, target, NULL, *layout, NULL);
if (compile_result != 0) {
result = EqualityError;
printf("Running the routing stage failed\n");
goto cleanup;
}
uint32_t num_dag_qubits = qk_dag_num_qubits(dag);
if (num_dag_qubits != 2048) {
result = EqualityError;
printf("Number of dag qubits %u does not match expected result 2048", num_dag_qubits);
}
uint32_t num_layout_qubits = qk_transpile_layout_num_output_qubits(*layout);
if (num_layout_qubits != 2048) {
result = EqualityError;
printf("Number of layout qubits %u does not match expected result 2048\n",
num_layout_qubits);
}
cleanup:
qk_target_free(target);
qk_dag_free(dag);
qk_transpile_layout_free(*layout);
free(layout);
return result;
}

int test_translation_stage_empty(void) {
int result = Ok;
uint32_t num_qubits = 2048;
Expand Down Expand Up @@ -392,6 +440,7 @@ int test_transpiler(void) {
num_failed += RUN_TEST(test_transpile_options_null);
num_failed += RUN_TEST(test_init_stage_empty);
num_failed += RUN_TEST(test_layout_stage_empty);
num_failed += RUN_TEST(test_routing_stage_empty);
num_failed += RUN_TEST(test_translation_stage_empty);
num_failed += RUN_TEST(test_optimization_stage_empty);

Expand Down