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]
name = "klamath_rs_ext"
version = "0.1.0"
version = "0.2.0"
authors = ["jan <jan@mpxd.net>"]
edition = "2021"
@ -12,5 +12,3 @@ crate-type = ["cdylib", "rlib"]
[dependencies]
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
__version__ = 0.1
__version__ = 0.2

View File

@ -1,24 +1,68 @@
from collections.abc import Sequence
import ctypes
from pathlib import Path
from itertools import chain
import numpy
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:
arr = numpy.asarray(data)
if arr.dtype in (
numpy.float64, numpy.float32,
numpy.int64, numpy.uint64,
numpy.int32, numpy.uint32,
numpy.int16, numpy.uint16,
):
if arr.dtype in CONV_TABLE_i16.keys():
arr = numpy.require(arr, requirements=('C_CONTIGUOUS', 'ALIGNED', 'WRITEABLE', 'OWNDATA'))
if arr is data:
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]
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:
arr = numpy.asarray(data)
if arr.dtype in (
numpy.float64, numpy.float32,
numpy.int64, numpy.uint64,
numpy.int32, numpy.uint32,
):
if arr.dtype in CONV_TABLE_i32.keys():
arr = numpy.require(arr, requirements=('C_CONTIGUOUS', 'ALIGNED', 'WRITEABLE', 'OWNDATA'))
if arr is data:
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]
return i4arr.tobytes()

View File

@ -2,6 +2,32 @@
requires = ["maturin>1.0,<2.0"]
build-backend = "maturin"
[tool.maturin]
features = ["pyo3/extension-module"]
[project]
name = "klamath_rs_ext"
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;
//use ndarray;
use numpy::{PyArray1, PyUntypedArray, PyUntypedArrayMethods, PyArrayDescrMethods, PyArrayMethods, dtype};
use pyo3::prelude::{Python, pymodule, PyModule, PyResult, Bound, wrap_pyfunction, pyfunction, PyModuleMethods, PyAnyMethods};
use pyo3::exceptions::{PyValueError, PyTypeError};
use rust_util::ToInt2BE;
use rust_util::ToInt4BE;
#[pymodule]
fn klamath_rs_ext(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(arr_to_int2, m)?)?;
m.add_function(wrap_pyfunction!(arr_to_int4, m)?)?;
Ok(())
macro_rules! mkfun {
( $fname:ident, $tt:ty, $elfn:ident ) => {
#[no_mangle]
pub extern "C" fn $fname(arr: *mut $tt, size: usize) -> $tt {
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]
fn arr_to_int2(py: Python<'_>, pyarr: &Bound<'_, PyUntypedArray>) -> PyResult<()> {
use rust_util::ToInt2BE;
mkfun!(f64_to_i16, f64, convert_to_i2be);
mkfun!(f32_to_i16, f32, convert_to_i2be);
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!");
macro_rules! i2if {
( $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_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)))
}
mkfun!(f64_to_i32, f64, convert_to_i4be);
mkfun!(f32_to_i32, f32, convert_to_i4be);
mkfun!(i64_to_i32, i64, convert_to_i4be);
mkfun!(u64_to_i32, u64, convert_to_i4be);
mkfun!(i32_to_i32, i32, convert_to_i4be);
mkfun!(u32_to_i32, u32, convert_to_i4be);
mod rust_util {