Skip to content

Commit 519ce51

Browse files
mtreinishCryorisjakelishman
authored
Embed C API into Python extension (Qiskit#13934)
* Embed C API into Python extension 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 <[email protected]> * Remove python space function The path is trivial to get if you need it and we shouldn't commit to this in the public api yet. * Add feature gate for python function * Add optional python header and and fix rename overrides prefixing * Correct dependencies in Makefile * Fix cbindgen config * Add cfg feature guard for python dependent functionality * Adjust python gil usage and document it * Update crates/pyext/src/lib.rs * Update crates/cext/src/sparse_observable.rs Co-authored-by: Jake Lishman <[email protected]> * Manual object rename We should find a better solution, maybe the Rust interface of cbindgen allows for more options here. * pyo3 as optional dependency in cext but implicitly it still depends on it via qiskit-accelerate --------- Co-authored-by: Julien Gacon <[email protected]> Co-authored-by: Jake Lishman <[email protected]> Co-authored-by: Jake Lishman <[email protected]>
1 parent 149d5a6 commit 519ce51

File tree

8 files changed

+65
-14
lines changed

8 files changed

+65
-14
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Makefile

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,16 +115,18 @@ cformat:
115115
fix_cformat:
116116
bash tools/run_clang_format.sh apply
117117

118-
# The header file is managed by a different build tool - pretend it's always dirty.
119-
.PHONY: $(C_QISKIT_H)
120-
$(C_QISKIT_H):
121-
cargo build --release --no-default-features --features cbinding
118+
# The library file is managed by a different build tool - pretend it's always dirty.
119+
.PHONY: $(C_LIB_CARGO_PATH)
120+
$(C_LIB_CARGO_PATH):
121+
cargo build --release --no-default-features --features cbinding -p qiskit-cext
122+
123+
$(C_QISKIT_H): $(C_LIB_CARGO_PATH)
122124
cbindgen --crate qiskit-cext --output $(C_DIR_INCLUDE)/qiskit.h --lang C
123125

124126
$(C_DIR_LIB):
125127
mkdir -p $(C_DIR_LIB)
126128

127-
$(C_LIBQISKIT): $(C_DIR_LIB)
129+
$(C_LIBQISKIT): $(C_DIR_LIB) $(C_LIB_CARGO_PATH)
128130
cp $(C_LIB_CARGO_PATH) $(C_DIR_LIB)/$(subst _cext,,$(C_LIB_CARGO_FILENAME))
129131

130132
.PHONY: cheader clib c

crates/cext/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ workspace = true
1717
thiserror.workspace = true
1818
num-complex.workspace = true
1919
qiskit-accelerate.workspace = true
20+
pyo3 = { workspace = true, optional = true }
2021

2122
[build-dependencies]
2223
cbindgen = "0.28"
2324

2425
[features]
2526
default = ["cbinding"]
27+
python_binding = ["dep:pyo3"]
2628
cbinding = []

crates/cext/cbindgen.toml

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
include_version = true
2-
style = "type"
2+
style = "type"
33
sys_includes = ["complex.h"]
44
after_includes = """
5+
#ifdef QISKIT_C_PYTHON_INTERFACE
6+
#include <Python.h>
7+
#endif
8+
59
// Complex number typedefs -- note these are memory aligned but
610
// not calling convention compatible.
7-
typedef float complex QkComplex32;
11+
typedef float complex QkComplex32;
812
typedef double complex QkComplex64;
913
10-
// Always expose [cfg(feature = "cbinding")] -- workaround for
14+
// Always expose [cfg(feature = "cbinding")] -- workaround for
1115
// https://github.com/mozilla/cbindgen/issues/995
1216
#define QISKIT_WITH_CBINDINGS
1317
"""
1418

1519
[defines]
1620
"feature = cbinding" = "QISKIT_WITH_CBINDINGS"
21+
"feature = python_binding" = "QISKIT_C_PYTHON_INTERFACE"
1722

1823
[parse]
1924
parse_deps = true
@@ -22,10 +27,8 @@ include = ["qiskit-accelerate"]
2227
[enum]
2328
prefix_with_name = true
2429

25-
[export]
26-
prefix = "Qk"
27-
renaming_overrides_prefixing = false
28-
2930
[export.rename]
30-
"SparseObservable" = "Obs"
31-
"CSparseTerm" = "ObsTerm"
31+
"SparseObservable" = "QkObs"
32+
"CSparseTerm" = "QkObsTerm"
33+
"BitTerm" = "QkBitTerm"
34+
"Complex64" = "QkComplex64"

crates/cext/src/sparse_observable.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,16 @@ use std::ffi::{c_char, CString};
1414

1515
use crate::exit_codes::{CInputError, ExitCode};
1616
use num_complex::Complex64;
17+
1718
use qiskit_accelerate::sparse_observable::{BitTerm, SparseObservable, SparseTermView};
1819

20+
#[cfg(feature = "python_binding")]
21+
use pyo3::ffi::PyObject;
22+
#[cfg(feature = "python_binding")]
23+
use pyo3::{Py, Python};
24+
#[cfg(feature = "python_binding")]
25+
use qiskit_accelerate::sparse_observable::PySparseObservable;
26+
1927
/// @ingroup QkObsTerm
2028
/// A term in a [SparseObservable].
2129
///
@@ -924,3 +932,34 @@ pub extern "C" fn qk_bitterm_label(bit_term: BitTerm) -> u8 {
924932
.next()
925933
.expect("Label has exactly one character") as u8
926934
}
935+
936+
/// @ingroup SparseObservable
937+
/// Convert to a Python-space [PySparseObservable].
938+
///
939+
/// @param obs The C-space [SparseObservable] pointer.
940+
///
941+
/// @return A Python object representing the [PySparseObservable].
942+
///
943+
/// # Safety
944+
///
945+
/// Behavior is undefined if ``obs`` is not a valid, non-null pointer to a ``QkObs``.
946+
///
947+
/// It is assumed that the thread currently executing this function holds the
948+
/// Python GIL this is required to create the Python object returned by this
949+
/// function.
950+
#[no_mangle]
951+
#[cfg(feature = "python_binding")]
952+
#[cfg(feature = "cbinding")]
953+
pub unsafe extern "C" fn qk_obs_to_python(obs: *const SparseObservable) -> *mut PyObject {
954+
// SAFETY: Per documentation, the pointer is non-null and aligned.
955+
let obs = unsafe { const_ptr_as_ref(obs) };
956+
let py_obs: PySparseObservable = obs.clone().into();
957+
958+
// SAFETY: the C caller is required to hold the GIL.
959+
unsafe {
960+
let py = Python::assume_gil_acquired();
961+
Py::new(py, py_obs)
962+
.expect("Unable to create a Python object")
963+
.into_ptr()
964+
}
965+
}

crates/pyext/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ qiskit-accelerate.workspace = true
2828
qiskit-circuit.workspace = true
2929
qiskit-qasm2.workspace = true
3030
qiskit-qasm3.workspace = true
31+
qiskit-cext = { workspace = true, features = ["python_binding"] }

crates/pyext/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
// that they have been altered from the originals.
1212

1313
use pyo3::prelude::*;
14+
pub use qiskit_cext::*;
1415

1516
#[inline(always)]
1617
#[doc(hidden)]

qiskit/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@
131131
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
132132
from .version import __version__
133133

134+
134135
__all__ = [
135136
"AncillaRegister",
136137
"ClassicalRegister",

0 commit comments

Comments
 (0)