Path: blob/main/crates/polars-python/src/series/numpy_ufunc.rs
7889 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.downcast_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}9394let size = self.len();95let (out_array, av) =96unsafe { aligned_array::<<$type as PolarsNumericType>::Native>(py, size) };9798debug_assert_eq!(get_refcnt(&out_array), 1);99// inserting it in a tuple increase the reference count by 1.100let args = PyTuple::new(py, std::slice::from_ref(&out_array))?;101debug_assert_eq!(get_refcnt(&out_array), 2);102103// whatever the result, we must take the leaked memory ownership back104let s = match lambda.call1(args) {105Ok(_) => {106// if this assert fails, the lambda has taken a reference to the object, so we must panic107// args and the lambda return have a reference, making a total of 3108assert!(get_refcnt(&out_array) <= 3);109110let s = self.series.read();111let validity = s.chunks()[0].validity().cloned();112let ca = ChunkedArray::<$type>::from_vec_validity(113s.name().clone(),114av,115validity,116);117PySeries::new(ca.into_series())118},119Err(e) => {120// return error information121return Err(e);122},123};124125Ok(s)126})127}128}129};130}131132impl_ufuncs!(apply_ufunc_f32, Float32Type, unsafe_from_ptr_f32);133impl_ufuncs!(apply_ufunc_f64, Float64Type, unsafe_from_ptr_f64);134impl_ufuncs!(apply_ufunc_u8, UInt8Type, unsafe_from_ptr_u8);135impl_ufuncs!(apply_ufunc_u16, UInt16Type, unsafe_from_ptr_u16);136impl_ufuncs!(apply_ufunc_u32, UInt32Type, unsafe_from_ptr_u32);137impl_ufuncs!(apply_ufunc_u64, UInt64Type, unsafe_from_ptr_u64);138impl_ufuncs!(apply_ufunc_i8, Int8Type, unsafe_from_ptr_i8);139impl_ufuncs!(apply_ufunc_i16, Int16Type, unsafe_from_ptr_i16);140impl_ufuncs!(apply_ufunc_i32, Int32Type, unsafe_from_ptr_i32);141impl_ufuncs!(apply_ufunc_i64, Int64Type, unsafe_from_ptr_i64);142143144