ctypes approach

This commit is contained in:
jan 2024-12-21 13:56:51 -08:00
parent ba07d253d2
commit 320958d888
5 changed files with 115 additions and 92 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "klamath_rs_ext" name = "klamath_rs_ext"
version = "0.1.0" version = "0.2.0"
authors = ["jan <jan@mpxd.net>"] authors = ["jan <jan@mpxd.net>"]
edition = "2021" edition = "2021"
@ -12,5 +12,3 @@ crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
byteorder = "^1" byteorder = "^1"
pyo3 = "^0"
numpy = "^0"

View File

@ -2,5 +2,5 @@ from .basic import pack_int2 as pack_int2
from .basic import pack_int4 as pack_int4 from .basic import pack_int4 as pack_int4
__version__ = 0.1 __version__ = 0.2

View File

@ -1,24 +1,68 @@
from collections.abc import Sequence from collections.abc import Sequence
import ctypes
from pathlib import Path
from itertools import chain
import numpy import numpy
from numpy.typing import NDArray from numpy.typing import NDArray
from .klamath_rs_ext import arr_to_int2, arr_to_int4 so_path = Path(__file__).resolve().parent / 'libklamath_rs_ext.so'
clib = ctypes.CDLL(so_path)
CONV_TABLE_i16 = {
numpy.float64: clib.f64_to_i16,
numpy.float32: clib.f32_to_i16,
numpy.int64: clib.i64_to_i16,
numpy.int32: clib.i32_to_i16,
numpy.int16: clib.i16_to_i16,
numpy.uint64: clib.u64_to_i16,
numpy.uint32: clib.u32_to_i16,
numpy.uint16: clib.u16_to_i16,
}
CONV_TABLE_i32 = {
numpy.float64: clib.f64_to_i32,
numpy.float32: clib.f32_to_i32,
numpy.int64: clib.i64_to_i32,
numpy.int32: clib.i32_to_i32,
numpy.uint64: clib.u64_to_i32,
numpy.uint32: clib.u32_to_i32,
}
clib.f64_to_i16.restype = ctypes.c_double
clib.f32_to_i16.restype = ctypes.c_float
clib.i64_to_i16.restype = ctypes.c_int64
clib.i32_to_i16.restype = ctypes.c_int32
clib.i16_to_i16.restype = ctypes.c_int16
clib.u64_to_i16.restype = ctypes.c_uint64
clib.u32_to_i16.restype = ctypes.c_uint32
clib.u16_to_i16.restype = ctypes.c_uint16
clib.f64_to_i32.restype = ctypes.c_double
clib.f32_to_i32.restype = ctypes.c_float
clib.i64_to_i32.restype = ctypes.c_int64
clib.i32_to_i32.restype = ctypes.c_int32
clib.u64_to_i32.restype = ctypes.c_uint64
clib.u32_to_i32.restype = ctypes.c_uint32
for fn in chain(CONV_TABLE_i16.values(), CONV_TABLE_i32.values()):
fn.argtypes = [ctypes.POINTER(fn.restype), ctypes.c_size_t]
def pack_int2(data: NDArray[numpy.integer] | Sequence[int] | int) -> bytes: def pack_int2(data: NDArray[numpy.integer] | Sequence[int] | int) -> bytes:
arr = numpy.asarray(data) arr = numpy.asarray(data)
if arr.dtype in ( if arr.dtype in CONV_TABLE_i16.keys():
numpy.float64, numpy.float32,
numpy.int64, numpy.uint64,
numpy.int32, numpy.uint32,
numpy.int16, numpy.uint16,
):
arr = numpy.require(arr, requirements=('C_CONTIGUOUS', 'ALIGNED', 'WRITEABLE', 'OWNDATA')) arr = numpy.require(arr, requirements=('C_CONTIGUOUS', 'ALIGNED', 'WRITEABLE', 'OWNDATA'))
if arr is data: if arr is data:
arr = numpy.array(arr, copy=True) arr = numpy.array(arr, copy=True)
arr_to_int2(arr)
fn = CONV_TABLE_i16[arr.dtype]
result = fn(arr.ctypes.data_as(fn.argtypes[0]), arr.size)
i2arr = arr.view('>i2')[::arr.itemsize // 2] i2arr = arr.view('>i2')[::arr.itemsize // 2]
return i2arr.tobytes() return i2arr.tobytes()
@ -34,15 +78,14 @@ def pack_int2(data: NDArray[numpy.integer] | Sequence[int] | int) -> bytes:
def pack_int4(data: NDArray[numpy.integer] | Sequence[int] | int) -> bytes: def pack_int4(data: NDArray[numpy.integer] | Sequence[int] | int) -> bytes:
arr = numpy.asarray(data) arr = numpy.asarray(data)
if arr.dtype in ( if arr.dtype in CONV_TABLE_i32.keys():
numpy.float64, numpy.float32,
numpy.int64, numpy.uint64,
numpy.int32, numpy.uint32,
):
arr = numpy.require(arr, requirements=('C_CONTIGUOUS', 'ALIGNED', 'WRITEABLE', 'OWNDATA')) arr = numpy.require(arr, requirements=('C_CONTIGUOUS', 'ALIGNED', 'WRITEABLE', 'OWNDATA'))
if arr is data: if arr is data:
arr = numpy.array(arr, copy=True) arr = numpy.array(arr, copy=True)
arr_to_int4(arr)
fn = CONV_TABLE_i32[arr.dtype]
result = fn(arr.ctypes.data_as(fn.argtypes[0]), arr.size)
i4arr = arr.view('>i4')[::arr.itemsize // 4] i4arr = arr.view('>i4')[::arr.itemsize // 4]
return i4arr.tobytes() return i4arr.tobytes()

View File

@ -2,6 +2,32 @@
requires = ["maturin>1.0,<2.0"] requires = ["maturin>1.0,<2.0"]
build-backend = "maturin" build-backend = "maturin"
[project]
[tool.maturin] name = "klamath_rs_ext"
features = ["pyo3/extension-module"] description = "Compiled extensions for klamath GDS library"
#readme = "README.md"
#license = { file = "LICENSE.md" }
authors = [
{ name="Jan Petykiewicz", email="jan@mpxd.net" },
]
homepage = "https://mpxd.net/code/jan/klamath-rs"
repository = "https://mpxd.net/code/jan/klamath-rs"
classifiers = [
"Programming Language :: Python :: 3",
"Development Status :: 4 - Beta",
# "Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Intended Audience :: Information Technology",
"Intended Audience :: Manufacturing",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)",
]
requires-python = ">=3.11"
#include = [
# "LICENSE.md"
# ]
dynamic = ["version"]
dependencies = [
"cffi",
]

View File

@ -7,87 +7,43 @@ pub mod elements;
pub mod library; pub mod library;
//use ndarray; use rust_util::ToInt2BE;
use numpy::{PyArray1, PyUntypedArray, PyUntypedArrayMethods, PyArrayDescrMethods, PyArrayMethods, dtype}; use rust_util::ToInt4BE;
use pyo3::prelude::{Python, pymodule, PyModule, PyResult, Bound, wrap_pyfunction, pyfunction, PyModuleMethods, PyAnyMethods};
use pyo3::exceptions::{PyValueError, PyTypeError};
#[pymodule] macro_rules! mkfun {
fn klamath_rs_ext(m: &Bound<'_, PyModule>) -> PyResult<()> { ( $fname:ident, $tt:ty, $elfn:ident ) => {
m.add_function(wrap_pyfunction!(arr_to_int2, m)?)?; #[no_mangle]
m.add_function(wrap_pyfunction!(arr_to_int4, m)?)?; pub extern "C" fn $fname(arr: *mut $tt, size: usize) -> $tt {
Ok(()) let sl = unsafe { std::slice::from_raw_parts_mut(arr, size) };
for xx in sl.iter_mut() {
let res = <$tt>::$elfn(*xx);
match res {
Err(cc) => return cc,
Ok(cc) => { *xx = cc; },
}
}
0 as $tt
}
}
} }
#[pyfunction] mkfun!(f64_to_i16, f64, convert_to_i2be);
fn arr_to_int2(py: Python<'_>, pyarr: &Bound<'_, PyUntypedArray>) -> PyResult<()> { mkfun!(f32_to_i16, f32, convert_to_i2be);
use rust_util::ToInt2BE; mkfun!(i64_to_i16, i64, convert_to_i2be);
mkfun!(u64_to_i16, u64, convert_to_i2be);
mkfun!(i32_to_i16, i32, convert_to_i2be);
mkfun!(u32_to_i16, u32, convert_to_i2be);
mkfun!(i16_to_i16, i16, convert_to_i2be);
mkfun!(u16_to_i16, u16, convert_to_i2be);
assert!(pyarr.is_c_contiguous(), "Array must be c-contiguous!"); mkfun!(f64_to_i32, f64, convert_to_i4be);
mkfun!(f32_to_i32, f32, convert_to_i4be);
macro_rules! i2if { mkfun!(i64_to_i32, i64, convert_to_i4be);
( $el_type:expr, $tt:ty ) => { mkfun!(u64_to_i32, u64, convert_to_i4be);
if $el_type.is_equiv_to(&dtype::<$tt>(py)) { mkfun!(i32_to_i32, i32, convert_to_i4be);
let arr = pyarr.downcast::<PyArray1<$tt>>()?; mkfun!(u32_to_i32, u32, convert_to_i4be);
let mut array = unsafe { arr.as_array_mut() };
for xx in array.iter_mut() {
*xx = <$tt>::convert_to_i2be(*xx).map_err(
|e| PyValueError::new_err(format!("Invalid value for 2-byte int: {}", e))
)?;
}
return Ok(())
}
}
}
let el_type = pyarr.dtype();
i2if!(el_type, f64);
i2if!(el_type, f32);
i2if!(el_type, i64);
i2if!(el_type, u64);
i2if!(el_type, i32);
i2if!(el_type, u32);
i2if!(el_type, i16);
i2if!(el_type, u16);
Err(PyTypeError::new_err(format!("arr_to_int2 not implemented for type {:?}", el_type)))
}
#[pyfunction]
fn arr_to_int4(py: Python<'_>, pyarr: &Bound<'_, PyUntypedArray>) -> PyResult<()> {
use rust_util::ToInt4BE;
assert!(pyarr.is_c_contiguous(), "Array must be c-contiguous!");
macro_rules! i4if {
( $el_type:expr, $tt:ty ) => {
if $el_type.is_equiv_to(&dtype::<$tt>(py)) {
let arr = pyarr.downcast::<PyArray1<$tt>>()?;
let mut array = unsafe { arr.as_array_mut() };
for xx in array.iter_mut() {
*xx = <$tt>::convert_to_i4be(*xx).map_err(
|e| PyValueError::new_err(format!("Invalid value for 4-byte int: {}", e))
)?;
}
return Ok(())
}
}
}
let el_type = pyarr.dtype();
i4if!(el_type, f64);
i4if!(el_type, f32);
i4if!(el_type, i64);
i4if!(el_type, u64);
i4if!(el_type, i32);
i4if!(el_type, u32);
Err(PyTypeError::new_err(format!("arr_to_int4 not implemented for type {:?}", el_type)))
}
mod rust_util { mod rust_util {