numpy/array.rs
1//! Safe interface for NumPy's [N-dimensional arrays][ndarray]
2//!
3//! [ndarray]: https://numpy.org/doc/stable/reference/arrays.ndarray.html
4
5use std::{
6 marker::PhantomData,
7 mem,
8 os::raw::{c_int, c_void},
9 ptr, slice,
10};
11
12use ndarray::{
13 Array, ArrayBase, ArrayView, ArrayViewMut, Axis, Data, Dim, Dimension, IntoDimension, Ix0, Ix1,
14 Ix2, Ix3, Ix4, Ix5, Ix6, IxDyn, RawArrayView, RawArrayViewMut, RawData, ShapeBuilder,
15 StrideShape,
16};
17use num_traits::AsPrimitive;
18use pyo3::{
19 ffi,
20 types::{DerefToPyAny, PyModule},
21 Bound, CastError, Py, PyAny, PyErr, PyResult, PyTypeCheck, PyTypeInfo, Python,
22};
23
24use crate::borrow::{PyReadonlyArray, PyReadwriteArray};
25use crate::cold;
26use crate::convert::{ArrayExt, IntoPyArray, NpyIndex, ToNpyDims, ToPyArray};
27use crate::dtype::{Element, PyArrayDescrMethods};
28use crate::error::{
29 AsSliceError, BorrowError, DimensionalityError, FromVecError, IgnoreError, TypeError,
30 DIMENSIONALITY_MISMATCH_ERR, MAX_DIMENSIONALITY_ERR,
31};
32use crate::npyffi::{self, npy_intp, NPY_ORDER, PY_ARRAY_API};
33use crate::slice_container::PySliceContainer;
34use crate::untyped_array::{PyUntypedArray, PyUntypedArrayMethods};
35
36/// A safe, statically-typed wrapper for NumPy's [`ndarray`][ndarray] class.
37///
38/// # Memory location
39///
40/// - Allocated by Rust: Constructed via [`IntoPyArray`] or
41/// [`from_vec`][Self::from_vec] or [`from_owned_array`][Self::from_owned_array].
42///
43/// These methods transfers ownership of the Rust allocation into a suitable Python object
44/// and uses the memory as the internal buffer backing the NumPy array.
45///
46/// Please note that some destructive methods like [`resize`][Self::resize] will fail
47/// when used with this kind of array as NumPy cannot reallocate the internal buffer.
48///
49/// - Allocated by NumPy: Constructed via other methods, like [`ToPyArray`] or
50/// [`from_slice`][Self::from_slice] or [`from_array`][Self::from_array].
51///
52/// These methods allocate memory in Python's private heap via NumPy's API.
53///
54/// In both cases, `PyArray` is managed by Python so it can neither be moved from
55/// nor deallocated manually.
56///
57/// # References
58///
59/// Like [`new`][Self::new], all constructor methods of `PyArray` return a shared reference `&PyArray`
60/// instead of an owned value. This design follows [PyO3's ownership concept][pyo3-memory],
61/// i.e. the return value is GIL-bound owning reference into Python's heap.
62///
63/// # Element type and dimensionality
64///
65/// `PyArray` has two type parametes `T` and `D`.
66/// `T` represents the type of its elements, e.g. [`f32`] or [`PyObject`].
67/// `D` represents its dimensionality, e.g [`Ix2`][type@Ix2] or [`IxDyn`][type@IxDyn].
68///
69/// Element types are Rust types which implement the [`Element`] trait.
70/// Dimensions are represented by the [`ndarray::Dimension`] trait.
71///
72/// Typically, `Ix1, Ix2, ...` are used for fixed dimensionality arrays,
73/// and `IxDyn` is used for dynamic dimensionality arrays. Type aliases
74/// for combining `PyArray` with these types are provided, e.g. [`PyArray1`] or [`PyArrayDyn`].
75///
76/// To specify concrete dimension like `3×4×5`, types which implement the [`ndarray::IntoDimension`]
77/// trait are used. Typically, this means arrays like `[3, 4, 5]` or tuples like `(3, 4, 5)`.
78///
79/// # Example
80///
81/// ```
82/// use numpy::{PyArray, PyArrayMethods};
83/// use ndarray::{array, Array};
84/// use pyo3::Python;
85///
86/// Python::attach(|py| {
87/// let pyarray = PyArray::arange(py, 0., 4., 1.).reshape([2, 2]).unwrap();
88/// let array = array![[3., 4.], [5., 6.]];
89///
90/// assert_eq!(
91/// array.dot(&pyarray.readonly().as_array()),
92/// array![[8., 15.], [12., 23.]]
93/// );
94/// });
95/// ```
96///
97/// [ndarray]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html
98/// [pyo3-memory]: https://pyo3.rs/main/memory.html
99#[repr(transparent)]
100pub struct PyArray<T, D>(PyAny, PhantomData<T>, PhantomData<D>);
101
102/// Zero-dimensional array.
103pub type PyArray0<T> = PyArray<T, Ix0>;
104/// One-dimensional array.
105pub type PyArray1<T> = PyArray<T, Ix1>;
106/// Two-dimensional array.
107pub type PyArray2<T> = PyArray<T, Ix2>;
108/// Three-dimensional array.
109pub type PyArray3<T> = PyArray<T, Ix3>;
110/// Four-dimensional array.
111pub type PyArray4<T> = PyArray<T, Ix4>;
112/// Five-dimensional array.
113pub type PyArray5<T> = PyArray<T, Ix5>;
114/// Six-dimensional array.
115pub type PyArray6<T> = PyArray<T, Ix6>;
116/// Dynamic-dimensional array.
117pub type PyArrayDyn<T> = PyArray<T, IxDyn>;
118
119/// Returns a handle to NumPy's multiarray module.
120pub fn get_array_module<'py>(py: Python<'py>) -> PyResult<Bound<'py, PyModule>> {
121 PyModule::import(py, npyffi::array::mod_name(py)?)
122}
123
124impl<T, D> DerefToPyAny for PyArray<T, D> {}
125
126unsafe impl<T: Element, D: Dimension> PyTypeInfo for PyArray<T, D> {
127 const NAME: &'static str = "PyArray<T, D>";
128 const MODULE: Option<&'static str> = Some("numpy");
129
130 fn type_object_raw<'py>(py: Python<'py>) -> *mut ffi::PyTypeObject {
131 unsafe { npyffi::PY_ARRAY_API.get_type_object(py, npyffi::NpyTypes::PyArray_Type) }
132 }
133
134 fn is_type_of(ob: &Bound<'_, PyAny>) -> bool {
135 Self::extract::<IgnoreError>(ob).is_ok()
136 }
137}
138
139impl<T: Element, D: Dimension> PyArray<T, D> {
140 fn extract<'a, 'py, E>(ob: &'a Bound<'py, PyAny>) -> Result<&'a Bound<'py, Self>, E>
141 where
142 E: From<CastError<'a, 'py>> + From<DimensionalityError> + From<TypeError<'py>>,
143 {
144 // Check if the object is an array.
145 let array = unsafe {
146 if npyffi::PyArray_Check(ob.py(), ob.as_ptr()) == 0 {
147 return Err(CastError::new(
148 ob.as_borrowed(),
149 <Self as PyTypeCheck>::classinfo_object(ob.py()),
150 )
151 .into());
152 }
153 ob.cast_unchecked::<Self>()
154 };
155
156 // Check if the dimensionality matches `D`.
157 let src_ndim = array.ndim();
158 if let Some(dst_ndim) = D::NDIM {
159 if src_ndim != dst_ndim {
160 return Err(DimensionalityError::new(src_ndim, dst_ndim).into());
161 }
162 }
163
164 // Check if the element type matches `T`.
165 let src_dtype = array.dtype();
166 let dst_dtype = T::get_dtype(ob.py());
167 if !src_dtype.is_equiv_to(&dst_dtype) {
168 return Err(TypeError::new(src_dtype, dst_dtype).into());
169 }
170
171 Ok(array)
172 }
173
174 /// Creates a new uninitialized NumPy array.
175 ///
176 /// If `is_fortran` is true, then it has Fortran/column-major order,
177 /// otherwise it has C/row-major order.
178 ///
179 /// # Safety
180 ///
181 /// The returned array will always be safe to be dropped as the elements must either
182 /// be trivially copyable (as indicated by `<T as Element>::IS_COPY`) or be pointers
183 /// into Python's heap, which NumPy will automatically zero-initialize.
184 ///
185 /// However, the elements themselves will not be valid and should be initialized manually
186 /// using raw pointers obtained via [`uget_raw`][Self::uget_raw]. Before that, all methods
187 /// which produce references to the elements invoke undefined behaviour. In particular,
188 /// zero-initialized pointers are _not_ valid instances of `PyObject`.
189 ///
190 /// # Example
191 ///
192 /// ```
193 /// use numpy::prelude::*;
194 /// use numpy::PyArray3;
195 /// use pyo3::Python;
196 ///
197 /// Python::attach(|py| {
198 /// let arr = unsafe {
199 /// let arr = PyArray3::<i32>::new(py, [4, 5, 6], false);
200 ///
201 /// for i in 0..4 {
202 /// for j in 0..5 {
203 /// for k in 0..6 {
204 /// arr.uget_raw([i, j, k]).write((i * j * k) as i32);
205 /// }
206 /// }
207 /// }
208 ///
209 /// arr
210 /// };
211 ///
212 /// assert_eq!(arr.shape(), &[4, 5, 6]);
213 /// });
214 /// ```
215 pub unsafe fn new<'py, ID>(py: Python<'py>, dims: ID, is_fortran: bool) -> Bound<'py, Self>
216 where
217 ID: IntoDimension<Dim = D>,
218 {
219 let flags = c_int::from(is_fortran);
220 Self::new_uninit(py, dims, ptr::null_mut(), flags)
221 }
222
223 pub(crate) unsafe fn new_uninit<'py, ID>(
224 py: Python<'py>,
225 dims: ID,
226 strides: *const npy_intp,
227 flag: c_int,
228 ) -> Bound<'py, Self>
229 where
230 ID: IntoDimension<Dim = D>,
231 {
232 let mut dims = dims.into_dimension();
233 let ptr = PY_ARRAY_API.PyArray_NewFromDescr(
234 py,
235 PY_ARRAY_API.get_type_object(py, npyffi::NpyTypes::PyArray_Type),
236 T::get_dtype(py).into_dtype_ptr(),
237 dims.ndim_cint(),
238 dims.as_dims_ptr(),
239 strides as *mut npy_intp, // strides
240 ptr::null_mut(), // data
241 flag, // flag
242 ptr::null_mut(), // obj
243 );
244
245 Bound::from_owned_ptr(py, ptr).cast_into_unchecked()
246 }
247
248 unsafe fn new_with_data<'py, ID>(
249 py: Python<'py>,
250 dims: ID,
251 strides: *const npy_intp,
252 data_ptr: *const T,
253 container: *mut PyAny,
254 ) -> Bound<'py, Self>
255 where
256 ID: IntoDimension<Dim = D>,
257 {
258 let mut dims = dims.into_dimension();
259 let ptr = PY_ARRAY_API.PyArray_NewFromDescr(
260 py,
261 PY_ARRAY_API.get_type_object(py, npyffi::NpyTypes::PyArray_Type),
262 T::get_dtype(py).into_dtype_ptr(),
263 dims.ndim_cint(),
264 dims.as_dims_ptr(),
265 strides as *mut npy_intp, // strides
266 data_ptr as *mut c_void, // data
267 npyffi::NPY_ARRAY_WRITEABLE, // flag
268 ptr::null_mut(), // obj
269 );
270
271 PY_ARRAY_API.PyArray_SetBaseObject(
272 py,
273 ptr as *mut npyffi::PyArrayObject,
274 container as *mut ffi::PyObject,
275 );
276
277 Bound::from_owned_ptr(py, ptr).cast_into_unchecked()
278 }
279
280 pub(crate) unsafe fn from_raw_parts<'py>(
281 py: Python<'py>,
282 dims: D,
283 strides: *const npy_intp,
284 data_ptr: *const T,
285 container: PySliceContainer,
286 ) -> Bound<'py, Self> {
287 let container = Bound::new(py, container)
288 .expect("Failed to create slice container")
289 .into_ptr();
290
291 Self::new_with_data(py, dims, strides, data_ptr, container.cast())
292 }
293
294 /// Creates a NumPy array backed by `array` and ties its ownership to the Python object `container`.
295 ///
296 /// The resulting NumPy array will be writeable from Python space. If this is undesireable, use
297 /// [PyReadwriteArray::make_nonwriteable].
298 ///
299 /// # Safety
300 ///
301 /// `container` is set as a base object of the returned array which must not be dropped until `container` is dropped.
302 /// Furthermore, `array` must not be reallocated from the time this method is called and until `container` is dropped.
303 ///
304 /// # Example
305 ///
306 /// ```rust
307 /// # use pyo3::prelude::*;
308 /// # use numpy::{ndarray::Array1, PyArray1};
309 /// #
310 /// #[pyclass]
311 /// struct Owner {
312 /// array: Array1<f64>,
313 /// }
314 ///
315 /// #[pymethods]
316 /// impl Owner {
317 /// #[getter]
318 /// fn array<'py>(this: Bound<'py, Self>) -> Bound<'py, PyArray1<f64>> {
319 /// let array = &this.borrow().array;
320 ///
321 /// // SAFETY: The memory backing `array` will stay valid as long as this object is alive
322 /// // as we do not modify `array` in any way which would cause it to be reallocated.
323 /// unsafe { PyArray1::borrow_from_array(array, this.into_any()) }
324 /// }
325 /// }
326 /// ```
327 pub unsafe fn borrow_from_array<'py, S>(
328 array: &ArrayBase<S, D>,
329 container: Bound<'py, PyAny>,
330 ) -> Bound<'py, Self>
331 where
332 S: Data<Elem = T>,
333 {
334 let (strides, dims) = (array.npy_strides(), array.raw_dim());
335 let data_ptr = array.as_ptr();
336
337 let py = container.py();
338
339 Self::new_with_data(
340 py,
341 dims,
342 strides.as_ptr(),
343 data_ptr,
344 container.into_ptr().cast(),
345 )
346 }
347
348 /// Construct a new NumPy array filled with zeros.
349 ///
350 /// If `is_fortran` is true, then it has Fortran/column-major order,
351 /// otherwise it has C/row-major order.
352 ///
353 /// For arrays of Python objects, this will fill the array
354 /// with valid pointers to zero-valued Python integer objects.
355 ///
356 /// See also [`numpy.zeros`][numpy-zeros] and [`PyArray_Zeros`][PyArray_Zeros].
357 ///
358 /// # Example
359 ///
360 /// ```
361 /// use numpy::{PyArray2, PyArrayMethods};
362 /// use pyo3::Python;
363 ///
364 /// Python::attach(|py| {
365 /// let pyarray = PyArray2::<usize>::zeros(py, [2, 2], true);
366 ///
367 /// assert_eq!(pyarray.readonly().as_slice().unwrap(), [0; 4]);
368 /// });
369 /// ```
370 ///
371 /// [numpy-zeros]: https://numpy.org/doc/stable/reference/generated/numpy.zeros.html
372 /// [PyArray_Zeros]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_Zeros
373 pub fn zeros<ID>(py: Python<'_>, dims: ID, is_fortran: bool) -> Bound<'_, Self>
374 where
375 ID: IntoDimension<Dim = D>,
376 {
377 let mut dims = dims.into_dimension();
378 unsafe {
379 let ptr = PY_ARRAY_API.PyArray_Zeros(
380 py,
381 dims.ndim_cint(),
382 dims.as_dims_ptr(),
383 T::get_dtype(py).into_dtype_ptr(),
384 if is_fortran { -1 } else { 0 },
385 );
386 Bound::from_owned_ptr(py, ptr).cast_into_unchecked()
387 }
388 }
389
390 /// Constructs a NumPy from an [`ndarray::Array`]
391 ///
392 /// This method uses the internal [`Vec`] of the [`ndarray::Array`] as the base object of the NumPy array.
393 ///
394 /// # Example
395 ///
396 /// ```
397 /// use numpy::{PyArray, PyArrayMethods};
398 /// use ndarray::array;
399 /// use pyo3::Python;
400 ///
401 /// Python::attach(|py| {
402 /// let pyarray = PyArray::from_owned_array(py, array![[1, 2], [3, 4]]);
403 ///
404 /// assert_eq!(pyarray.readonly().as_array(), array![[1, 2], [3, 4]]);
405 /// });
406 /// ```
407 pub fn from_owned_array(py: Python<'_>, mut arr: Array<T, D>) -> Bound<'_, Self> {
408 let (strides, dims) = (arr.npy_strides(), arr.raw_dim());
409 let data_ptr = arr.as_mut_ptr();
410 unsafe {
411 Self::from_raw_parts(
412 py,
413 dims,
414 strides.as_ptr(),
415 data_ptr,
416 PySliceContainer::from(arr),
417 )
418 }
419 }
420
421 /// Construct a NumPy array from a [`ndarray::ArrayBase`].
422 ///
423 /// This method allocates memory in Python's heap via the NumPy API,
424 /// and then copies all elements of the array there.
425 ///
426 /// # Example
427 ///
428 /// ```
429 /// use numpy::{PyArray, PyArrayMethods};
430 /// use ndarray::array;
431 /// use pyo3::Python;
432 ///
433 /// Python::attach(|py| {
434 /// let pyarray = PyArray::from_array(py, &array![[1, 2], [3, 4]]);
435 ///
436 /// assert_eq!(pyarray.readonly().as_array(), array![[1, 2], [3, 4]]);
437 /// });
438 /// ```
439 pub fn from_array<'py, S>(py: Python<'py>, arr: &ArrayBase<S, D>) -> Bound<'py, Self>
440 where
441 S: Data<Elem = T>,
442 {
443 ToPyArray::to_pyarray(arr, py)
444 }
445}
446
447impl<D: Dimension> PyArray<Py<PyAny>, D> {
448 /// Construct a NumPy array containing objects stored in a [`ndarray::Array`]
449 ///
450 /// This method uses the internal [`Vec`] of the [`ndarray::Array`] as the base object of the NumPy array.
451 ///
452 /// # Example
453 ///
454 /// ```
455 /// use ndarray::array;
456 /// use pyo3::{pyclass, Py, Python, types::PyAnyMethods};
457 /// use numpy::{PyArray, PyArrayMethods};
458 ///
459 /// #[pyclass]
460 /// # #[allow(dead_code)]
461 /// struct CustomElement {
462 /// foo: i32,
463 /// bar: f64,
464 /// }
465 ///
466 /// Python::attach(|py| {
467 /// let array = array![
468 /// Py::new(py, CustomElement {
469 /// foo: 1,
470 /// bar: 2.0,
471 /// }).unwrap(),
472 /// Py::new(py, CustomElement {
473 /// foo: 3,
474 /// bar: 4.0,
475 /// }).unwrap(),
476 /// ];
477 ///
478 /// let pyarray = PyArray::from_owned_object_array(py, array);
479 ///
480 /// assert!(pyarray.readonly().as_array().get(0).unwrap().bind(py).is_instance_of::<CustomElement>());
481 /// });
482 /// ```
483 pub fn from_owned_object_array<T>(py: Python<'_>, mut arr: Array<Py<T>, D>) -> Bound<'_, Self> {
484 let (strides, dims) = (arr.npy_strides(), arr.raw_dim());
485 let data_ptr = arr.as_mut_ptr().cast::<Py<PyAny>>().cast_const();
486 unsafe {
487 Self::from_raw_parts(
488 py,
489 dims,
490 strides.as_ptr(),
491 data_ptr,
492 PySliceContainer::from(arr),
493 )
494 }
495 }
496}
497
498impl<T: Element> PyArray<T, Ix1> {
499 /// Construct a one-dimensional array from a [mod@slice].
500 ///
501 /// # Example
502 ///
503 /// ```
504 /// use numpy::{PyArray, PyArrayMethods};
505 /// use pyo3::Python;
506 ///
507 /// Python::attach(|py| {
508 /// let slice = &[1, 2, 3, 4, 5];
509 /// let pyarray = PyArray::from_slice(py, slice);
510 /// assert_eq!(pyarray.readonly().as_slice().unwrap(), &[1, 2, 3, 4, 5]);
511 /// });
512 /// ```
513 pub fn from_slice<'py>(py: Python<'py>, slice: &[T]) -> Bound<'py, Self> {
514 unsafe {
515 let array = PyArray::new(py, [slice.len()], false);
516 let mut data_ptr = array.data();
517 clone_elements(py, slice, &mut data_ptr);
518 array
519 }
520 }
521
522 /// Construct a one-dimensional array from a [`Vec<T>`][Vec].
523 ///
524 /// # Example
525 ///
526 /// ```
527 /// use numpy::{PyArray, PyArrayMethods};
528 /// use pyo3::Python;
529 ///
530 /// Python::attach(|py| {
531 /// let vec = vec![1, 2, 3, 4, 5];
532 /// let pyarray = PyArray::from_vec(py, vec);
533 /// assert_eq!(pyarray.readonly().as_slice().unwrap(), &[1, 2, 3, 4, 5]);
534 /// });
535 /// ```
536 #[inline(always)]
537 pub fn from_vec<'py>(py: Python<'py>, vec: Vec<T>) -> Bound<'py, Self> {
538 vec.into_pyarray(py)
539 }
540
541 /// Construct a one-dimensional array from an [`Iterator`].
542 ///
543 /// If no reliable [`size_hint`][Iterator::size_hint] is available,
544 /// this method can allocate memory multiple times, which can hurt performance.
545 ///
546 /// # Example
547 ///
548 /// ```
549 /// use numpy::{PyArray, PyArrayMethods};
550 /// use pyo3::Python;
551 ///
552 /// Python::attach(|py| {
553 /// let pyarray = PyArray::from_iter(py, "abcde".chars().map(u32::from));
554 /// assert_eq!(pyarray.readonly().as_slice().unwrap(), &[97, 98, 99, 100, 101]);
555 /// });
556 /// ```
557 pub fn from_iter<I>(py: Python<'_>, iter: I) -> Bound<'_, Self>
558 where
559 I: IntoIterator<Item = T>,
560 {
561 let data = iter.into_iter().collect::<Vec<_>>();
562 data.into_pyarray(py)
563 }
564}
565
566impl<T: Element> PyArray<T, Ix2> {
567 /// Construct a two-dimension array from a [`Vec<Vec<T>>`][Vec].
568 ///
569 /// This function checks all dimensions of the inner vectors and returns
570 /// an error if they are not all equal.
571 ///
572 /// # Example
573 ///
574 /// ```
575 /// use numpy::{PyArray, PyArrayMethods};
576 /// use pyo3::Python;
577 /// use ndarray::array;
578 ///
579 /// Python::attach(|py| {
580 /// let vec2 = vec![vec![11, 12], vec![21, 22]];
581 /// let pyarray = PyArray::from_vec2(py, &vec2).unwrap();
582 /// assert_eq!(pyarray.readonly().as_array(), array![[11, 12], [21, 22]]);
583 ///
584 /// let ragged_vec2 = vec![vec![11, 12], vec![21]];
585 /// assert!(PyArray::from_vec2(py, &ragged_vec2).is_err());
586 /// });
587 /// ```
588 pub fn from_vec2<'py>(py: Python<'py>, v: &[Vec<T>]) -> Result<Bound<'py, Self>, FromVecError> {
589 let len2 = v.first().map_or(0, |v| v.len());
590 let dims = [v.len(), len2];
591 // SAFETY: The result of `Self::new` is always safe to drop.
592 unsafe {
593 let array = Self::new(py, dims, false);
594 let mut data_ptr = array.data();
595 for v in v {
596 if v.len() != len2 {
597 cold();
598 return Err(FromVecError::new(v.len(), len2));
599 }
600 clone_elements(py, v, &mut data_ptr);
601 }
602 Ok(array)
603 }
604 }
605}
606
607impl<T: Element> PyArray<T, Ix3> {
608 /// Construct a three-dimensional array from a [`Vec<Vec<Vec<T>>>`][Vec].
609 ///
610 /// This function checks all dimensions of the inner vectors and returns
611 /// an error if they are not all equal.
612 ///
613 /// # Example
614 ///
615 /// ```
616 /// use numpy::{PyArray, PyArrayMethods};
617 /// use pyo3::Python;
618 /// use ndarray::array;
619 ///
620 /// Python::attach(|py| {
621 /// let vec3 = vec![
622 /// vec![vec![111, 112], vec![121, 122]],
623 /// vec![vec![211, 212], vec![221, 222]],
624 /// ];
625 /// let pyarray = PyArray::from_vec3(py, &vec3).unwrap();
626 /// assert_eq!(
627 /// pyarray.readonly().as_array(),
628 /// array![[[111, 112], [121, 122]], [[211, 212], [221, 222]]]
629 /// );
630 ///
631 /// let ragged_vec3 = vec![
632 /// vec![vec![111, 112], vec![121, 122]],
633 /// vec![vec![211], vec![221, 222]],
634 /// ];
635 /// assert!(PyArray::from_vec3(py, &ragged_vec3).is_err());
636 /// });
637 /// ```
638 pub fn from_vec3<'py>(
639 py: Python<'py>,
640 v: &[Vec<Vec<T>>],
641 ) -> Result<Bound<'py, Self>, FromVecError> {
642 let len2 = v.first().map_or(0, |v| v.len());
643 let len3 = v.first().map_or(0, |v| v.first().map_or(0, |v| v.len()));
644 let dims = [v.len(), len2, len3];
645 // SAFETY: The result of `Self::new` is always safe to drop.
646 unsafe {
647 let array = Self::new(py, dims, false);
648 let mut data_ptr = array.data();
649 for v in v {
650 if v.len() != len2 {
651 cold();
652 return Err(FromVecError::new(v.len(), len2));
653 }
654 for v in v {
655 if v.len() != len3 {
656 cold();
657 return Err(FromVecError::new(v.len(), len3));
658 }
659 clone_elements(py, v, &mut data_ptr);
660 }
661 }
662 Ok(array)
663 }
664 }
665}
666
667impl<T: Element + AsPrimitive<f64>> PyArray<T, Ix1> {
668 /// Return evenly spaced values within a given interval.
669 ///
670 /// See [numpy.arange][numpy.arange] for the Python API and [PyArray_Arange][PyArray_Arange] for the C API.
671 ///
672 /// # Example
673 ///
674 /// ```
675 /// use numpy::{PyArray, PyArrayMethods};
676 /// use pyo3::Python;
677 ///
678 /// Python::attach(|py| {
679 /// let pyarray = PyArray::arange(py, 2.0, 4.0, 0.5);
680 /// assert_eq!(pyarray.readonly().as_slice().unwrap(), &[2.0, 2.5, 3.0, 3.5]);
681 ///
682 /// let pyarray = PyArray::arange(py, -2, 4, 3);
683 /// assert_eq!(pyarray.readonly().as_slice().unwrap(), &[-2, 1]);
684 /// });
685 /// ```
686 ///
687 /// [numpy.arange]: https://numpy.org/doc/stable/reference/generated/numpy.arange.html
688 /// [PyArray_Arange]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_Arange
689 pub fn arange<'py>(py: Python<'py>, start: T, stop: T, step: T) -> Bound<'py, Self> {
690 unsafe {
691 let ptr = PY_ARRAY_API.PyArray_Arange(
692 py,
693 start.as_(),
694 stop.as_(),
695 step.as_(),
696 T::get_dtype(py).num(),
697 );
698 Bound::from_owned_ptr(py, ptr).cast_into_unchecked()
699 }
700 }
701}
702
703unsafe fn clone_elements<T: Element>(py: Python<'_>, elems: &[T], data_ptr: &mut *mut T) {
704 if T::IS_COPY {
705 ptr::copy_nonoverlapping(elems.as_ptr(), *data_ptr, elems.len());
706 *data_ptr = data_ptr.add(elems.len());
707 } else {
708 for elem in elems {
709 data_ptr.write(elem.clone_ref(py));
710 *data_ptr = data_ptr.add(1);
711 }
712 }
713}
714
715/// Implementation of functionality for [`PyArray<T, D>`].
716#[doc(alias = "PyArray")]
717pub trait PyArrayMethods<'py, T, D>: PyUntypedArrayMethods<'py> + Sized {
718 /// Access an untyped representation of this array.
719 fn as_untyped(&self) -> &Bound<'py, PyUntypedArray>;
720
721 /// Returns a pointer to the first element of the array.
722 fn data(&self) -> *mut T;
723
724 /// Same as [`shape`][PyUntypedArray::shape], but returns `D` instead of `&[usize]`.
725 #[inline(always)]
726 fn dims(&self) -> D
727 where
728 D: Dimension,
729 {
730 D::from_dimension(&Dim(self.shape())).expect(DIMENSIONALITY_MISMATCH_ERR)
731 }
732
733 /// Returns an immutable view of the internal data as a slice.
734 ///
735 /// # Safety
736 ///
737 /// Calling this method is undefined behaviour if the underlying array
738 /// is aliased mutably by other instances of `PyArray`
739 /// or concurrently modified by Python or other native code.
740 ///
741 /// Please consider the safe alternative [`PyReadonlyArray::as_slice`].
742 unsafe fn as_slice(&self) -> Result<&[T], AsSliceError>
743 where
744 T: Element,
745 D: Dimension,
746 {
747 let len = self.len();
748 if len == 0 {
749 // We can still produce a slice over zero objects regardless of whether
750 // the underlying pointer is aligned or not.
751 Ok(&[])
752 } else if self.is_aligned() && self.is_contiguous() {
753 Ok(slice::from_raw_parts(self.data(), len))
754 } else {
755 Err(AsSliceError)
756 }
757 }
758
759 /// Returns a mutable view of the internal data as a slice.
760 ///
761 /// # Safety
762 ///
763 /// Calling this method is undefined behaviour if the underlying array
764 /// is aliased immutably or mutably by other instances of [`PyArray`]
765 /// or concurrently modified by Python or other native code.
766 ///
767 /// Please consider the safe alternative [`PyReadwriteArray::as_slice_mut`].
768 #[allow(clippy::mut_from_ref)]
769 unsafe fn as_slice_mut(&self) -> Result<&mut [T], AsSliceError>
770 where
771 T: Element,
772 D: Dimension,
773 {
774 let len = self.len();
775 if len == 0 {
776 // We can still produce a slice over zero objects regardless of whether
777 // the underlying pointer is aligned or not.
778 Ok(&mut [])
779 } else if self.is_aligned() && self.is_contiguous() {
780 Ok(slice::from_raw_parts_mut(self.data(), len))
781 } else {
782 Err(AsSliceError)
783 }
784 }
785
786 /// Get a reference of the specified element if the given index is valid.
787 ///
788 /// # Safety
789 ///
790 /// Calling this method is undefined behaviour if the underlying array
791 /// is aliased mutably by other instances of `PyArray`
792 /// or concurrently modified by Python or other native code.
793 ///
794 /// Consider using safe alternatives like [`PyReadonlyArray::get`].
795 ///
796 /// # Example
797 ///
798 /// ```
799 /// use numpy::{PyArray, PyArrayMethods};
800 /// use pyo3::Python;
801 ///
802 /// Python::attach(|py| {
803 /// let pyarray = PyArray::arange(py, 0, 16, 1).reshape([2, 2, 4]).unwrap();
804 ///
805 /// assert_eq!(unsafe { *pyarray.get([1, 0, 3]).unwrap() }, 11);
806 /// });
807 /// ```
808 unsafe fn get(&self, index: impl NpyIndex<Dim = D>) -> Option<&T>
809 where
810 T: Element,
811 D: Dimension;
812
813 /// Same as [`get`][Self::get], but returns `Option<&mut T>`.
814 ///
815 /// # Safety
816 ///
817 /// Calling this method is undefined behaviour if the underlying array
818 /// is aliased immutably or mutably by other instances of [`PyArray`]
819 /// or concurrently modified by Python or other native code.
820 ///
821 /// Consider using safe alternatives like [`PyReadwriteArray::get_mut`].
822 ///
823 /// # Example
824 ///
825 /// ```
826 /// use numpy::{PyArray, PyArrayMethods};
827 /// use pyo3::Python;
828 ///
829 /// Python::attach(|py| {
830 /// let pyarray = PyArray::arange(py, 0, 16, 1).reshape([2, 2, 4]).unwrap();
831 ///
832 /// unsafe {
833 /// *pyarray.get_mut([1, 0, 3]).unwrap() = 42;
834 /// }
835 ///
836 /// assert_eq!(unsafe { *pyarray.get([1, 0, 3]).unwrap() }, 42);
837 /// });
838 /// ```
839 #[allow(clippy::mut_from_ref)]
840 unsafe fn get_mut(&self, index: impl NpyIndex<Dim = D>) -> Option<&mut T>
841 where
842 T: Element,
843 D: Dimension;
844
845 /// Get an immutable reference of the specified element,
846 /// without checking the given index.
847 ///
848 /// See [`NpyIndex`] for what types can be used as the index.
849 ///
850 /// # Safety
851 ///
852 /// Passing an invalid index is undefined behavior.
853 /// The element must also have been initialized and
854 /// all other references to it is must also be shared.
855 ///
856 /// See [`PyReadonlyArray::get`] for a safe alternative.
857 ///
858 /// # Example
859 ///
860 /// ```
861 /// use numpy::{PyArray, PyArrayMethods};
862 /// use pyo3::Python;
863 ///
864 /// Python::attach(|py| {
865 /// let pyarray = PyArray::arange(py, 0, 16, 1).reshape([2, 2, 4]).unwrap();
866 ///
867 /// assert_eq!(unsafe { *pyarray.uget([1, 0, 3]) }, 11);
868 /// });
869 /// ```
870 #[inline(always)]
871 unsafe fn uget<Idx>(&self, index: Idx) -> &T
872 where
873 T: Element,
874 D: Dimension,
875 Idx: NpyIndex<Dim = D>,
876 {
877 &*self.uget_raw(index)
878 }
879
880 /// Same as [`uget`](Self::uget), but returns `&mut T`.
881 ///
882 /// # Safety
883 ///
884 /// Passing an invalid index is undefined behavior.
885 /// The element must also have been initialized and
886 /// other references to it must not exist.
887 ///
888 /// See [`PyReadwriteArray::get_mut`] for a safe alternative.
889 #[inline(always)]
890 #[allow(clippy::mut_from_ref)]
891 unsafe fn uget_mut<Idx>(&self, index: Idx) -> &mut T
892 where
893 T: Element,
894 D: Dimension,
895 Idx: NpyIndex<Dim = D>,
896 {
897 &mut *self.uget_raw(index)
898 }
899
900 /// Same as [`uget`][Self::uget], but returns `*mut T`.
901 ///
902 /// # Safety
903 ///
904 /// Passing an invalid index is undefined behavior.
905 #[inline(always)]
906 unsafe fn uget_raw<Idx>(&self, index: Idx) -> *mut T
907 where
908 T: Element,
909 D: Dimension,
910 Idx: NpyIndex<Dim = D>,
911 {
912 let offset = index.get_unchecked::<T>(self.strides());
913 self.data().offset(offset) as *mut _
914 }
915
916 /// Get a copy of the specified element in the array.
917 ///
918 /// See [`NpyIndex`] for what types can be used as the index.
919 ///
920 /// # Example
921 /// ```
922 /// use numpy::{PyArray, PyArrayMethods};
923 /// use pyo3::Python;
924 ///
925 /// Python::attach(|py| {
926 /// let pyarray = PyArray::arange(py, 0, 16, 1).reshape([2, 2, 4]).unwrap();
927 ///
928 /// assert_eq!(pyarray.get_owned([1, 0, 3]), Some(11));
929 /// });
930 /// ```
931 fn get_owned<Idx>(&self, index: Idx) -> Option<T>
932 where
933 T: Element,
934 D: Dimension,
935 Idx: NpyIndex<Dim = D>;
936
937 /// Turn an array with fixed dimensionality into one with dynamic dimensionality.
938 fn to_dyn(&self) -> &Bound<'py, PyArray<T, IxDyn>>
939 where
940 T: Element,
941 D: Dimension;
942
943 /// Returns a copy of the internal data of the array as a [`Vec`].
944 ///
945 /// Fails if the internal array is not contiguous. See also [`as_slice`][Self::as_slice].
946 ///
947 /// # Example
948 ///
949 /// ```
950 /// use numpy::{PyArray2, PyArrayMethods};
951 /// use pyo3::{Python, types::PyAnyMethods, ffi::c_str};
952 ///
953 /// # fn main() -> pyo3::PyResult<()> {
954 /// Python::attach(|py| {
955 /// let pyarray= py
956 /// .eval(c_str!("__import__('numpy').array([[0, 1], [2, 3]], dtype='int64')"), None, None)?
957 /// .cast_into::<PyArray2<i64>>()?;
958 ///
959 /// assert_eq!(pyarray.to_vec()?, vec![0, 1, 2, 3]);
960 /// # Ok(())
961 /// })
962 /// # }
963 /// ```
964 fn to_vec(&self) -> Result<Vec<T>, AsSliceError>
965 where
966 T: Element,
967 D: Dimension;
968
969 /// Consume `self` into an immutable borrow of the NumPy array
970 fn try_into_readonly(self) -> Result<PyReadonlyArray<'py, T, D>, BorrowError>
971 where
972 T: Element,
973 D: Dimension;
974
975 /// Get an immutable borrow of the NumPy array
976 fn try_readonly(&self) -> Result<PyReadonlyArray<'py, T, D>, BorrowError>
977 where
978 T: Element,
979 D: Dimension;
980
981 /// Consume `self` into an immutable borrow of the NumPy array
982 ///
983 /// # Panics
984 ///
985 /// Panics if the allocation backing the array is currently mutably borrowed.
986 ///
987 /// For a non-panicking variant, use [`try_into_readonly`][Self::try_into_readonly].
988 fn into_readonly(self) -> PyReadonlyArray<'py, T, D>
989 where
990 T: Element,
991 D: Dimension,
992 {
993 self.try_into_readonly().unwrap()
994 }
995
996 /// Get an immutable borrow of the NumPy array
997 ///
998 /// # Panics
999 ///
1000 /// Panics if the allocation backing the array is currently mutably borrowed.
1001 ///
1002 /// For a non-panicking variant, use [`try_readonly`][Self::try_readonly].
1003 fn readonly(&self) -> PyReadonlyArray<'py, T, D>
1004 where
1005 T: Element,
1006 D: Dimension,
1007 {
1008 self.try_readonly().unwrap()
1009 }
1010
1011 /// Consume `self` into an mutable borrow of the NumPy array
1012 fn try_into_readwrite(self) -> Result<PyReadwriteArray<'py, T, D>, BorrowError>
1013 where
1014 T: Element,
1015 D: Dimension;
1016
1017 /// Get a mutable borrow of the NumPy array
1018 fn try_readwrite(&self) -> Result<PyReadwriteArray<'py, T, D>, BorrowError>
1019 where
1020 T: Element,
1021 D: Dimension;
1022
1023 /// Consume `self` into an mutable borrow of the NumPy array
1024 ///
1025 /// # Panics
1026 ///
1027 /// Panics if the allocation backing the array is currently borrowed or
1028 /// if the array is [flagged as][flags] not writeable.
1029 ///
1030 /// For a non-panicking variant, use [`try_into_readwrite`][Self::try_into_readwrite].
1031 ///
1032 /// [flags]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.flags.html
1033 fn into_readwrite(self) -> PyReadwriteArray<'py, T, D>
1034 where
1035 T: Element,
1036 D: Dimension,
1037 {
1038 self.try_into_readwrite().unwrap()
1039 }
1040
1041 /// Get a mutable borrow of the NumPy array
1042 ///
1043 /// # Panics
1044 ///
1045 /// Panics if the allocation backing the array is currently borrowed or
1046 /// if the array is [flagged as][flags] not writeable.
1047 ///
1048 /// For a non-panicking variant, use [`try_readwrite`][Self::try_readwrite].
1049 ///
1050 /// [flags]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.flags.html
1051 fn readwrite(&self) -> PyReadwriteArray<'py, T, D>
1052 where
1053 T: Element,
1054 D: Dimension,
1055 {
1056 self.try_readwrite().unwrap()
1057 }
1058
1059 /// Returns an [`ArrayView`] of the internal array.
1060 ///
1061 /// See also [`PyReadonlyArray::as_array`].
1062 ///
1063 /// # Safety
1064 ///
1065 /// Calling this method invalidates all exclusive references to the internal data, e.g. `&mut [T]` or `ArrayViewMut`.
1066 unsafe fn as_array(&self) -> ArrayView<'_, T, D>
1067 where
1068 T: Element,
1069 D: Dimension;
1070
1071 /// Returns an [`ArrayViewMut`] of the internal array.
1072 ///
1073 /// See also [`PyReadwriteArray::as_array_mut`].
1074 ///
1075 /// # Safety
1076 ///
1077 /// Calling this method invalidates all other references to the internal data, e.g. `ArrayView` or `ArrayViewMut`.
1078 unsafe fn as_array_mut(&self) -> ArrayViewMut<'_, T, D>
1079 where
1080 T: Element,
1081 D: Dimension;
1082
1083 /// Returns the internal array as [`RawArrayView`] enabling element access via raw pointers
1084 fn as_raw_array(&self) -> RawArrayView<T, D>
1085 where
1086 T: Element,
1087 D: Dimension;
1088
1089 /// Returns the internal array as [`RawArrayViewMut`] enabling element access via raw pointers
1090 fn as_raw_array_mut(&self) -> RawArrayViewMut<T, D>
1091 where
1092 T: Element,
1093 D: Dimension;
1094
1095 /// Get a copy of the array as an [`ndarray::Array`].
1096 ///
1097 /// # Example
1098 ///
1099 /// ```
1100 /// use numpy::{PyArray, PyArrayMethods};
1101 /// use ndarray::array;
1102 /// use pyo3::Python;
1103 ///
1104 /// Python::attach(|py| {
1105 /// let pyarray = PyArray::arange(py, 0, 4, 1).reshape([2, 2]).unwrap();
1106 ///
1107 /// assert_eq!(
1108 /// pyarray.to_owned_array(),
1109 /// array![[0, 1], [2, 3]]
1110 /// )
1111 /// });
1112 /// ```
1113 fn to_owned_array(&self) -> Array<T, D>
1114 where
1115 T: Element,
1116 D: Dimension;
1117
1118 /// Copies `self` into `other`, performing a data type conversion if necessary.
1119 ///
1120 /// See also [`PyArray_CopyInto`][PyArray_CopyInto].
1121 ///
1122 /// # Example
1123 ///
1124 /// ```
1125 /// use numpy::{PyArray, PyArrayMethods};
1126 /// use pyo3::Python;
1127 ///
1128 /// Python::attach(|py| {
1129 /// let pyarray_f = PyArray::arange(py, 2.0, 5.0, 1.0);
1130 /// let pyarray_i = unsafe { PyArray::<i64, _>::new(py, [3], false) };
1131 ///
1132 /// assert!(pyarray_f.copy_to(&pyarray_i).is_ok());
1133 ///
1134 /// assert_eq!(pyarray_i.readonly().as_slice().unwrap(), &[2, 3, 4]);
1135 /// });
1136 /// ```
1137 ///
1138 /// [PyArray_CopyInto]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_CopyInto
1139 fn copy_to<U: Element>(&self, other: &Bound<'py, PyArray<U, D>>) -> PyResult<()>
1140 where
1141 T: Element;
1142
1143 /// Deprecated version of [`cast_array`](PyArrayMethods::cast_array)
1144 #[deprecated(since = "0.26.0", note = "use `cast_array` instead")]
1145 #[inline]
1146 fn cast<U: Element>(&self, is_fortran: bool) -> PyResult<Bound<'py, PyArray<U, D>>>
1147 where
1148 T: Element,
1149 {
1150 self.cast_array(is_fortran)
1151 }
1152
1153 /// Cast the `PyArray<T>` to `PyArray<U>`, by allocating a new array.
1154 ///
1155 /// See also [`PyArray_CastToType`][PyArray_CastToType].
1156 ///
1157 /// # Example
1158 ///
1159 /// ```
1160 /// use numpy::{PyArray, PyArrayMethods};
1161 /// use pyo3::Python;
1162 ///
1163 /// Python::attach(|py| {
1164 /// let pyarray_f = PyArray::arange(py, 2.0, 5.0, 1.0);
1165 ///
1166 /// let pyarray_i = pyarray_f.cast_array::<i32>(false).unwrap();
1167 ///
1168 /// assert_eq!(pyarray_i.readonly().as_slice().unwrap(), &[2, 3, 4]);
1169 /// });
1170 /// ```
1171 ///
1172 /// [PyArray_CastToType]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_CastToType
1173 fn cast_array<U: Element>(&self, is_fortran: bool) -> PyResult<Bound<'py, PyArray<U, D>>>
1174 where
1175 T: Element;
1176
1177 /// A view of `self` with a different order of axes determined by `axes`.
1178 ///
1179 /// If `axes` is `None`, the order of axes is reversed which corresponds to the standard matrix transpose.
1180 ///
1181 /// See also [`numpy.transpose`][numpy-transpose] and [`PyArray_Transpose`][PyArray_Transpose].
1182 ///
1183 /// # Example
1184 ///
1185 /// ```
1186 /// use numpy::prelude::*;
1187 /// use numpy::PyArray;
1188 /// use pyo3::Python;
1189 /// use ndarray::array;
1190 ///
1191 /// Python::attach(|py| {
1192 /// let array = array![[0, 1, 2], [3, 4, 5]].into_pyarray(py);
1193 ///
1194 /// let array = array.permute(Some([1, 0])).unwrap();
1195 ///
1196 /// assert_eq!(array.readonly().as_array(), array![[0, 3], [1, 4], [2, 5]]);
1197 /// });
1198 /// ```
1199 ///
1200 /// [numpy-transpose]: https://numpy.org/doc/stable/reference/generated/numpy.transpose.html
1201 /// [PyArray_Transpose]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_Transpose
1202 fn permute<ID: IntoDimension>(&self, axes: Option<ID>) -> PyResult<Bound<'py, PyArray<T, D>>>
1203 where
1204 T: Element;
1205
1206 /// Special case of [`permute`][Self::permute] which reverses the order the axes.
1207 fn transpose(&self) -> PyResult<Bound<'py, PyArray<T, D>>>
1208 where
1209 T: Element,
1210 {
1211 self.permute::<()>(None)
1212 }
1213
1214 /// Construct a new array which has same values as `self`,
1215 /// but has different dimensions specified by `shape`
1216 /// and a possibly different memory order specified by `order`.
1217 ///
1218 /// See also [`numpy.reshape`][numpy-reshape] and [`PyArray_Newshape`][PyArray_Newshape].
1219 ///
1220 /// # Example
1221 ///
1222 /// ```
1223 /// use numpy::prelude::*;
1224 /// use numpy::{npyffi::NPY_ORDER, PyArray};
1225 /// use pyo3::Python;
1226 /// use ndarray::array;
1227 ///
1228 /// Python::attach(|py| {
1229 /// let array =
1230 /// PyArray::from_iter(py, 0..9).reshape_with_order([3, 3], NPY_ORDER::NPY_FORTRANORDER).unwrap();
1231 ///
1232 /// assert_eq!(array.readonly().as_array(), array![[0, 3, 6], [1, 4, 7], [2, 5, 8]]);
1233 /// assert!(array.is_fortran_contiguous());
1234 ///
1235 /// assert!(array.reshape([5]).is_err());
1236 /// });
1237 /// ```
1238 ///
1239 /// [numpy-reshape]: https://numpy.org/doc/stable/reference/generated/numpy.reshape.html
1240 /// [PyArray_Newshape]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_Newshape
1241 fn reshape_with_order<ID: IntoDimension>(
1242 &self,
1243 shape: ID,
1244 order: NPY_ORDER,
1245 ) -> PyResult<Bound<'py, PyArray<T, ID::Dim>>>
1246 where
1247 T: Element;
1248
1249 /// Special case of [`reshape_with_order`][Self::reshape_with_order] which keeps the memory order the same.
1250 #[inline(always)]
1251 fn reshape<ID: IntoDimension>(&self, shape: ID) -> PyResult<Bound<'py, PyArray<T, ID::Dim>>>
1252 where
1253 T: Element,
1254 {
1255 self.reshape_with_order(shape, NPY_ORDER::NPY_ANYORDER)
1256 }
1257
1258 /// Extends or truncates the dimensions of an array.
1259 ///
1260 /// This method works only on [contiguous][PyUntypedArrayMethods::is_contiguous] arrays.
1261 /// Missing elements will be initialized as if calling [`zeros`][PyArray::zeros].
1262 ///
1263 /// See also [`ndarray.resize`][ndarray-resize] and [`PyArray_Resize`][PyArray_Resize].
1264 ///
1265 /// # Safety
1266 ///
1267 /// There should be no outstanding references (shared or exclusive) into the array
1268 /// as this method might re-allocate it and thereby invalidate all pointers into it.
1269 ///
1270 /// # Example
1271 ///
1272 /// ```
1273 /// use numpy::prelude::*;
1274 /// use numpy::PyArray;
1275 /// use pyo3::Python;
1276 ///
1277 /// Python::attach(|py| {
1278 /// let pyarray = PyArray::<f64, _>::zeros(py, (10, 10), false);
1279 /// assert_eq!(pyarray.shape(), [10, 10]);
1280 ///
1281 /// unsafe {
1282 /// pyarray.resize((100, 100)).unwrap();
1283 /// }
1284 /// assert_eq!(pyarray.shape(), [100, 100]);
1285 /// });
1286 /// ```
1287 ///
1288 /// [ndarray-resize]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.resize.html
1289 /// [PyArray_Resize]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_Resize
1290 unsafe fn resize<ID: IntoDimension>(&self, newshape: ID) -> PyResult<()>
1291 where
1292 T: Element;
1293
1294 /// Try to convert this array into a [`nalgebra::MatrixView`] using the given shape and strides.
1295 ///
1296 /// # Safety
1297 ///
1298 /// Calling this method invalidates all exclusive references to the internal data, e.g. `ArrayViewMut` or `MatrixSliceMut`.
1299 #[doc(alias = "nalgebra")]
1300 #[cfg(feature = "nalgebra")]
1301 unsafe fn try_as_matrix<R, C, RStride, CStride>(
1302 &self,
1303 ) -> Option<nalgebra::MatrixView<'_, T, R, C, RStride, CStride>>
1304 where
1305 T: nalgebra::Scalar + Element,
1306 D: Dimension,
1307 R: nalgebra::Dim,
1308 C: nalgebra::Dim,
1309 RStride: nalgebra::Dim,
1310 CStride: nalgebra::Dim;
1311
1312 /// Try to convert this array into a [`nalgebra::MatrixViewMut`] using the given shape and strides.
1313 ///
1314 /// # Safety
1315 ///
1316 /// Calling this method invalidates all other references to the internal data, e.g. `ArrayView`, `MatrixSlice`, `ArrayViewMut` or `MatrixSliceMut`.
1317 #[doc(alias = "nalgebra")]
1318 #[cfg(feature = "nalgebra")]
1319 unsafe fn try_as_matrix_mut<R, C, RStride, CStride>(
1320 &self,
1321 ) -> Option<nalgebra::MatrixViewMut<'_, T, R, C, RStride, CStride>>
1322 where
1323 T: nalgebra::Scalar + Element,
1324 D: Dimension,
1325 R: nalgebra::Dim,
1326 C: nalgebra::Dim,
1327 RStride: nalgebra::Dim,
1328 CStride: nalgebra::Dim;
1329}
1330
1331/// Implementation of functionality for [`PyArray0<T>`].
1332#[doc(alias = "PyArray", alias = "PyArray0")]
1333pub trait PyArray0Methods<'py, T>: PyArrayMethods<'py, T, Ix0> {
1334 /// Get the single element of a zero-dimensional array.
1335 ///
1336 /// See [`inner`][crate::inner] for an example.
1337 fn item(&self) -> T
1338 where
1339 T: Element + Copy,
1340 {
1341 unsafe { *self.data() }
1342 }
1343}
1344
1345#[inline(always)]
1346fn get_raw<T, D, Idx>(slf: &Bound<'_, PyArray<T, D>>, index: Idx) -> Option<*mut T>
1347where
1348 T: Element,
1349 D: Dimension,
1350 Idx: NpyIndex<Dim = D>,
1351{
1352 let offset = index.get_checked::<T>(slf.shape(), slf.strides())?;
1353 Some(unsafe { slf.data().offset(offset) })
1354}
1355
1356fn as_view<T, D, S, F>(slf: &Bound<'_, PyArray<T, D>>, from_shape_ptr: F) -> ArrayBase<S, D>
1357where
1358 T: Element,
1359 D: Dimension,
1360 S: RawData,
1361 F: FnOnce(StrideShape<D>, *mut T) -> ArrayBase<S, D>,
1362{
1363 fn inner<D: Dimension>(
1364 shape: &[usize],
1365 strides: &[isize],
1366 itemsize: usize,
1367 mut data_ptr: *mut u8,
1368 ) -> (StrideShape<D>, u32, *mut u8) {
1369 let shape = D::from_dimension(&Dim(shape)).expect(DIMENSIONALITY_MISMATCH_ERR);
1370
1371 assert!(strides.len() <= 32, "{}", MAX_DIMENSIONALITY_ERR);
1372
1373 let mut new_strides = D::zeros(strides.len());
1374 let mut inverted_axes = 0_u32;
1375
1376 for i in 0..strides.len() {
1377 // FIXME(kngwyu): Replace this hacky negative strides support with
1378 // a proper constructor, when it's implemented.
1379 // See https://github.com/rust-ndarray/ndarray/issues/842 for more.
1380 if strides[i] >= 0 {
1381 new_strides[i] = strides[i] as usize / itemsize;
1382 } else {
1383 // Move the pointer to the start position.
1384 data_ptr = unsafe { data_ptr.offset(strides[i] * (shape[i] as isize - 1)) };
1385
1386 new_strides[i] = (-strides[i]) as usize / itemsize;
1387 inverted_axes |= 1 << i;
1388 }
1389 }
1390
1391 (shape.strides(new_strides), inverted_axes, data_ptr)
1392 }
1393
1394 let (shape, mut inverted_axes, data_ptr) = inner(
1395 slf.shape(),
1396 slf.strides(),
1397 mem::size_of::<T>(),
1398 slf.data() as _,
1399 );
1400
1401 let mut array = from_shape_ptr(shape, data_ptr as _);
1402
1403 while inverted_axes != 0 {
1404 let axis = inverted_axes.trailing_zeros() as usize;
1405 inverted_axes &= !(1 << axis);
1406
1407 array.invert_axis(Axis(axis));
1408 }
1409
1410 array
1411}
1412
1413#[cfg(feature = "nalgebra")]
1414fn try_as_matrix_shape_strides<N, D, R, C, RStride, CStride>(
1415 slf: &Bound<'_, PyArray<N, D>>,
1416) -> Option<((R, C), (RStride, CStride))>
1417where
1418 N: nalgebra::Scalar + Element,
1419 D: Dimension,
1420 R: nalgebra::Dim,
1421 C: nalgebra::Dim,
1422 RStride: nalgebra::Dim,
1423 CStride: nalgebra::Dim,
1424{
1425 let ndim = slf.ndim();
1426 let shape = slf.shape();
1427 let strides = slf.strides();
1428
1429 if ndim != 1 && ndim != 2 {
1430 return None;
1431 }
1432
1433 if strides.iter().any(|strides| *strides < 0) {
1434 return None;
1435 }
1436
1437 let rows = shape[0];
1438 let cols = *shape.get(1).unwrap_or(&1);
1439
1440 if R::try_to_usize().map(|expected| rows == expected) == Some(false) {
1441 return None;
1442 }
1443
1444 if C::try_to_usize().map(|expected| cols == expected) == Some(false) {
1445 return None;
1446 }
1447
1448 let row_stride = strides[0] as usize / mem::size_of::<N>();
1449 let col_stride = strides
1450 .get(1)
1451 .map_or(rows, |stride| *stride as usize / mem::size_of::<N>());
1452
1453 if RStride::try_to_usize().map(|expected| row_stride == expected) == Some(false) {
1454 return None;
1455 }
1456
1457 if CStride::try_to_usize().map(|expected| col_stride == expected) == Some(false) {
1458 return None;
1459 }
1460
1461 let shape = (R::from_usize(rows), C::from_usize(cols));
1462
1463 let strides = (
1464 RStride::from_usize(row_stride),
1465 CStride::from_usize(col_stride),
1466 );
1467
1468 Some((shape, strides))
1469}
1470
1471impl<'py, T, D> PyArrayMethods<'py, T, D> for Bound<'py, PyArray<T, D>> {
1472 #[inline(always)]
1473 fn as_untyped(&self) -> &Bound<'py, PyUntypedArray> {
1474 unsafe { self.cast_unchecked() }
1475 }
1476
1477 #[inline(always)]
1478 fn data(&self) -> *mut T {
1479 unsafe { (*self.as_array_ptr()).data.cast() }
1480 }
1481
1482 #[inline(always)]
1483 unsafe fn get(&self, index: impl NpyIndex<Dim = D>) -> Option<&T>
1484 where
1485 T: Element,
1486 D: Dimension,
1487 {
1488 let ptr = get_raw(self, index)?;
1489 Some(&*ptr)
1490 }
1491
1492 #[inline(always)]
1493 unsafe fn get_mut(&self, index: impl NpyIndex<Dim = D>) -> Option<&mut T>
1494 where
1495 T: Element,
1496 D: Dimension,
1497 {
1498 let ptr = get_raw(self, index)?;
1499 Some(&mut *ptr)
1500 }
1501
1502 fn get_owned<Idx>(&self, index: Idx) -> Option<T>
1503 where
1504 T: Element,
1505 D: Dimension,
1506 Idx: NpyIndex<Dim = D>,
1507 {
1508 let element = unsafe { self.get(index) };
1509 element.map(|elem| elem.clone_ref(self.py()))
1510 }
1511
1512 fn to_dyn(&self) -> &Bound<'py, PyArray<T, IxDyn>> {
1513 unsafe { self.cast_unchecked() }
1514 }
1515
1516 fn to_vec(&self) -> Result<Vec<T>, AsSliceError>
1517 where
1518 T: Element,
1519 D: Dimension,
1520 {
1521 let slice = unsafe { self.as_slice() };
1522 slice.map(|slc| T::vec_from_slice(self.py(), slc))
1523 }
1524
1525 fn try_into_readonly(self) -> Result<PyReadonlyArray<'py, T, D>, BorrowError>
1526 where
1527 T: Element,
1528 D: Dimension,
1529 {
1530 PyReadonlyArray::try_new(self)
1531 }
1532
1533 fn try_readonly(&self) -> Result<PyReadonlyArray<'py, T, D>, BorrowError>
1534 where
1535 T: Element,
1536 D: Dimension,
1537 {
1538 self.clone().try_into_readonly()
1539 }
1540
1541 fn try_into_readwrite(self) -> Result<PyReadwriteArray<'py, T, D>, BorrowError>
1542 where
1543 T: Element,
1544 D: Dimension,
1545 {
1546 PyReadwriteArray::try_new(self)
1547 }
1548
1549 fn try_readwrite(&self) -> Result<PyReadwriteArray<'py, T, D>, BorrowError>
1550 where
1551 T: Element,
1552 D: Dimension,
1553 {
1554 self.clone().try_into_readwrite()
1555 }
1556
1557 unsafe fn as_array(&self) -> ArrayView<'_, T, D>
1558 where
1559 T: Element,
1560 D: Dimension,
1561 {
1562 as_view(self, |shape, ptr| ArrayView::from_shape_ptr(shape, ptr))
1563 }
1564
1565 unsafe fn as_array_mut(&self) -> ArrayViewMut<'_, T, D>
1566 where
1567 T: Element,
1568 D: Dimension,
1569 {
1570 as_view(self, |shape, ptr| ArrayViewMut::from_shape_ptr(shape, ptr))
1571 }
1572
1573 fn as_raw_array(&self) -> RawArrayView<T, D>
1574 where
1575 T: Element,
1576 D: Dimension,
1577 {
1578 as_view(self, |shape, ptr| unsafe {
1579 RawArrayView::from_shape_ptr(shape, ptr)
1580 })
1581 }
1582
1583 fn as_raw_array_mut(&self) -> RawArrayViewMut<T, D>
1584 where
1585 T: Element,
1586 D: Dimension,
1587 {
1588 as_view(self, |shape, ptr| unsafe {
1589 RawArrayViewMut::from_shape_ptr(shape, ptr)
1590 })
1591 }
1592
1593 fn to_owned_array(&self) -> Array<T, D>
1594 where
1595 T: Element,
1596 D: Dimension,
1597 {
1598 let view = unsafe { self.as_array() };
1599 T::array_from_view(self.py(), view)
1600 }
1601
1602 fn copy_to<U: Element>(&self, other: &Bound<'py, PyArray<U, D>>) -> PyResult<()>
1603 where
1604 T: Element,
1605 {
1606 let self_ptr = self.as_array_ptr();
1607 let other_ptr = other.as_array_ptr();
1608 let result = unsafe { PY_ARRAY_API.PyArray_CopyInto(self.py(), other_ptr, self_ptr) };
1609 if result != -1 {
1610 Ok(())
1611 } else {
1612 Err(PyErr::fetch(self.py()))
1613 }
1614 }
1615
1616 fn cast_array<U: Element>(&self, is_fortran: bool) -> PyResult<Bound<'py, PyArray<U, D>>>
1617 where
1618 T: Element,
1619 {
1620 let ptr = unsafe {
1621 PY_ARRAY_API.PyArray_CastToType(
1622 self.py(),
1623 self.as_array_ptr(),
1624 U::get_dtype(self.py()).into_dtype_ptr(),
1625 if is_fortran { -1 } else { 0 },
1626 )
1627 };
1628 unsafe { Bound::from_owned_ptr_or_err(self.py(), ptr).map(|ob| ob.cast_into_unchecked()) }
1629 }
1630
1631 fn permute<ID: IntoDimension>(&self, axes: Option<ID>) -> PyResult<Bound<'py, PyArray<T, D>>> {
1632 let mut axes = axes.map(|axes| axes.into_dimension());
1633 let mut axes = axes.as_mut().map(|axes| axes.to_npy_dims());
1634 let axes = axes
1635 .as_mut()
1636 .map_or_else(ptr::null_mut, |axes| axes as *mut npyffi::PyArray_Dims);
1637
1638 let py = self.py();
1639 let ptr = unsafe { PY_ARRAY_API.PyArray_Transpose(py, self.as_array_ptr(), axes) };
1640 unsafe { Bound::from_owned_ptr_or_err(py, ptr).map(|ob| ob.cast_into_unchecked()) }
1641 }
1642
1643 fn reshape_with_order<ID: IntoDimension>(
1644 &self,
1645 shape: ID,
1646 order: NPY_ORDER,
1647 ) -> PyResult<Bound<'py, PyArray<T, ID::Dim>>>
1648 where
1649 T: Element,
1650 {
1651 let mut shape = shape.into_dimension();
1652 let mut shape = shape.to_npy_dims();
1653
1654 let py = self.py();
1655 let ptr = unsafe {
1656 PY_ARRAY_API.PyArray_Newshape(
1657 py,
1658 self.as_array_ptr(),
1659 &mut shape as *mut npyffi::PyArray_Dims,
1660 order,
1661 )
1662 };
1663 unsafe { Bound::from_owned_ptr_or_err(py, ptr).map(|ob| ob.cast_into_unchecked()) }
1664 }
1665
1666 unsafe fn resize<ID: IntoDimension>(&self, newshape: ID) -> PyResult<()>
1667 where
1668 T: Element,
1669 {
1670 let mut newshape = newshape.into_dimension();
1671 let mut newshape = newshape.to_npy_dims();
1672
1673 let py = self.py();
1674 let res = PY_ARRAY_API.PyArray_Resize(
1675 py,
1676 self.as_array_ptr(),
1677 &mut newshape as *mut npyffi::PyArray_Dims,
1678 1,
1679 NPY_ORDER::NPY_ANYORDER,
1680 );
1681
1682 if !res.is_null() {
1683 Ok(())
1684 } else {
1685 Err(PyErr::fetch(py))
1686 }
1687 }
1688
1689 #[cfg(feature = "nalgebra")]
1690 unsafe fn try_as_matrix<R, C, RStride, CStride>(
1691 &self,
1692 ) -> Option<nalgebra::MatrixView<'_, T, R, C, RStride, CStride>>
1693 where
1694 T: nalgebra::Scalar + Element,
1695 D: Dimension,
1696 R: nalgebra::Dim,
1697 C: nalgebra::Dim,
1698 RStride: nalgebra::Dim,
1699 CStride: nalgebra::Dim,
1700 {
1701 let (shape, strides) = try_as_matrix_shape_strides(self)?;
1702
1703 let storage = nalgebra::ViewStorage::from_raw_parts(self.data(), shape, strides);
1704
1705 Some(nalgebra::Matrix::from_data(storage))
1706 }
1707
1708 #[cfg(feature = "nalgebra")]
1709 unsafe fn try_as_matrix_mut<R, C, RStride, CStride>(
1710 &self,
1711 ) -> Option<nalgebra::MatrixViewMut<'_, T, R, C, RStride, CStride>>
1712 where
1713 T: nalgebra::Scalar + Element,
1714 D: Dimension,
1715 R: nalgebra::Dim,
1716 C: nalgebra::Dim,
1717 RStride: nalgebra::Dim,
1718 CStride: nalgebra::Dim,
1719 {
1720 let (shape, strides) = try_as_matrix_shape_strides(self)?;
1721
1722 let storage = nalgebra::ViewStorageMut::from_raw_parts(self.data(), shape, strides);
1723
1724 Some(nalgebra::Matrix::from_data(storage))
1725 }
1726}
1727
1728impl<'py, T> PyArray0Methods<'py, T> for Bound<'py, PyArray0<T>> {}
1729
1730#[cfg(test)]
1731mod tests {
1732 use super::*;
1733
1734 use ndarray::array;
1735 use pyo3::{py_run, types::PyList};
1736
1737 #[test]
1738 fn test_dyn_to_owned_array() {
1739 Python::attach(|py| {
1740 let array = PyArray::from_vec2(py, &[vec![1, 2], vec![3, 4]])
1741 .unwrap()
1742 .to_dyn()
1743 .to_owned_array();
1744
1745 assert_eq!(array, array![[1, 2], [3, 4]].into_dyn());
1746 });
1747 }
1748
1749 #[test]
1750 fn test_hasobject_flag() {
1751 Python::attach(|py| {
1752 let array: Bound<'_, PyArray<Py<PyAny>, _>> =
1753 PyArray1::from_slice(py, &[PyList::empty(py).into()]);
1754
1755 py_run!(py, array, "assert array.dtype.hasobject");
1756 });
1757 }
1758}