scirs2_core/python/
conversions.rs1#[cfg(feature = "python")]
7use pyo3::prelude::*;
8
9#[cfg(feature = "python")]
10use scirs2_numpy::{
11 Element, PyArray1, PyArray2, PyArrayDyn, PyArrayMethods, PyUntypedArrayMethods,
12};
13
14#[cfg(feature = "python")]
16use ::ndarray::{Array1, Array2, ArrayD};
17
18#[cfg(feature = "python")]
24pub fn py_float_to_f32(obj: &Bound<'_, PyAny>) -> PyResult<f32> {
25 obj.extract::<f64>().map(|v| v as f32)
26}
27
28#[cfg(feature = "python")]
30pub fn py_float_to_f64(obj: &Bound<'_, PyAny>) -> PyResult<f64> {
31 obj.extract::<f64>()
32}
33
34#[cfg(feature = "python")]
36pub fn py_int_to_i32(obj: &Bound<'_, PyAny>) -> PyResult<i32> {
37 obj.extract::<i64>().map(|v| v as i32)
38}
39
40#[cfg(feature = "python")]
42pub fn py_int_to_i64(obj: &Bound<'_, PyAny>) -> PyResult<i64> {
43 obj.extract::<i64>()
44}
45
46#[cfg(feature = "python")]
52pub fn py_list_to_vec_f32(obj: &Bound<'_, PyAny>) -> PyResult<Vec<f32>> {
53 obj.extract::<Vec<f64>>()
54 .map(|v| v.into_iter().map(|x| x as f32).collect())
55}
56
57#[cfg(feature = "python")]
59pub fn py_list_to_vec_i32(obj: &Bound<'_, PyAny>) -> PyResult<Vec<i32>> {
60 obj.extract::<Vec<i64>>()
61 .map(|v| v.into_iter().map(|x| x as i32).collect())
62}
63
64#[cfg(feature = "python")]
66pub fn py_list_to_vec_usize(obj: &Bound<'_, PyAny>) -> PyResult<Vec<usize>> {
67 obj.extract::<Vec<i64>>()
68 .map(|v| v.into_iter().map(|x| x as usize).collect())
69}
70
71#[cfg(feature = "python")]
77pub fn py_shape_to_vec(obj: &Bound<'_, PyAny>) -> PyResult<Vec<usize>> {
78 py_list_to_vec_usize(obj)
79}
80
81#[cfg(feature = "python")]
83pub fn shape_to_py_tuple(shape: &[usize], py: Python<'_>) -> PyResult<Py<pyo3::types::PyTuple>> {
84 Ok(pyo3::types::PyTuple::new(py, shape)?.unbind())
85}
86
87#[cfg(feature = "python")]
93pub fn numpy_to_scirs_array1<T: Element + Clone>(
94 array: &Bound<'_, scirs2_numpy::PyArray1<T>>,
95) -> PyResult<Array1<T>> {
96 let readonly = array.readonly();
97 Ok(readonly.as_array().to_owned())
98}
99
100#[cfg(feature = "python")]
102pub fn numpy_to_scirs_array2<T: Element + Clone>(
103 array: &Bound<'_, scirs2_numpy::PyArray2<T>>,
104) -> PyResult<Array2<T>> {
105 let readonly = array.readonly();
106 Ok(readonly.as_array().to_owned())
107}
108
109#[cfg(feature = "python")]
111pub fn scirs_array1_to_numpy<T: Element>(
112 array: Array1<T>,
113 py: Python<'_>,
114) -> PyResult<Py<scirs2_numpy::PyArray1<T>>> {
115 Ok(scirs2_numpy::PyArray1::from_owned_array(py, array).unbind())
116}
117
118#[cfg(feature = "python")]
120pub fn scirs_array2_to_numpy<T: Element>(
121 array: Array2<T>,
122 py: Python<'_>,
123) -> PyResult<Py<scirs2_numpy::PyArray2<T>>> {
124 Ok(scirs2_numpy::PyArray2::from_owned_array(py, array).unbind())
125}
126
127#[cfg(feature = "python")]
133pub fn rust_dtype_to_numpy_str<T: 'static>() -> &'static str {
134 use std::any::TypeId;
135
136 match TypeId::of::<T>() {
137 t if t == TypeId::of::<f32>() => "float32",
138 t if t == TypeId::of::<f64>() => "float64",
139 t if t == TypeId::of::<i8>() => "int8",
140 t if t == TypeId::of::<i16>() => "int16",
141 t if t == TypeId::of::<i32>() => "int32",
142 t if t == TypeId::of::<i64>() => "int64",
143 t if t == TypeId::of::<u8>() => "uint8",
144 t if t == TypeId::of::<u16>() => "uint16",
145 t if t == TypeId::of::<u32>() => "uint32",
146 t if t == TypeId::of::<u64>() => "uint64",
147 _ => "unknown",
148 }
149}
150
151#[cfg(feature = "python")]
157pub fn validate_numpy_array<T: Element>(array: &Bound<'_, PyArrayDyn<T>>) -> PyResult<()> {
158 let shape = array.shape();
159
160 if shape.is_empty() {
162 return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
163 "Array must have at least one dimension",
164 ));
165 }
166
167 if shape.contains(&0) {
169 return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
170 "Array dimensions must be non-zero",
171 ));
172 }
173
174 Ok(())
175}
176
177#[cfg(feature = "python")]
183#[derive(Debug, Clone, Copy, PartialEq, Eq)]
184pub struct MemoryLayoutInfo {
185 pub is_c_contiguous: bool,
186 pub is_f_contiguous: bool,
187 pub itemsize: usize,
188 pub ndim: usize,
189}
190
191#[cfg(feature = "python")]
193pub fn get_memory_layout_info<T: Element>(array: &Bound<'_, PyArrayDyn<T>>) -> MemoryLayoutInfo {
194 MemoryLayoutInfo {
195 is_c_contiguous: array.is_c_contiguous(),
196 is_f_contiguous: array.is_fortran_contiguous(),
197 itemsize: std::mem::size_of::<T>(),
198 ndim: array.ndim(),
199 }
200}
201
202#[cfg(all(test, feature = "python"))]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn test_dtype_strings() {
212 assert_eq!(rust_dtype_to_numpy_str::<f32>(), "float32");
213 assert_eq!(rust_dtype_to_numpy_str::<f64>(), "float64");
214 assert_eq!(rust_dtype_to_numpy_str::<i32>(), "int32");
215 assert_eq!(rust_dtype_to_numpy_str::<i64>(), "int64");
216 }
217
218 #[test]
219 fn test_shape_conversion() {
220 Python::with_gil(|py| {
221 let shape = vec![2, 3, 4];
222 let py_tuple = shape_to_py_tuple(&shape, py).expect("Operation failed");
223 let bound_tuple = py_tuple.bind(py);
224
225 assert_eq!(bound_tuple.len(), 3);
226 assert_eq!(
227 bound_tuple
228 .get_item(0)
229 .expect("Operation failed")
230 .extract::<usize>()
231 .expect("Operation failed"),
232 2
233 );
234 assert_eq!(
235 bound_tuple
236 .get_item(1)
237 .expect("Operation failed")
238 .extract::<usize>()
239 .expect("Operation failed"),
240 3
241 );
242 assert_eq!(
243 bound_tuple
244 .get_item(2)
245 .expect("Operation failed")
246 .extract::<usize>()
247 .expect("Operation failed"),
248 4
249 );
250 });
251 }
252}