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