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