Path: blob/main/crates/polars-python/src/series/numpy_ufunc.rs
8364 views
#![allow(unsafe_op_in_unsafe_fn)]1use std::ptr;23use ndarray::IntoDimension;4use numpy::npyffi::types::npy_intp;5use numpy::npyffi::{self, flags};6use numpy::{Element, PY_ARRAY_API, PyArray1, PyArrayDescrMethods, ToNpyDims};7use polars_core::prelude::*;8use polars_core::utils::arrow::types::NativeType;9use pyo3::prelude::*;10use pyo3::types::{PyNone, PyTuple};1112use super::PySeries;1314/// Create an empty numpy array arrows 64 byte alignment15///16/// # Safety17/// All elements in the array are non initialized18///19/// The array is also writable from Python.20unsafe fn aligned_array<T: Element + NativeType>(21py: Python<'_>,22size: usize,23) -> (Bound<'_, PyArray1<T>>, Vec<T>) {24let mut buf = vec![T::default(); size];2526// modified from27// numpy-0.10.0/src/array.rs:3752829let len = buf.len();30let buffer_ptr = buf.as_mut_ptr();3132let mut dims = [len].into_dimension();33let strides = [size_of::<T>() as npy_intp];3435let ptr = PY_ARRAY_API.PyArray_NewFromDescr(36py,37PY_ARRAY_API.get_type_object(py, npyffi::NpyTypes::PyArray_Type),38T::get_dtype(py).into_dtype_ptr(),39dims.ndim_cint(),40dims.as_dims_ptr(),41strides.as_ptr() as *mut _, // strides42buffer_ptr as _, // data43flags::NPY_ARRAY_OUT_ARRAY, // flag44ptr::null_mut(), //obj45);46(47Bound::from_owned_ptr(py, ptr)48.cast_into_exact::<PyArray1<T>>()49.unwrap(),50buf,51)52}5354/// Get reference counter for numpy arrays.55/// - For CPython: Get reference counter.56/// - For PyPy: Reference counters for a live PyPy object = refcnt + 2 << 60.57fn get_refcnt<T>(pyarray: &Bound<'_, PyArray1<T>>) -> isize {58let refcnt = pyarray.get_refcnt();59#[cfg(target_pointer_width = "64")]60if refcnt >= (2 << 60) {61return refcnt - (2 << 60);62}63refcnt64}6566macro_rules! impl_ufuncs {67($name:ident, $type:ident, $unsafe_from_ptr_method:ident) => {68#[pymethods]69impl PySeries {70// Applies a ufunc by accepting a lambda out: ufunc(*args, out=out).71//72// If allocate_out is true, the out array is allocated in this73// method, send to Python and once the ufunc is applied ownership is74// taken by Rust again to prevent memory leak. if the ufunc fails,75// we first must take ownership back.76//77// If allocate_out is false, the out parameter to the lambda will be78// None, meaning the ufunc will allocate memory itself. We will then79// have to convert that NumPy array into a pl.Series.80fn $name(&self, lambda: &Bound<PyAny>, allocate_out: bool) -> PyResult<PySeries> {81// numpy array object, and a *mut ptr82Python::attach(|py| {83if !allocate_out {84// We're not going to allocate the output array.85// Instead, we'll let the ufunc do it.86let result = lambda.call1((PyNone::get(py),))?;87let series_factory = crate::py_modules::pl_series(py).bind(py);88return series_factory89.call((self.name(), result), None)?90.getattr("_s")?91.extract::<PySeries>()92.map_err(PyErr::from);93}9495let size = self.len();96let (out_array, av) =97unsafe { aligned_array::<<$type as PolarsNumericType>::Native>(py, size) };9899debug_assert_eq!(get_refcnt(&out_array), 1);100// inserting it in a tuple increase the reference count by 1.101let args = PyTuple::new(py, std::slice::from_ref(&out_array))?;102debug_assert_eq!(get_refcnt(&out_array), 2);103104// whatever the result, we must take the leaked memory ownership back105let s = match lambda.call1(args) {106Ok(_) => {107// if this assert fails, the lambda has taken a reference to the object, so we must panic108// args and the lambda return have a reference, making a total of 3109assert!(get_refcnt(&out_array) <= 3);110111let s = self.series.read();112let validity = s.chunks()[0].validity().cloned();113let ca = ChunkedArray::<$type>::from_vec_validity(114s.name().clone(),115av,116validity,117);118PySeries::new(ca.into_series())119},120Err(e) => {121// return error information122return Err(e);123},124};125126Ok(s)127})128}129}130};131}132133impl_ufuncs!(apply_ufunc_f32, Float32Type, unsafe_from_ptr_f32);134impl_ufuncs!(apply_ufunc_f64, Float64Type, unsafe_from_ptr_f64);135impl_ufuncs!(apply_ufunc_u8, UInt8Type, unsafe_from_ptr_u8);136impl_ufuncs!(apply_ufunc_u16, UInt16Type, unsafe_from_ptr_u16);137impl_ufuncs!(apply_ufunc_u32, UInt32Type, unsafe_from_ptr_u32);138impl_ufuncs!(apply_ufunc_u64, UInt64Type, unsafe_from_ptr_u64);139impl_ufuncs!(apply_ufunc_i8, Int8Type, unsafe_from_ptr_i8);140impl_ufuncs!(apply_ufunc_i16, Int16Type, unsafe_from_ptr_i16);141impl_ufuncs!(apply_ufunc_i32, Int32Type, unsafe_from_ptr_i32);142impl_ufuncs!(apply_ufunc_i64, Int64Type, unsafe_from_ptr_i64);143144145