-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Add SabreLayout to the C API #14711
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add SabreLayout to the C API #14711
Changes from all commits
09c0b7c
9435002
28f3737
94b80bc
ac2792a
b44e527
b26658f
127d6d7
0aa1f10
05725ba
6d95b87
dcc672b
be8e750
6bf9a6e
87e98f1
cb8960d
6f5ffe5
58a6e45
8d54e2c
6209b8d
59d316d
4ef73e4
fd4e06d
df6b2e4
5775678
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,164 @@ | ||
| // 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. | ||
|
|
||
| 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; | ||
| use qiskit_circuit::dag_circuit::DAGCircuit; | ||
| 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; | ||
| 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 layout and routing algorithms. | ||
| #[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. | ||
| /// | ||
| /// 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. | ||
|
jakelishman marked this conversation as resolved.
|
||
| /// | ||
| /// 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. | ||
|
Comment on lines
+79
to
+82
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the future, we're almost certainly going to want to expose some sort of per-function and per-process "threading configuration" objects into C space, given the target audience.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I am putting these comments in the docstrings because I've already had discussions about calling some of the C API functions in an OpenMP context and wanted to make sure we were at least documenting how it worked now. But I agree we'll probably want to expose a structure maybe as part of |
||
| /// | ||
| /// # 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. 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 | ||
| /// | ||
| /// @return The transpile layout that describes the layout and output permutation caused | ||
| /// by 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: *mut CircuitData, | ||
|
mtreinish marked this conversation as resolved.
|
||
| target: *const Target, | ||
| 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) | ||
| .unwrap_or_else(|_| panic!("Internal circuit to DAG conversion failed.")); | ||
| 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, | ||
|
jakelishman marked this conversation as resolved.
|
||
| ); | ||
| let (result, initial_layout, final_layout) = sabre_layout_and_routing( | ||
| &mut dag, | ||
| target, | ||
| &heuristic, | ||
| options.max_iterations, | ||
| options.num_swap_trials, | ||
| options.num_random_trials, | ||
| Some(options.seed), | ||
| Vec::new(), | ||
| false, | ||
| ) | ||
| .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_permutation = (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(out_permutation), | ||
| result.qubits().objects().clone(), | ||
| num_input_qubits, | ||
| ))) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In VF2 land I went for making the internals of the struct private to let us extend it in an API-safe way, but I think it's good to try both ways and see which feels better, while we're still uncommitted to a stable interface - let's leave this as-is.