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