Skip to content

Commit

Permalink
Embed C API into Python extension
Browse files Browse the repository at this point in the history
This commit adds the C extension crate into the Python extension build.
This is done by adding the qiskit-cext crate as a dependency for
qiskit-pyext. Then the contents of cext are re-exported from pyext. This
enables linking against the _accelerate shared library object using the
C API. This facilitates writing compiled Python extensions that
interface with Qiskit directly via C. For example, building a
SparseObservable object in a compiled language via the C API and then
returning that to Python for use with the rest of Qiskit.

Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
  • Loading branch information
Cryoris and mtreinish committed Feb 28, 2025
1 parent c1aeb1e commit f2aa474
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 6 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/cext/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ workspace = true
thiserror.workspace = true
num-complex.workspace = true
qiskit-accelerate.workspace = true
pyo3.workspace = true

[build-dependencies]
cbindgen = "0.28"
Expand Down
11 changes: 6 additions & 5 deletions crates/cext/cbindgen.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
include_version = true
style = "type"
style = "type"
sys_includes = ["complex.h"]
after_includes = """
// Complex number typedefs -- note these are memory aligned but
// not calling convention compatible.
typedef float complex QkComplex32;
typedef float complex QkComplex32;
typedef double complex QkComplex64;
// Always expose [cfg(feature = "cbinding")] -- workaround for
// Always expose [cfg(feature = "cbinding")] -- workaround for
// https://github.com/mozilla/cbindgen/issues/995
#define QISKIT_WITH_CBINDINGS
"""
Expand All @@ -24,7 +24,8 @@ prefix_with_name = true

[export]
prefix = "Qk"
renaming_overrides_prefixing = false
renaming_overrides_prefixing = true

[export.rename]
"CSparseTerm" = "SparseTerm"
"CSparseTerm" = "QkSparseTerm"
"PyObject" = "PyObject"
25 changes: 24 additions & 1 deletion crates/cext/src/sparse_observable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ use std::ffi::{c_char, CString};

use crate::exit_codes::{CInputError, ExitCode};
use num_complex::Complex64;
use qiskit_accelerate::sparse_observable::{BitTerm, SparseObservable, SparseTermView};
use pyo3::ffi::PyObject;
use pyo3::{Py, Python};
use qiskit_accelerate::sparse_observable::{
BitTerm, PySparseObservable, SparseObservable, SparseTermView,
};

/// @ingroup QkSparseTerm
/// A term in a [SparseObservable].
Expand Down Expand Up @@ -847,3 +851,22 @@ pub extern "C" fn qk_bitterm_label(bit_term: BitTerm) -> u8 {
.next()
.expect("Label has exactly one character") as u8
}

/// @ingroup SparseObservable
/// Convert to a Python-space [PySparseObservable].
///
/// @param obs The C-space [SparseObservable] pointer.
///
/// @return A Python object representing the [PySparseObservable].
#[no_mangle]
#[cfg(feature = "cbinding")]
pub unsafe extern "C" fn qk_obs_to_python(obs: *const SparseObservable) -> *mut PyObject {
let obs = unsafe { const_ptr_as_ref(obs) };
let py_obs: PySparseObservable = obs.clone().into();

Python::with_gil(|py| {
Py::new(py, py_obs)
.expect("Unable to create a Python object")
.into_ptr()
})
}
1 change: 1 addition & 0 deletions crates/pyext/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ qiskit-accelerate.workspace = true
qiskit-circuit.workspace = true
qiskit-qasm2.workspace = true
qiskit-qasm3.workspace = true
qiskit-cext.workspace = true
2 changes: 2 additions & 0 deletions crates/pyext/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
// that they have been altered from the originals.

use pyo3::prelude::*;
pub use qiskit_cext::sparse_observable::*;
pub use qiskit_cext::*;

#[inline(always)]
#[doc(hidden)]
Expand Down
14 changes: 14 additions & 0 deletions qiskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,20 @@
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from .version import __version__


def get_lib() -> str:
"""Get the path to the compiled library object.
When interfacing with Qiskit's C API for a Python extension the shared library object
returned from this function must be what is linked against. This will ensure that all
objects are built using the same library file.
Returns:
The path.
"""
return _accelerate.__file__


__all__ = [
"AncillaRegister",
"ClassicalRegister",
Expand Down

0 comments on commit f2aa474

Please sign in to comment.