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