numpy-0.27.1/.cargo_vcs_info.json0000644000000001360000000000100122730ustar { "git": { "sha1": "9fffe3163624072c8f3c55aaf056a62fe57e428a" }, "path_in_vcs": "" }numpy-0.27.1/CHANGELOG.md000064400000000000000000000314531046102023000127020ustar 00000000000000# Changelog - v0.27.1 - Bump ndarray dependency to v0.17. ([#516](https://github.com/PyO3/rust-numpy/pull/516)) - v0.27.0 - Bump PyO3 dependency to v0.27.0. ([#515](https://github.com/PyO3/rust-numpy/pull/515)) - v0.26.0 - bump MSRV to 1.74, matching PyO3 ([#504](https://github.com/PyO3/rust-numpy/pull/504)) - extend supported `nalgebra` version to `0.34` ([#503](https://github.com/PyO3/rust-numpy/pull/503)) - Bump PyO3 dependency to v0.26.0. ([#506](https://github.com/PyO3/rust-numpy/pull/506)) - v0.25.0, - Bump PyO3 dependency to v0.25.0. ([#492](https://github.com/PyO3/rust-numpy/pull/492)) - v0.24.0 - Bump PyO3 dependency to v0.24.0. ([#483](https://github.com/PyO3/rust-numpy/pull/483)) - Support Python 3.13t "free-threaded" Python. ([#471](https://github.com/PyO3/rust-numpy/pull/471)) - v0.23.0 - Drop support for PyPy 3.7 and 3.8. ([#470](https://github.com/PyO3/rust-numpy/pull/470)) - Require `Element: Sync` as part of the free-threading support in PyO3 0.23 ([#469](https://github.com/PyO3/rust-numpy/pull/469)) - Bump PyO3 dependency to v0.23.0 ([[#457](https://github.com/PyO3/rust-numpy/pull/457)]) - removed the `gil-refs` feature - reintroduced function names without `_bound` suffix + deprecating the old names - switched to `IntoPyObject` as trait bound - Bump `rustc-hash` dependency to 2.0. ([[#472](https://github.com/PyO3/rust-numpy/pull/472)]) - v0.22.1 - Fix building on 32-bit Windows. ([#463](https://github.com/PyO3/rust-numpy/pull/463)) - Add `PyReadwriteArray::make_nonwriteable`. ([#462](https://github.com/PyO3/rust-numpy/pull/462)) - Implement `From` for `PyReadonlyArray`. ([#462](https://github.com/PyO3/rust-numpy/pull/462)) - v0.22.0 - Bump MSRV to 1.63. ([#450](https://github.com/PyO3/rust-numpy/pull/450)) - Add `permute` and `transpose` methods for changing the order of axes of a `PyArray`. ([#428](https://github.com/PyO3/rust-numpy/pull/428)) - Add support for NumPy v2 which had a number of changes to the [C API](https://numpy.org/devdocs/numpy_2_0_migration_guide.html#c-api-changes). ([#442](https://github.com/PyO3/rust-numpy/pull/442)) - Add support for ndarray 0.16. ([#439](https://github.com/PyO3/rust-numpy/pull/439)) - Bumped pyo3 dependency to v0.22.0 which required the addition of several new methods to the `Element` trait. ([#435](https://github.com/PyO3/rust-numpy/pull/435)) - v0.21.0 - Migrate to the new `Bound` API introduced by PyO3 0.21. ([#410](https://github.com/PyO3/rust-numpy/pull/410)) ([#411](https://github.com/PyO3/rust-numpy/pull/411)) ([#412](https://github.com/PyO3/rust-numpy/pull/412)) ([#415](https://github.com/PyO3/rust-numpy/pull/415)) ([#416](https://github.com/PyO3/rust-numpy/pull/416)) ([#418](https://github.com/PyO3/rust-numpy/pull/418)) ([#419](https://github.com/PyO3/rust-numpy/pull/419)) ([#420](https://github.com/PyO3/rust-numpy/pull/420)) ([#421](https://github.com/PyO3/rust-numpy/pull/421)) ([#422](https://github.com/PyO3/rust-numpy/pull/422)) - Add a `prelude` module to simplify importing method traits required by the `Bound` API. ([#417](https://github.com/PyO3/rust-numpy/pull/417)) - Extend documentation to cover some more surprising behaviours. ([#405](https://github.com/PyO3/rust-numpy/pull/405)) ([#414](https://github.com/PyO3/rust-numpy/pull/414)) - v0.20.0 - Increase MSRV to 1.56 released in October 2021 and available in Debain 12, RHEL 9 and Alpine 3.17 following the same change for PyO3. ([#378](https://github.com/PyO3/rust-numpy/pull/378)) - Add support for ASCII (`PyFixedString`) and Unicode (`PyFixedUnicode`) string arrays, i.e. dtypes `SN` and `UN` where `N` is the number of characters. ([#378](https://github.com/PyO3/rust-numpy/pull/378)) - Add support for the `bfloat16` dtype by extending the optional integration with the `half` crate. Note that the `bfloat16` dtype is not part of NumPy itself so that usage requires third-party packages like Tensorflow. ([#381](https://github.com/PyO3/rust-numpy/pull/381)) - Add `PyArrayLike` type which extracts `PyReadonlyArray` if a NumPy array of the correct type is given and attempts a conversion using `numpy.asarray` otherwise. ([#383](https://github.com/PyO3/rust-numpy/pull/383)) - v0.19.0 - Add `PyUntypedArray` as an untyped base type for `PyArray` which can be used to inspect arguments before more targeted downcasts. This is accompanied by some methods like `dtype` and `shape` moving from `PyArray` to `PyUntypedArray`. They are still accessible though, as `PyArray` dereferences to `PyUntypedArray` via the `Deref` trait. ([#369](https://github.com/PyO3/rust-numpy/pull/369)) - Drop deprecated `PyArray::from_exact_iter` as it does not provide any benefits over `PyArray::from_iter`. ([#370](https://github.com/PyO3/rust-numpy/pull/370)) - v0.18.0 - Add conversions from and to datatypes provided by the [`nalgebra` crate](https://nalgebra.org/). ([#347](https://github.com/PyO3/rust-numpy/pull/347)) - Drop our wrapper for NumPy iterators which were deprecated in v0.16.0 as ndarray's iteration facilities are almost always preferable. ([#324](https://github.com/PyO3/rust-numpy/pull/324)) - Dynamic borrow checking now uses a capsule-based API and therefore works across multiple extensions using PyO3 and potentially other bindings or languages. ([#361](https://github.com/PyO3/rust-numpy/pull/361)) - v0.17.2 - Fix unsound aliasing into `Box<[T]>` when converting them into NumPy arrays. ([#351](https://github.com/PyO3/rust-numpy/pull/351)) - v0.17.1 - Fix use-after-free in `PyArray::resize`, `PyArray::reshape` and `PyArray::reshape_with_order`. ([#341](https://github.com/PyO3/rust-numpy/pull/341)) - Fix UB in `ToNpyDims::as_dims_ptr` with dimensions of dynamic size (-1). ([#344](https://github.com/PyO3/rust-numpy/pull/344)) - v0.17.0 - Add dynamic borrow checking to safely construct references into the interior of NumPy arrays. ([#274](https://github.com/PyO3/rust-numpy/pull/274)) - The deprecated iterator builders `NpySingleIterBuilder::{readonly,readwrite}` and `NpyMultiIterBuilder::add_{readonly,readwrite}` now take referencces to `PyReadonlyArray` and `PyReadwriteArray` instead of consuming them. - The destructive `PyArray::resize` method is now unsafe if used without an instance of `PyReadwriteArray`. ([#302](https://github.com/PyO3/rust-numpy/pull/302)) - Add support for `datetime64` and `timedelta64` element types via the `datetime` module. ([#308](https://github.com/PyO3/rust-numpy/pull/308)) - Add support for IEEE 754-2008 16-bit floating point numbers via an optional dependency on the `half` crate. ([#314](https://github.com/PyO3/rust-numpy/pull/314)) - The `inner`, `dot` and `einsum` functions can also return a scalar instead of a zero-dimensional array to match NumPy's types ([#285](https://github.com/PyO3/rust-numpy/pull/285)) - The `PyArray::resize` function supports n-dimensional contiguous arrays. ([#312](https://github.com/PyO3/rust-numpy/pull/312)) - Deprecate `PyArray::from_exact_iter` after optimizing `PyArray::from_iter`. ([#292](https://github.com/PyO3/rust-numpy/pull/292)) - Remove `DimensionalityError` and `TypeError` from the public API as they never used directly. ([#315](https://github.com/PyO3/rust-numpy/pull/315)) - Remove the deprecated `PyArrayDescr::get_type` which was replaced by `PyArrayDescr::typeobj` in the last cycle. ([#308](https://github.com/PyO3/rust-numpy/pull/308)) - Fix returning invalid slices from `PyArray::{strides,shape}` for rank zero arrays. ([#303](https://github.com/PyO3/rust-numpy/pull/303)) - v0.16.2 - Fix build on platforms where `c_char` is `u8` like Linux/AArch64. ([#296](https://github.com/PyO3/rust-numpy/pull/296)) - v0.16.1 - Fix build when PyO3's `multiple-pymethods` feature is used. ([#288](https://github.com/PyO3/rust-numpy/pull/288)) - v0.16.0 - Bump PyO3 version to 0.16 ([#259](https://github.com/PyO3/rust-numpy/pull/259)) - Support object arrays ([#216](https://github.com/PyO3/rust-numpy/pull/216)) - Support borrowing arrays that are part of other Python objects via `PyArray::borrow_from_array` ([#230](https://github.com/PyO3/rust-numpy/pull/230)) - Fixed downcasting ignoring element type and dimensionality ([#265](https://github.com/PyO3/rust-numpy/pull/265)) - `PyArray::new` is now `unsafe`, as it produces uninitialized arrays ([#220](https://github.com/PyO3/rust-numpy/pull/220)) - `PyArray::iter`, `NpySingleIterBuilder::readwrite` and `NpyMultiIterBuilder::add_readwrite` are now `unsafe`, as they allow aliasing mutable references to be created ([#278/](https://github.com/PyO3/rust-numpy/pull/278)) - The `npyiter` module is deprecated as rust-ndarray's facilities for iteration are more flexible and performant ([#280](https://github.com/PyO3/rust-numpy/pull/280)) - `PyArray::from_exact_iter` does not unsoundly trust `ExactSizeIterator::len` any more ([#262](https://github.com/PyO3/rust-numpy/pull/262)) - `PyArray::as_cell_slice` was removed as it unsoundly interacts with `PyReadonlyArray` allowing safe code to violate aliasing rules ([#260](https://github.com/PyO3/rust-numpy/pull/260)) - `rayon` feature is now removed, and directly specifying the feature via `ndarray` dependency is recommended ([#250](https://github.com/PyO3/rust-numpy/pull/250)) - `Element` trait and `PyArrayDescr` changes ([#256](https://github.com/PyO3/rust-numpy/pull/256)): - `Element` trait has been simplified to `get_dtype()` and `IS_COPY` - New `PyArrayDescr` methods: `of`, `into_dtype_ptr`, `is_equiv_to` - Added `numpy::dtype` function - `Element` is now implemented for `isize` - `c32` / `c64` have been renamed with `Complex32` / `Complex64` - `ShapeError` has been split into `TypeError` and `DimensionalityError` - `i32`, `i64`, `u32`, `u64` are now guaranteed to map to `np.u?int{32,64}`. - Removed `cfg_if` dependency - Removed `DataType` enum - Added `PyArrayDescr::new` constructor ([#266](https://github.com/PyO3/rust-numpy/pull/266)) - New `PyArrayDescr` methods ([#266](https://github.com/PyO3/rust-numpy/pull/261)): - `num`, `base`, `ndim`, `shape`, `byteorder`, `char`, `kind`, `itemsize`, `alignment`, `flags`, `has_object`, `is_aligned_struct`, `names`, `get_field`, `has_subarray`, `has_fields`, `is_native_byteorder` - Renamed `get_type` to `typeobj` - v0.15.1 - Make arrays produced via `IntoPyArray`, i.e. those owning Rust data, writeable ([#235](https://github.com/PyO3/rust-numpy/pull/235)) - Fix thread-safety in internal API globals ([#222](https://github.com/PyO3/rust-numpy/pull/222)) - v0.15.0 - [Remove resolver from Cargo.toml](https://github.com/PyO3/rust-numpy/pull/202) - [Bump PyO3 to 0.15](https://github.com/PyO3/rust-numpy/pull/212) - v0.14.1 - [Fix MSRV support](https://github.com/PyO3/rust-numpy/issues/198) - v0.14 - Bump PyO3 to 0.14 - Fix [conversion bug](https://github.com/PyO3/rust-numpy/pull/194) - v0.13.2 - Support ndarray 0.15 - v0.13.1 - Allow ndarray `>=0.13, < 0.15` to work with Rust 1.41.1. - Add inner, dot, and einsum - Add PyArray0 - v0.13.0 - Bump num-complex to 0.3 - Bump ndarray to 0.14 - Bump pyo3 to 0.13 - Drop support for Python 3.5 (as it is now end-of-life). - Remove unused `python3` feature - v0.12.2 - Pin PyO3 minor versions to 0.12 - Pin ndarray minor versions to 0.13 - v0.12.1 - Fix compile error in Rust 1.39 - v0.12.0 - Introduce `NpySingleIter` and `NpyMultiIter`. - Introduce `PyArrayDescr`. - v0.11.0 - `PyArray::get` is now unsafe. - Introduce `PyArray::get_owned` and `PyReadonlyArray::get`. - v0.10.0 - Remove `ErrorKind` and introduce some concrete error types. - `PyArray::as_slice`, `PyArray::as_slice_mut`, `PyArray::as_array`, and `PyArray::as_array_mut` is now unsafe. - Introduce `PyArray::as_cell_slice`, `PyArray::to_vec`, and `PyArray::to_owned_array`. - Rename `TypeNum` trait `Element`, and `NpyDataType` `DataType`. - Update PyO3 to 0.11 - v0.9.0 - Update PyO3 to 0.10.0 - v0.8.0 - Update PyO3 to 0.9.0 - Fix SliceBox initialization - v0.7.0 - Update PyO3 to 0.8 - v0.6.0 - Update PyO3 to 0.7 - Drop Python2 support - v0.5.0 - Update PyO3 to 0.6 - v0.4.0 - Duplicate `PyArrayModule` and import Numpy API automatically - Fix memory leak of `IntoPyArray` and add `ToPyArray` crate - PyArray has dimension as type parameter. Now it looks like `PyArray` - Use `ndarray::IntoDimension` to specify dimension - Python2 support - v0.3.1, v0.3.2 - Just update dependencies - v0.3.0 - Breaking Change: Migrated to pyo3 from rust-cpython - Some api addition - [Static type checking with PhantomData](https://github.com/PyO3/rust-numpy/pull/41) - v0.2.1 - NEW: trait `IntoPyErr`, `IntoPyResult` for error translation - v0.2.0 - NEW: traits `IntoPyArray`, `ToPyArray` - MOD: Interface of `PyArray` creation functions are changed - v0.1.1 - Update documents - v0.1.0 - First Release - Expose unsafe interface of Array and UFunc API numpy-0.27.1/Cargo.lock0000644000000316530000000000100102560ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "approx" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ "num-traits", ] [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bytemuck" version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "crunchy" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "glam" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "333928d5eb103c5d4050533cec0384302db6be8ef7d3cebd30ec6a35350353da" [[package]] name = "glam" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3abb554f8ee44336b72d522e0a7fe86a29e09f839a36022fa869a7dfe941a54b" [[package]] name = "glam" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4126c0479ccf7e8664c36a2d719f5f2c140fbb4f9090008098d2c291fa5b3f16" [[package]] name = "glam" version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01732b97afd8508eee3333a541b9f7610f454bb818669e66e90f5f57c93a776" [[package]] name = "glam" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525a3e490ba77b8e326fb67d4b44b4bd2f920f44d4cc73ccec50adc68e3bee34" [[package]] name = "glam" version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b8509e6791516e81c1a630d0bd7fbac36d2fa8712a9da8662e716b52d5051ca" [[package]] name = "glam" version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f43e957e744be03f5801a55472f593d43fabdebf25a4585db250f04d86b1675f" [[package]] name = "glam" version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "518faa5064866338b013ff9b2350dc318e14cc4fcd6cb8206d7e7c9886c98815" [[package]] name = "glam" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12f597d56c1bd55a811a1be189459e8fad2bbc272616375602443bdfb37fa774" [[package]] name = "glam" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c" [[package]] name = "glam" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5418c17512bdf42730f9032c74e1ae39afc408745ebb2acf72fbc4691c17945" [[package]] name = "glam" version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" [[package]] name = "glam" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" [[package]] name = "glam" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94" [[package]] name = "glam" version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" [[package]] name = "glam" version = "0.30.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd47b05dddf0005d850e5644cae7f2b14ac3df487979dbfff3b56f20b1a6ae46" [[package]] name = "half" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", "zerocopy", ] [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "indoc" version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" dependencies = [ "rustversion", ] [[package]] name = "libc" version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "matrixmultiply" version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" dependencies = [ "autocfg", "rawpointer", ] [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "nalgebra" version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4d5b3eff5cd580f93da45e64715e8c20a3996342f1e466599cf7a267a0c2f5f" dependencies = [ "approx", "glam 0.14.0", "glam 0.15.2", "glam 0.16.0", "glam 0.17.3", "glam 0.18.0", "glam 0.19.0", "glam 0.20.5", "glam 0.21.3", "glam 0.22.0", "glam 0.23.0", "glam 0.24.2", "glam 0.25.0", "glam 0.27.0", "glam 0.28.0", "glam 0.29.3", "glam 0.30.9", "matrixmultiply", "num-complex", "num-rational", "num-traits", "simba", "typenum", ] [[package]] name = "ndarray" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7c9125e8f6f10c9da3aad044cc918cf8784fa34de857b1aa68038eb05a50a9" dependencies = [ "matrixmultiply", "num-complex", "num-integer", "num-traits", "portable-atomic", "portable-atomic-util", "rawpointer", ] [[package]] name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-complex" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-rational" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ "num-bigint", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "numpy" version = "0.27.1" dependencies = [ "half", "libc", "nalgebra", "ndarray", "num-complex", "num-integer", "num-traits", "pyo3", "pyo3-build-config", "rustc-hash", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "portable-atomic" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" dependencies = [ "portable-atomic", ] [[package]] name = "proc-macro2" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab53c047fcd1a1d2a8820fe84f05d6be69e9526be40cb03b73f86b6b03e6d87d" dependencies = [ "indoc", "libc", "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", "unindent", ] [[package]] name = "pyo3-build-config" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b455933107de8642b4487ed26d912c2d899dec6114884214a0b3bb3be9261ea6" dependencies = [ "target-lexicon", ] [[package]] name = "pyo3-ffi" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c85c9cbfaddf651b1221594209aed57e9e5cff63c4d11d1feead529b872a089" dependencies = [ "libc", "pyo3-build-config", ] [[package]] name = "pyo3-macros" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a5b10c9bf9888125d917fb4d2ca2d25c8df94c7ab5a52e13313a07e050a3b02" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", "syn", ] [[package]] name = "pyo3-macros-backend" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03b51720d314836e53327f5871d4c0cfb4fb37cc2c4a11cc71907a86342c40f9" dependencies = [ "heck", "proc-macro2", "pyo3-build-config", "quote", "syn", ] [[package]] name = "quote" version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] name = "rawpointer" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "safe_arch" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" dependencies = [ "bytemuck", ] [[package]] name = "simba" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c99284beb21666094ba2b75bbceda012e610f5479dfcc2d6e2426f53197ffd95" dependencies = [ "approx", "num-complex", "num-traits", "paste", "wide", ] [[package]] name = "syn" version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "target-lexicon" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "typenum" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unindent" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" [[package]] name = "wide" version = "0.7.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" dependencies = [ "bytemuck", "safe_arch", ] [[package]] name = "zerocopy" version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", "syn", ] numpy-0.27.1/Cargo.toml0000644000000050230000000000100102710ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.74" name = "numpy" version = "0.27.1" authors = [ "The rust-numpy Project Developers", "PyO3 Project and Contributors ", ] build = "build.rs" exclude = [ ".github/*", ".gitignore", "codecov.yml", "x.py", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "PyO3-based Rust bindings of the NumPy C-API" documentation = "https://docs.rs/numpy" readme = "README.md" keywords = [ "python", "numpy", "ffi", "pyo3", ] categories = [ "api-bindings", "development-tools::ffi", "science", ] license = "BSD-2-Clause" repository = "https://github.com/PyO3/rust-numpy" [package.metadata.docs.rs] all-features = true [lib] name = "numpy" path = "src/lib.rs" [[test]] name = "array" path = "tests/array.rs" [[test]] name = "array_like" path = "tests/array_like.rs" [[test]] name = "borrow" path = "tests/borrow.rs" [[test]] name = "sum_products" path = "tests/sum_products.rs" [[test]] name = "to_py" path = "tests/to_py.rs" [[bench]] name = "array" path = "benches/array.rs" [[bench]] name = "borrow" path = "benches/borrow.rs" [dependencies.half] version = "2.0" optional = true default-features = false [dependencies.libc] version = "0.2" [dependencies.nalgebra] version = ">=0.30, <0.35" optional = true default-features = false [dependencies.ndarray] version = ">= 0.15, <=0.17" [dependencies.num-complex] version = ">= 0.2, < 0.5" [dependencies.num-integer] version = "0.1" [dependencies.num-traits] version = "0.2" [dependencies.pyo3] version = "0.27.0" features = ["macros"] default-features = false [dependencies.rustc-hash] version = "2.0" [dev-dependencies.nalgebra] version = ">=0.30, <0.35" features = ["std"] default-features = false [dev-dependencies.pyo3] version = "0.27.0" features = ["auto-initialize"] default-features = false [build-dependencies.pyo3-build-config] version = "0.27" features = ["resolve-config"] [lints.clippy] needless-lifetimes = "allow" [lints.rust] elided-lifetimes-in-paths = "deny" numpy-0.27.1/Cargo.toml.orig000064400000000000000000000026251046102023000137570ustar 00000000000000[package] name = "numpy" version = "0.27.1" authors = [ "The rust-numpy Project Developers", "PyO3 Project and Contributors ", ] description = "PyO3-based Rust bindings of the NumPy C-API" documentation = "https://docs.rs/numpy" edition = "2021" rust-version = "1.74" repository = "https://github.com/PyO3/rust-numpy" categories = ["api-bindings", "development-tools::ffi", "science"] keywords = ["python", "numpy", "ffi", "pyo3"] license = "BSD-2-Clause" exclude = [ ".github/*", ".gitignore", "codecov.yml", "x.py", ] [dependencies] half = { version = "2.0", default-features = false, optional = true } libc = "0.2" nalgebra = { version = ">=0.30, <0.35", default-features = false, optional = true } num-complex = ">= 0.2, < 0.5" num-integer = "0.1" num-traits = "0.2" ndarray = ">= 0.15, <=0.17" pyo3 = { version = "0.27.0", default-features = false, features = ["macros"] } rustc-hash = "2.0" [dev-dependencies] pyo3 = { version = "0.27.0", default-features = false, features = ["auto-initialize"]} nalgebra = { version = ">=0.30, <0.35", default-features = false, features = ["std"] } [build-dependencies] pyo3-build-config = { version = "0.27", features = ["resolve-config"]} [package.metadata.docs.rs] all-features = true [lints.rust] # We usually want to make the GIL lifetime explicit. elided-lifetimes-in-paths = "deny" [lints.clippy] needless-lifetimes = "allow" numpy-0.27.1/LICENSE000064400000000000000000000024511046102023000120720ustar 00000000000000BSD 2-Clause License Copyright (c) 2017, Toshiki Teramura All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. numpy-0.27.1/README.md000064400000000000000000000113751046102023000123510ustar 00000000000000rust-numpy =========== [![Actions Status](https://github.com/PyO3/rust-numpy/workflows/CI/badge.svg)](https://github.com/PyO3/rust-numpy/actions) [![Crate](https://img.shields.io/crates/v/numpy.svg)](https://crates.io/crates/numpy) [![Minimum rustc 1.74](https://img.shields.io/badge/rustc-1.74+-blue.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html) [![Documentation](https://docs.rs/numpy/badge.svg)](https://docs.rs/numpy) [![codecov](https://codecov.io/gh/PyO3/rust-numpy/branch/main/graph/badge.svg)](https://codecov.io/gh/PyO3/rust-numpy) Rust bindings for the NumPy C-API. ## API documentation - [Latest release](https://docs.rs/numpy) - [Current main](https://pyo3.github.io/rust-numpy) ## Requirements - Rust >= 1.74.0 - Basically, our MSRV follows the one of [PyO3](https://github.com/PyO3/pyo3) - Python >= 3.7 - Python 3.6 support was dropped from 0.16 - Some Rust libraries - [ndarray](https://github.com/rust-ndarray/ndarray) for Rust-side matrix library - [PyO3](https://github.com/PyO3/pyo3) for Python bindings - And more (see [Cargo.toml](Cargo.toml)) - [numpy](https://numpy.org/) installed in your Python environments (e.g., via `pip install numpy`) - We recommend `numpy >= 1.16.0`, though older versions may work ## Example ### Write a Python module in Rust Please see the [simple](examples/simple) example for how to get started. There are also examples using [ndarray-linalg](examples/linalg) and [rayon](examples/parallel). ```toml [lib] name = "rust_ext" crate-type = ["cdylib"] [dependencies] pyo3 = { version = "0.27", features = ["extension-module"] } numpy = "0.27" ``` ```rust #[pyo3::pymodule] mod rust_ext { use numpy::ndarray::{ArrayD, ArrayViewD, ArrayViewMutD}; use numpy::{IntoPyArray, PyArrayDyn, PyReadonlyArrayDyn, PyArrayMethods}; use pyo3::{pyfunction, PyResult, Python, Bound}; // example using immutable borrows producing a new array fn axpy(a: f64, x: ArrayViewD<'_, f64>, y: ArrayViewD<'_, f64>) -> ArrayD { a * &x + &y } // example using a mutable borrow to modify an array in-place fn mult(a: f64, mut x: ArrayViewMutD<'_, f64>) { x *= a; } // wrapper of `axpy` #[pyfunction(name = "axpy")] fn axpy_py<'py>( py: Python<'py>, a: f64, x: PyReadonlyArrayDyn<'py, f64>, y: PyReadonlyArrayDyn<'py, f64>, ) -> Bound<'py, PyArrayDyn> { let x = x.as_array(); let y = y.as_array(); let z = axpy(a, x, y); z.into_pyarray(py) } // wrapper of `mult` #[pyfunction(name = "mult")] fn mult_py<'py>(a: f64, x: &Bound<'py, PyArrayDyn>) { let x = unsafe { x.as_array_mut() }; mult(a, x); } } ``` ### Execute a Python program from Rust and get results ``` toml [package] name = "numpy-test" [dependencies] pyo3 = { version = "0.27", features = ["auto-initialize"] } numpy = "0.27" ``` ```rust use numpy::{PyArray1, PyArrayMethods}; use pyo3::{types::{IntoPyDict, PyAnyMethods}, PyResult, Python, ffi::c_str}; fn main() -> PyResult<()> { Python::attach(|py| { let np = py.import("numpy")?; let locals = [("np", np)].into_py_dict(py)?; let pyarray = py .eval(c_str!("np.absolute(np.array([-1, -2, -3], dtype='int32'))"), Some(&locals), None)? .cast_into::>()?; let readonly = pyarray.readonly(); let slice = readonly.as_slice()?; assert_eq!(slice, &[1, 2, 3]); Ok(()) }) } ``` ## Dependency on ndarray This crate uses types from `ndarray` in its public API. `ndarray` is re-exported in the crate root so that you do not need to specify it as a direct dependency. Furthermore, this crate is compatible with multiple versions of `ndarray` and therefore depends on a range of semver-incompatible versions, currently `>= 0.15, < 0.17`. Cargo does not automatically choose a single version of `ndarray` by itself if you depend directly or indirectly on anything but that exact range. It can therefore be necessary to manually unify these dependencies. For example, if you specify the following dependencies ```toml numpy = "0.27" ndarray = "0.15" ``` this will currently depend on both version `0.15.6` and `0.16.1` of `ndarray` by default even though `0.15.6` is within the range `>= 0.15, <= 0.17`. To fix this, you can run ```sh cargo update --package ndarray:0.16.1 --precise 0.15.6 ``` to achieve a single dependency on version `0.15.6` of `ndarray`. ## Contributing We welcome [issues](https://github.com/PyO3/rust-numpy/issues) and [pull requests](https://github.com/PyO3/rust-numpy/pulls). PyO3's [Contributing.md](https://github.com/PyO3/pyo3/blob/main/Contributing.md) is a nice guide for starting. Also, we have a [Gitter](https://gitter.im/PyO3/Lobby) channel for communicating. numpy-0.27.1/benches/array.rs000064400000000000000000000103751046102023000141640ustar 00000000000000#![feature(test)] extern crate test; use test::{black_box, Bencher}; use std::ops::Range; use numpy::{PyArray1, PyArray2, PyArray3}; use pyo3::{types::PyAnyMethods, Bound, IntoPyObjectExt, Python}; #[bench] fn extract_success(bencher: &mut Bencher) { Python::attach(|py| { let any = PyArray2::::zeros(py, (10, 10), false).into_any(); bencher.iter(|| { black_box(&any) .extract::>>() .unwrap() }); }); } #[bench] fn extract_failure(bencher: &mut Bencher) { Python::attach(|py| { let any = PyArray2::::zeros(py, (10, 10), false).into_any(); bencher.iter(|| { black_box(&any) .extract::>>() .unwrap_err() }); }); } #[bench] fn cast_success(bencher: &mut Bencher) { Python::attach(|py| { let any = PyArray2::::zeros(py, (10, 10), false).into_any(); bencher.iter(|| black_box(&any).cast::>().unwrap()); }); } #[bench] fn cast_failure(bencher: &mut Bencher) { Python::attach(|py| { let any = PyArray2::::zeros(py, (10, 10), false).into_any(); bencher.iter(|| black_box(&any).cast::>().unwrap_err()); }); } struct Iter(Range); impl Iterator for Iter { type Item = usize; fn next(&mut self) -> Option { self.0.next() } } fn from_iter(bencher: &mut Bencher, size: usize) { Python::attach(|py| { bencher.iter(|| { let iter = black_box(Iter(0..size)); PyArray1::from_iter(py, iter) }); }); } #[bench] fn from_iter_small(bencher: &mut Bencher) { from_iter(bencher, 2_usize.pow(5)); } #[bench] fn from_iter_medium(bencher: &mut Bencher) { from_iter(bencher, 2_usize.pow(10)); } #[bench] fn from_iter_large(bencher: &mut Bencher) { from_iter(bencher, 2_usize.pow(15)); } fn from_slice(bencher: &mut Bencher, size: usize) { let vec = (0..size).collect::>(); Python::attach(|py| { bencher.iter(|| { let slice = black_box(&vec); PyArray1::from_slice(py, slice) }); }); } #[bench] fn from_slice_small(bencher: &mut Bencher) { from_slice(bencher, 2_usize.pow(5)); } #[bench] fn from_slice_medium(bencher: &mut Bencher) { from_slice(bencher, 2_usize.pow(10)); } #[bench] fn from_slice_large(bencher: &mut Bencher) { from_slice(bencher, 2_usize.pow(15)); } fn from_object_slice(bencher: &mut Bencher, size: usize) { let vec = Python::attach(|py| { (0..size) .map(|val| val.into_py_any(py).unwrap()) .collect::>() }); Python::attach(|py| { bencher.iter(|| { let slice = black_box(&vec); PyArray1::from_slice(py, slice) }); }); } #[bench] fn from_object_slice_small(bencher: &mut Bencher) { from_object_slice(bencher, 2_usize.pow(5)); } #[bench] fn from_object_slice_medium(bencher: &mut Bencher) { from_object_slice(bencher, 2_usize.pow(10)); } #[bench] fn from_object_slice_large(bencher: &mut Bencher) { from_object_slice(bencher, 2_usize.pow(15)); } fn from_vec2(bencher: &mut Bencher, size: usize) { let vec2 = vec![vec![0; size]; size]; Python::attach(|py| { bencher.iter(|| { let vec2 = black_box(&vec2); PyArray2::from_vec2(py, vec2).unwrap() }); }); } #[bench] fn from_vec2_small(bencher: &mut Bencher) { from_vec2(bencher, 2_usize.pow(3)); } #[bench] fn from_vec2_medium(bencher: &mut Bencher) { from_vec2(bencher, 2_usize.pow(5)); } #[bench] fn from_vec2_large(bencher: &mut Bencher) { from_vec2(bencher, 2_usize.pow(8)); } fn from_vec3(bencher: &mut Bencher, size: usize) { let vec3 = vec![vec![vec![0; size]; size]; size]; Python::attach(|py| { bencher.iter(|| { let vec3 = black_box(&vec3); PyArray3::from_vec3(py, vec3).unwrap() }); }); } #[bench] fn from_vec3_small(bencher: &mut Bencher) { from_vec3(bencher, 2_usize.pow(2)); } #[bench] fn from_vec3_medium(bencher: &mut Bencher) { from_vec3(bencher, 2_usize.pow(4)); } #[bench] fn from_vec3_large(bencher: &mut Bencher) { from_vec3(bencher, 2_usize.pow(5)); } numpy-0.27.1/benches/borrow.rs000064400000000000000000000021241046102023000143510ustar 00000000000000#![feature(test)] extern crate test; use test::{black_box, Bencher}; use numpy::{PyArray, PyArrayMethods}; use pyo3::Python; #[bench] fn initial_shared_borrow(bencher: &mut Bencher) { Python::attach(|py| { let array = PyArray::::zeros(py, (6, 5, 4, 3, 2, 1), false); bencher.iter(|| { let array = black_box(&array); let _shared = array.readonly(); }); }); } #[bench] fn additional_shared_borrow(bencher: &mut Bencher) { Python::attach(|py| { let array = PyArray::::zeros(py, (6, 5, 4, 3, 2, 1), false); let _shared = (0..128).map(|_| array.readonly()).collect::>(); bencher.iter(|| { let array = black_box(&array); let _shared = array.readonly(); }); }); } #[bench] fn exclusive_borrow(bencher: &mut Bencher) { Python::attach(|py| { let array = PyArray::::zeros(py, (6, 5, 4, 3, 2, 1), false); bencher.iter(|| { let array = black_box(&array); let _exclusive = array.readwrite(); }); }); } numpy-0.27.1/build.rs000064400000000000000000000000711046102023000125260ustar 00000000000000fn main() { pyo3_build_config::use_pyo3_cfgs(); } numpy-0.27.1/src/array.rs000064400000000000000000001510111046102023000133350ustar 00000000000000//! Safe interface for NumPy's [N-dimensional arrays][ndarray] //! //! [ndarray]: https://numpy.org/doc/stable/reference/arrays.ndarray.html use std::{ marker::PhantomData, mem, os::raw::{c_int, c_void}, ptr, slice, }; use ndarray::{ Array, ArrayBase, ArrayView, ArrayViewMut, Axis, Data, Dim, Dimension, IntoDimension, Ix0, Ix1, Ix2, Ix3, Ix4, Ix5, Ix6, IxDyn, RawArrayView, RawArrayViewMut, RawData, ShapeBuilder, StrideShape, }; use num_traits::AsPrimitive; use pyo3::{ ffi, types::{DerefToPyAny, PyModule}, Bound, CastError, Py, PyAny, PyErr, PyResult, PyTypeCheck, PyTypeInfo, Python, }; use crate::borrow::{PyReadonlyArray, PyReadwriteArray}; use crate::cold; use crate::convert::{ArrayExt, IntoPyArray, NpyIndex, ToNpyDims, ToPyArray}; use crate::dtype::{Element, PyArrayDescrMethods}; use crate::error::{ BorrowError, DimensionalityError, FromVecError, IgnoreError, NotContiguousError, TypeError, DIMENSIONALITY_MISMATCH_ERR, MAX_DIMENSIONALITY_ERR, }; use crate::npyffi::{self, npy_intp, NPY_ORDER, PY_ARRAY_API}; use crate::slice_container::PySliceContainer; use crate::untyped_array::{PyUntypedArray, PyUntypedArrayMethods}; /// A safe, statically-typed wrapper for NumPy's [`ndarray`][ndarray] class. /// /// # Memory location /// /// - Allocated by Rust: Constructed via [`IntoPyArray`] or /// [`from_vec`][Self::from_vec] or [`from_owned_array`][Self::from_owned_array]. /// /// These methods transfers ownership of the Rust allocation into a suitable Python object /// and uses the memory as the internal buffer backing the NumPy array. /// /// Please note that some destructive methods like [`resize`][Self::resize] will fail /// when used with this kind of array as NumPy cannot reallocate the internal buffer. /// /// - Allocated by NumPy: Constructed via other methods, like [`ToPyArray`] or /// [`from_slice`][Self::from_slice] or [`from_array`][Self::from_array]. /// /// These methods allocate memory in Python's private heap via NumPy's API. /// /// In both cases, `PyArray` is managed by Python so it can neither be moved from /// nor deallocated manually. /// /// # References /// /// Like [`new`][Self::new], all constructor methods of `PyArray` return a shared reference `&PyArray` /// instead of an owned value. This design follows [PyO3's ownership concept][pyo3-memory], /// i.e. the return value is GIL-bound owning reference into Python's heap. /// /// # Element type and dimensionality /// /// `PyArray` has two type parametes `T` and `D`. /// `T` represents the type of its elements, e.g. [`f32`] or [`PyObject`]. /// `D` represents its dimensionality, e.g [`Ix2`][type@Ix2] or [`IxDyn`][type@IxDyn]. /// /// Element types are Rust types which implement the [`Element`] trait. /// Dimensions are represented by the [`ndarray::Dimension`] trait. /// /// Typically, `Ix1, Ix2, ...` are used for fixed dimensionality arrays, /// and `IxDyn` is used for dynamic dimensionality arrays. Type aliases /// for combining `PyArray` with these types are provided, e.g. [`PyArray1`] or [`PyArrayDyn`]. /// /// To specify concrete dimension like `3×4×5`, types which implement the [`ndarray::IntoDimension`] /// trait are used. Typically, this means arrays like `[3, 4, 5]` or tuples like `(3, 4, 5)`. /// /// # Example /// /// ``` /// use numpy::{PyArray, PyArrayMethods}; /// use ndarray::{array, Array}; /// use pyo3::Python; /// /// Python::attach(|py| { /// let pyarray = PyArray::arange(py, 0., 4., 1.).reshape([2, 2]).unwrap(); /// let array = array![[3., 4.], [5., 6.]]; /// /// assert_eq!( /// array.dot(&pyarray.readonly().as_array()), /// array![[8., 15.], [12., 23.]] /// ); /// }); /// ``` /// /// [ndarray]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html /// [pyo3-memory]: https://pyo3.rs/main/memory.html #[repr(transparent)] pub struct PyArray(PyAny, PhantomData, PhantomData); /// Zero-dimensional array. pub type PyArray0 = PyArray; /// One-dimensional array. pub type PyArray1 = PyArray; /// Two-dimensional array. pub type PyArray2 = PyArray; /// Three-dimensional array. pub type PyArray3 = PyArray; /// Four-dimensional array. pub type PyArray4 = PyArray; /// Five-dimensional array. pub type PyArray5 = PyArray; /// Six-dimensional array. pub type PyArray6 = PyArray; /// Dynamic-dimensional array. pub type PyArrayDyn = PyArray; /// Returns a handle to NumPy's multiarray module. pub fn get_array_module<'py>(py: Python<'py>) -> PyResult> { PyModule::import(py, npyffi::array::mod_name(py)?) } impl DerefToPyAny for PyArray {} unsafe impl PyTypeInfo for PyArray { const NAME: &'static str = "PyArray"; const MODULE: Option<&'static str> = Some("numpy"); fn type_object_raw<'py>(py: Python<'py>) -> *mut ffi::PyTypeObject { unsafe { npyffi::PY_ARRAY_API.get_type_object(py, npyffi::NpyTypes::PyArray_Type) } } fn is_type_of(ob: &Bound<'_, PyAny>) -> bool { Self::extract::(ob).is_ok() } } impl PyArray { fn extract<'a, 'py, E>(ob: &'a Bound<'py, PyAny>) -> Result<&'a Bound<'py, Self>, E> where E: From> + From + From>, { // Check if the object is an array. let array = unsafe { if npyffi::PyArray_Check(ob.py(), ob.as_ptr()) == 0 { return Err(CastError::new( ob.as_borrowed(), ::classinfo_object(ob.py()), ) .into()); } ob.cast_unchecked::() }; // Check if the dimensionality matches `D`. let src_ndim = array.ndim(); if let Some(dst_ndim) = D::NDIM { if src_ndim != dst_ndim { return Err(DimensionalityError::new(src_ndim, dst_ndim).into()); } } // Check if the element type matches `T`. let src_dtype = array.dtype(); let dst_dtype = T::get_dtype(ob.py()); if !src_dtype.is_equiv_to(&dst_dtype) { return Err(TypeError::new(src_dtype, dst_dtype).into()); } Ok(array) } /// Creates a new uninitialized NumPy array. /// /// If `is_fortran` is true, then it has Fortran/column-major order, /// otherwise it has C/row-major order. /// /// # Safety /// /// The returned array will always be safe to be dropped as the elements must either /// be trivially copyable (as indicated by `::IS_COPY`) or be pointers /// into Python's heap, which NumPy will automatically zero-initialize. /// /// However, the elements themselves will not be valid and should be initialized manually /// using raw pointers obtained via [`uget_raw`][Self::uget_raw]. Before that, all methods /// which produce references to the elements invoke undefined behaviour. In particular, /// zero-initialized pointers are _not_ valid instances of `PyObject`. /// /// # Example /// /// ``` /// use numpy::prelude::*; /// use numpy::PyArray3; /// use pyo3::Python; /// /// Python::attach(|py| { /// let arr = unsafe { /// let arr = PyArray3::::new(py, [4, 5, 6], false); /// /// for i in 0..4 { /// for j in 0..5 { /// for k in 0..6 { /// arr.uget_raw([i, j, k]).write((i * j * k) as i32); /// } /// } /// } /// /// arr /// }; /// /// assert_eq!(arr.shape(), &[4, 5, 6]); /// }); /// ``` pub unsafe fn new<'py, ID>(py: Python<'py>, dims: ID, is_fortran: bool) -> Bound<'py, Self> where ID: IntoDimension, { let flags = c_int::from(is_fortran); Self::new_uninit(py, dims, ptr::null_mut(), flags) } pub(crate) unsafe fn new_uninit<'py, ID>( py: Python<'py>, dims: ID, strides: *const npy_intp, flag: c_int, ) -> Bound<'py, Self> where ID: IntoDimension, { let mut dims = dims.into_dimension(); let ptr = PY_ARRAY_API.PyArray_NewFromDescr( py, PY_ARRAY_API.get_type_object(py, npyffi::NpyTypes::PyArray_Type), T::get_dtype(py).into_dtype_ptr(), dims.ndim_cint(), dims.as_dims_ptr(), strides as *mut npy_intp, // strides ptr::null_mut(), // data flag, // flag ptr::null_mut(), // obj ); Bound::from_owned_ptr(py, ptr).cast_into_unchecked() } unsafe fn new_with_data<'py, ID>( py: Python<'py>, dims: ID, strides: *const npy_intp, data_ptr: *const T, container: *mut PyAny, ) -> Bound<'py, Self> where ID: IntoDimension, { let mut dims = dims.into_dimension(); let ptr = PY_ARRAY_API.PyArray_NewFromDescr( py, PY_ARRAY_API.get_type_object(py, npyffi::NpyTypes::PyArray_Type), T::get_dtype(py).into_dtype_ptr(), dims.ndim_cint(), dims.as_dims_ptr(), strides as *mut npy_intp, // strides data_ptr as *mut c_void, // data npyffi::NPY_ARRAY_WRITEABLE, // flag ptr::null_mut(), // obj ); PY_ARRAY_API.PyArray_SetBaseObject( py, ptr as *mut npyffi::PyArrayObject, container as *mut ffi::PyObject, ); Bound::from_owned_ptr(py, ptr).cast_into_unchecked() } pub(crate) unsafe fn from_raw_parts<'py>( py: Python<'py>, dims: D, strides: *const npy_intp, data_ptr: *const T, container: PySliceContainer, ) -> Bound<'py, Self> { let container = Bound::new(py, container) .expect("Failed to create slice container") .into_ptr(); Self::new_with_data(py, dims, strides, data_ptr, container.cast()) } /// Creates a NumPy array backed by `array` and ties its ownership to the Python object `container`. /// /// The resulting NumPy array will be writeable from Python space. If this is undesireable, use /// [PyReadwriteArray::make_nonwriteable]. /// /// # Safety /// /// `container` is set as a base object of the returned array which must not be dropped until `container` is dropped. /// Furthermore, `array` must not be reallocated from the time this method is called and until `container` is dropped. /// /// # Example /// /// ```rust /// # use pyo3::prelude::*; /// # use numpy::{ndarray::Array1, PyArray1}; /// # /// #[pyclass] /// struct Owner { /// array: Array1, /// } /// /// #[pymethods] /// impl Owner { /// #[getter] /// fn array<'py>(this: Bound<'py, Self>) -> Bound<'py, PyArray1> { /// let array = &this.borrow().array; /// /// // SAFETY: The memory backing `array` will stay valid as long as this object is alive /// // as we do not modify `array` in any way which would cause it to be reallocated. /// unsafe { PyArray1::borrow_from_array(array, this.into_any()) } /// } /// } /// ``` pub unsafe fn borrow_from_array<'py, S>( array: &ArrayBase, container: Bound<'py, PyAny>, ) -> Bound<'py, Self> where S: Data, { let (strides, dims) = (array.npy_strides(), array.raw_dim()); let data_ptr = array.as_ptr(); let py = container.py(); Self::new_with_data( py, dims, strides.as_ptr(), data_ptr, container.into_ptr().cast(), ) } /// Construct a new NumPy array filled with zeros. /// /// If `is_fortran` is true, then it has Fortran/column-major order, /// otherwise it has C/row-major order. /// /// For arrays of Python objects, this will fill the array /// with valid pointers to zero-valued Python integer objects. /// /// See also [`numpy.zeros`][numpy-zeros] and [`PyArray_Zeros`][PyArray_Zeros]. /// /// # Example /// /// ``` /// use numpy::{PyArray2, PyArrayMethods}; /// use pyo3::Python; /// /// Python::attach(|py| { /// let pyarray = PyArray2::::zeros(py, [2, 2], true); /// /// assert_eq!(pyarray.readonly().as_slice().unwrap(), [0; 4]); /// }); /// ``` /// /// [numpy-zeros]: https://numpy.org/doc/stable/reference/generated/numpy.zeros.html /// [PyArray_Zeros]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_Zeros pub fn zeros(py: Python<'_>, dims: ID, is_fortran: bool) -> Bound<'_, Self> where ID: IntoDimension, { let mut dims = dims.into_dimension(); unsafe { let ptr = PY_ARRAY_API.PyArray_Zeros( py, dims.ndim_cint(), dims.as_dims_ptr(), T::get_dtype(py).into_dtype_ptr(), if is_fortran { -1 } else { 0 }, ); Bound::from_owned_ptr(py, ptr).cast_into_unchecked() } } /// Constructs a NumPy from an [`ndarray::Array`] /// /// This method uses the internal [`Vec`] of the [`ndarray::Array`] as the base object of the NumPy array. /// /// # Example /// /// ``` /// use numpy::{PyArray, PyArrayMethods}; /// use ndarray::array; /// use pyo3::Python; /// /// Python::attach(|py| { /// let pyarray = PyArray::from_owned_array(py, array![[1, 2], [3, 4]]); /// /// assert_eq!(pyarray.readonly().as_array(), array![[1, 2], [3, 4]]); /// }); /// ``` pub fn from_owned_array(py: Python<'_>, mut arr: Array) -> Bound<'_, Self> { let (strides, dims) = (arr.npy_strides(), arr.raw_dim()); let data_ptr = arr.as_mut_ptr(); unsafe { Self::from_raw_parts( py, dims, strides.as_ptr(), data_ptr, PySliceContainer::from(arr), ) } } /// Construct a NumPy array from a [`ndarray::ArrayBase`]. /// /// This method allocates memory in Python's heap via the NumPy API, /// and then copies all elements of the array there. /// /// # Example /// /// ``` /// use numpy::{PyArray, PyArrayMethods}; /// use ndarray::array; /// use pyo3::Python; /// /// Python::attach(|py| { /// let pyarray = PyArray::from_array(py, &array![[1, 2], [3, 4]]); /// /// assert_eq!(pyarray.readonly().as_array(), array![[1, 2], [3, 4]]); /// }); /// ``` pub fn from_array<'py, S>(py: Python<'py>, arr: &ArrayBase) -> Bound<'py, Self> where S: Data, { ToPyArray::to_pyarray(arr, py) } } impl PyArray, D> { /// Construct a NumPy array containing objects stored in a [`ndarray::Array`] /// /// This method uses the internal [`Vec`] of the [`ndarray::Array`] as the base object of the NumPy array. /// /// # Example /// /// ``` /// use ndarray::array; /// use pyo3::{pyclass, Py, Python, types::PyAnyMethods}; /// use numpy::{PyArray, PyArrayMethods}; /// /// #[pyclass] /// # #[allow(dead_code)] /// struct CustomElement { /// foo: i32, /// bar: f64, /// } /// /// Python::attach(|py| { /// let array = array![ /// Py::new(py, CustomElement { /// foo: 1, /// bar: 2.0, /// }).unwrap(), /// Py::new(py, CustomElement { /// foo: 3, /// bar: 4.0, /// }).unwrap(), /// ]; /// /// let pyarray = PyArray::from_owned_object_array(py, array); /// /// assert!(pyarray.readonly().as_array().get(0).unwrap().bind(py).is_instance_of::()); /// }); /// ``` pub fn from_owned_object_array(py: Python<'_>, mut arr: Array, D>) -> Bound<'_, Self> { let (strides, dims) = (arr.npy_strides(), arr.raw_dim()); let data_ptr = arr.as_mut_ptr().cast::>().cast_const(); unsafe { Self::from_raw_parts( py, dims, strides.as_ptr(), data_ptr, PySliceContainer::from(arr), ) } } } impl PyArray { /// Construct a one-dimensional array from a [mod@slice]. /// /// # Example /// /// ``` /// use numpy::{PyArray, PyArrayMethods}; /// use pyo3::Python; /// /// Python::attach(|py| { /// let slice = &[1, 2, 3, 4, 5]; /// let pyarray = PyArray::from_slice(py, slice); /// assert_eq!(pyarray.readonly().as_slice().unwrap(), &[1, 2, 3, 4, 5]); /// }); /// ``` pub fn from_slice<'py>(py: Python<'py>, slice: &[T]) -> Bound<'py, Self> { unsafe { let array = PyArray::new(py, [slice.len()], false); let mut data_ptr = array.data(); clone_elements(py, slice, &mut data_ptr); array } } /// Construct a one-dimensional array from a [`Vec`][Vec]. /// /// # Example /// /// ``` /// use numpy::{PyArray, PyArrayMethods}; /// use pyo3::Python; /// /// Python::attach(|py| { /// let vec = vec![1, 2, 3, 4, 5]; /// let pyarray = PyArray::from_vec(py, vec); /// assert_eq!(pyarray.readonly().as_slice().unwrap(), &[1, 2, 3, 4, 5]); /// }); /// ``` #[inline(always)] pub fn from_vec<'py>(py: Python<'py>, vec: Vec) -> Bound<'py, Self> { vec.into_pyarray(py) } /// Construct a one-dimensional array from an [`Iterator`]. /// /// If no reliable [`size_hint`][Iterator::size_hint] is available, /// this method can allocate memory multiple times, which can hurt performance. /// /// # Example /// /// ``` /// use numpy::{PyArray, PyArrayMethods}; /// use pyo3::Python; /// /// Python::attach(|py| { /// let pyarray = PyArray::from_iter(py, "abcde".chars().map(u32::from)); /// assert_eq!(pyarray.readonly().as_slice().unwrap(), &[97, 98, 99, 100, 101]); /// }); /// ``` pub fn from_iter(py: Python<'_>, iter: I) -> Bound<'_, Self> where I: IntoIterator, { let data = iter.into_iter().collect::>(); data.into_pyarray(py) } } impl PyArray { /// Construct a two-dimension array from a [`Vec>`][Vec]. /// /// This function checks all dimensions of the inner vectors and returns /// an error if they are not all equal. /// /// # Example /// /// ``` /// use numpy::{PyArray, PyArrayMethods}; /// use pyo3::Python; /// use ndarray::array; /// /// Python::attach(|py| { /// let vec2 = vec![vec![11, 12], vec![21, 22]]; /// let pyarray = PyArray::from_vec2(py, &vec2).unwrap(); /// assert_eq!(pyarray.readonly().as_array(), array![[11, 12], [21, 22]]); /// /// let ragged_vec2 = vec![vec![11, 12], vec![21]]; /// assert!(PyArray::from_vec2(py, &ragged_vec2).is_err()); /// }); /// ``` pub fn from_vec2<'py>(py: Python<'py>, v: &[Vec]) -> Result, FromVecError> { let len2 = v.first().map_or(0, |v| v.len()); let dims = [v.len(), len2]; // SAFETY: The result of `Self::new` is always safe to drop. unsafe { let array = Self::new(py, dims, false); let mut data_ptr = array.data(); for v in v { if v.len() != len2 { cold(); return Err(FromVecError::new(v.len(), len2)); } clone_elements(py, v, &mut data_ptr); } Ok(array) } } } impl PyArray { /// Construct a three-dimensional array from a [`Vec>>`][Vec]. /// /// This function checks all dimensions of the inner vectors and returns /// an error if they are not all equal. /// /// # Example /// /// ``` /// use numpy::{PyArray, PyArrayMethods}; /// use pyo3::Python; /// use ndarray::array; /// /// Python::attach(|py| { /// let vec3 = vec![ /// vec![vec![111, 112], vec![121, 122]], /// vec![vec![211, 212], vec![221, 222]], /// ]; /// let pyarray = PyArray::from_vec3(py, &vec3).unwrap(); /// assert_eq!( /// pyarray.readonly().as_array(), /// array![[[111, 112], [121, 122]], [[211, 212], [221, 222]]] /// ); /// /// let ragged_vec3 = vec![ /// vec![vec![111, 112], vec![121, 122]], /// vec![vec![211], vec![221, 222]], /// ]; /// assert!(PyArray::from_vec3(py, &ragged_vec3).is_err()); /// }); /// ``` pub fn from_vec3<'py>( py: Python<'py>, v: &[Vec>], ) -> Result, FromVecError> { let len2 = v.first().map_or(0, |v| v.len()); let len3 = v.first().map_or(0, |v| v.first().map_or(0, |v| v.len())); let dims = [v.len(), len2, len3]; // SAFETY: The result of `Self::new` is always safe to drop. unsafe { let array = Self::new(py, dims, false); let mut data_ptr = array.data(); for v in v { if v.len() != len2 { cold(); return Err(FromVecError::new(v.len(), len2)); } for v in v { if v.len() != len3 { cold(); return Err(FromVecError::new(v.len(), len3)); } clone_elements(py, v, &mut data_ptr); } } Ok(array) } } } impl> PyArray { /// Return evenly spaced values within a given interval. /// /// See [numpy.arange][numpy.arange] for the Python API and [PyArray_Arange][PyArray_Arange] for the C API. /// /// # Example /// /// ``` /// use numpy::{PyArray, PyArrayMethods}; /// use pyo3::Python; /// /// Python::attach(|py| { /// let pyarray = PyArray::arange(py, 2.0, 4.0, 0.5); /// assert_eq!(pyarray.readonly().as_slice().unwrap(), &[2.0, 2.5, 3.0, 3.5]); /// /// let pyarray = PyArray::arange(py, -2, 4, 3); /// assert_eq!(pyarray.readonly().as_slice().unwrap(), &[-2, 1]); /// }); /// ``` /// /// [numpy.arange]: https://numpy.org/doc/stable/reference/generated/numpy.arange.html /// [PyArray_Arange]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_Arange pub fn arange<'py>(py: Python<'py>, start: T, stop: T, step: T) -> Bound<'py, Self> { unsafe { let ptr = PY_ARRAY_API.PyArray_Arange( py, start.as_(), stop.as_(), step.as_(), T::get_dtype(py).num(), ); Bound::from_owned_ptr(py, ptr).cast_into_unchecked() } } } unsafe fn clone_elements(py: Python<'_>, elems: &[T], data_ptr: &mut *mut T) { if T::IS_COPY { ptr::copy_nonoverlapping(elems.as_ptr(), *data_ptr, elems.len()); *data_ptr = data_ptr.add(elems.len()); } else { for elem in elems { data_ptr.write(elem.clone_ref(py)); *data_ptr = data_ptr.add(1); } } } /// Implementation of functionality for [`PyArray`]. #[doc(alias = "PyArray")] pub trait PyArrayMethods<'py, T, D>: PyUntypedArrayMethods<'py> { /// Access an untyped representation of this array. fn as_untyped(&self) -> &Bound<'py, PyUntypedArray>; /// Returns a pointer to the first element of the array. fn data(&self) -> *mut T; /// Same as [`shape`][PyUntypedArray::shape], but returns `D` instead of `&[usize]`. #[inline(always)] fn dims(&self) -> D where D: Dimension, { D::from_dimension(&Dim(self.shape())).expect(DIMENSIONALITY_MISMATCH_ERR) } /// Returns an immutable view of the internal data as a slice. /// /// # Safety /// /// Calling this method is undefined behaviour if the underlying array /// is aliased mutably by other instances of `PyArray` /// or concurrently modified by Python or other native code. /// /// Please consider the safe alternative [`PyReadonlyArray::as_slice`]. unsafe fn as_slice(&self) -> Result<&[T], NotContiguousError> where T: Element, D: Dimension, { if self.is_contiguous() { Ok(slice::from_raw_parts(self.data(), self.len())) } else { Err(NotContiguousError) } } /// Returns a mutable view of the internal data as a slice. /// /// # Safety /// /// Calling this method is undefined behaviour if the underlying array /// is aliased immutably or mutably by other instances of [`PyArray`] /// or concurrently modified by Python or other native code. /// /// Please consider the safe alternative [`PyReadwriteArray::as_slice_mut`]. #[allow(clippy::mut_from_ref)] unsafe fn as_slice_mut(&self) -> Result<&mut [T], NotContiguousError> where T: Element, D: Dimension, { if self.is_contiguous() { Ok(slice::from_raw_parts_mut(self.data(), self.len())) } else { Err(NotContiguousError) } } /// Get a reference of the specified element if the given index is valid. /// /// # Safety /// /// Calling this method is undefined behaviour if the underlying array /// is aliased mutably by other instances of `PyArray` /// or concurrently modified by Python or other native code. /// /// Consider using safe alternatives like [`PyReadonlyArray::get`]. /// /// # Example /// /// ``` /// use numpy::{PyArray, PyArrayMethods}; /// use pyo3::Python; /// /// Python::attach(|py| { /// let pyarray = PyArray::arange(py, 0, 16, 1).reshape([2, 2, 4]).unwrap(); /// /// assert_eq!(unsafe { *pyarray.get([1, 0, 3]).unwrap() }, 11); /// }); /// ``` unsafe fn get(&self, index: impl NpyIndex) -> Option<&T> where T: Element, D: Dimension; /// Same as [`get`][Self::get], but returns `Option<&mut T>`. /// /// # Safety /// /// Calling this method is undefined behaviour if the underlying array /// is aliased immutably or mutably by other instances of [`PyArray`] /// or concurrently modified by Python or other native code. /// /// Consider using safe alternatives like [`PyReadwriteArray::get_mut`]. /// /// # Example /// /// ``` /// use numpy::{PyArray, PyArrayMethods}; /// use pyo3::Python; /// /// Python::attach(|py| { /// let pyarray = PyArray::arange(py, 0, 16, 1).reshape([2, 2, 4]).unwrap(); /// /// unsafe { /// *pyarray.get_mut([1, 0, 3]).unwrap() = 42; /// } /// /// assert_eq!(unsafe { *pyarray.get([1, 0, 3]).unwrap() }, 42); /// }); /// ``` #[allow(clippy::mut_from_ref)] unsafe fn get_mut(&self, index: impl NpyIndex) -> Option<&mut T> where T: Element, D: Dimension; /// Get an immutable reference of the specified element, /// without checking the given index. /// /// See [`NpyIndex`] for what types can be used as the index. /// /// # Safety /// /// Passing an invalid index is undefined behavior. /// The element must also have been initialized and /// all other references to it is must also be shared. /// /// See [`PyReadonlyArray::get`] for a safe alternative. /// /// # Example /// /// ``` /// use numpy::{PyArray, PyArrayMethods}; /// use pyo3::Python; /// /// Python::attach(|py| { /// let pyarray = PyArray::arange(py, 0, 16, 1).reshape([2, 2, 4]).unwrap(); /// /// assert_eq!(unsafe { *pyarray.uget([1, 0, 3]) }, 11); /// }); /// ``` #[inline(always)] unsafe fn uget(&self, index: Idx) -> &T where T: Element, D: Dimension, Idx: NpyIndex, { &*self.uget_raw(index) } /// Same as [`uget`](Self::uget), but returns `&mut T`. /// /// # Safety /// /// Passing an invalid index is undefined behavior. /// The element must also have been initialized and /// other references to it must not exist. /// /// See [`PyReadwriteArray::get_mut`] for a safe alternative. #[inline(always)] #[allow(clippy::mut_from_ref)] unsafe fn uget_mut(&self, index: Idx) -> &mut T where T: Element, D: Dimension, Idx: NpyIndex, { &mut *self.uget_raw(index) } /// Same as [`uget`][Self::uget], but returns `*mut T`. /// /// # Safety /// /// Passing an invalid index is undefined behavior. #[inline(always)] unsafe fn uget_raw(&self, index: Idx) -> *mut T where T: Element, D: Dimension, Idx: NpyIndex, { let offset = index.get_unchecked::(self.strides()); self.data().offset(offset) as *mut _ } /// Get a copy of the specified element in the array. /// /// See [`NpyIndex`] for what types can be used as the index. /// /// # Example /// ``` /// use numpy::{PyArray, PyArrayMethods}; /// use pyo3::Python; /// /// Python::attach(|py| { /// let pyarray = PyArray::arange(py, 0, 16, 1).reshape([2, 2, 4]).unwrap(); /// /// assert_eq!(pyarray.get_owned([1, 0, 3]), Some(11)); /// }); /// ``` fn get_owned(&self, index: Idx) -> Option where T: Element, D: Dimension, Idx: NpyIndex; /// Turn an array with fixed dimensionality into one with dynamic dimensionality. fn to_dyn(&self) -> &Bound<'py, PyArray> where T: Element, D: Dimension; /// Returns a copy of the internal data of the array as a [`Vec`]. /// /// Fails if the internal array is not contiguous. See also [`as_slice`][Self::as_slice]. /// /// # Example /// /// ``` /// use numpy::{PyArray2, PyArrayMethods}; /// use pyo3::{Python, types::PyAnyMethods, ffi::c_str}; /// /// # fn main() -> pyo3::PyResult<()> { /// Python::attach(|py| { /// let pyarray= py /// .eval(c_str!("__import__('numpy').array([[0, 1], [2, 3]], dtype='int64')"), None, None)? /// .cast_into::>()?; /// /// assert_eq!(pyarray.to_vec()?, vec![0, 1, 2, 3]); /// # Ok(()) /// }) /// # } /// ``` fn to_vec(&self) -> Result, NotContiguousError> where T: Element, D: Dimension; /// Get an immutable borrow of the NumPy array fn try_readonly(&self) -> Result, BorrowError> where T: Element, D: Dimension; /// Get an immutable borrow of the NumPy array /// /// # Panics /// /// Panics if the allocation backing the array is currently mutably borrowed. /// /// For a non-panicking variant, use [`try_readonly`][Self::try_readonly]. fn readonly(&self) -> PyReadonlyArray<'py, T, D> where T: Element, D: Dimension, { self.try_readonly().unwrap() } /// Get a mutable borrow of the NumPy array fn try_readwrite(&self) -> Result, BorrowError> where T: Element, D: Dimension; /// Get a mutable borrow of the NumPy array /// /// # Panics /// /// Panics if the allocation backing the array is currently borrowed or /// if the array is [flagged as][flags] not writeable. /// /// For a non-panicking variant, use [`try_readwrite`][Self::try_readwrite]. /// /// [flags]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.flags.html fn readwrite(&self) -> PyReadwriteArray<'py, T, D> where T: Element, D: Dimension, { self.try_readwrite().unwrap() } /// Returns an [`ArrayView`] of the internal array. /// /// See also [`PyReadonlyArray::as_array`]. /// /// # Safety /// /// Calling this method invalidates all exclusive references to the internal data, e.g. `&mut [T]` or `ArrayViewMut`. unsafe fn as_array(&self) -> ArrayView<'_, T, D> where T: Element, D: Dimension; /// Returns an [`ArrayViewMut`] of the internal array. /// /// See also [`PyReadwriteArray::as_array_mut`]. /// /// # Safety /// /// Calling this method invalidates all other references to the internal data, e.g. `ArrayView` or `ArrayViewMut`. unsafe fn as_array_mut(&self) -> ArrayViewMut<'_, T, D> where T: Element, D: Dimension; /// Returns the internal array as [`RawArrayView`] enabling element access via raw pointers fn as_raw_array(&self) -> RawArrayView where T: Element, D: Dimension; /// Returns the internal array as [`RawArrayViewMut`] enabling element access via raw pointers fn as_raw_array_mut(&self) -> RawArrayViewMut where T: Element, D: Dimension; /// Get a copy of the array as an [`ndarray::Array`]. /// /// # Example /// /// ``` /// use numpy::{PyArray, PyArrayMethods}; /// use ndarray::array; /// use pyo3::Python; /// /// Python::attach(|py| { /// let pyarray = PyArray::arange(py, 0, 4, 1).reshape([2, 2]).unwrap(); /// /// assert_eq!( /// pyarray.to_owned_array(), /// array![[0, 1], [2, 3]] /// ) /// }); /// ``` fn to_owned_array(&self) -> Array where T: Element, D: Dimension; /// Copies `self` into `other`, performing a data type conversion if necessary. /// /// See also [`PyArray_CopyInto`][PyArray_CopyInto]. /// /// # Example /// /// ``` /// use numpy::{PyArray, PyArrayMethods}; /// use pyo3::Python; /// /// Python::attach(|py| { /// let pyarray_f = PyArray::arange(py, 2.0, 5.0, 1.0); /// let pyarray_i = unsafe { PyArray::::new(py, [3], false) }; /// /// assert!(pyarray_f.copy_to(&pyarray_i).is_ok()); /// /// assert_eq!(pyarray_i.readonly().as_slice().unwrap(), &[2, 3, 4]); /// }); /// ``` /// /// [PyArray_CopyInto]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_CopyInto fn copy_to(&self, other: &Bound<'py, PyArray>) -> PyResult<()> where T: Element; /// Deprecated version of [`cast_array`](PyArrayMethods::cast_array) #[deprecated(since = "0.26.0", note = "use `cast_array` instead")] #[inline] fn cast(&self, is_fortran: bool) -> PyResult>> where T: Element, { self.cast_array(is_fortran) } /// Cast the `PyArray` to `PyArray`, by allocating a new array. /// /// See also [`PyArray_CastToType`][PyArray_CastToType]. /// /// # Example /// /// ``` /// use numpy::{PyArray, PyArrayMethods}; /// use pyo3::Python; /// /// Python::attach(|py| { /// let pyarray_f = PyArray::arange(py, 2.0, 5.0, 1.0); /// /// let pyarray_i = pyarray_f.cast_array::(false).unwrap(); /// /// assert_eq!(pyarray_i.readonly().as_slice().unwrap(), &[2, 3, 4]); /// }); /// ``` /// /// [PyArray_CastToType]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_CastToType fn cast_array(&self, is_fortran: bool) -> PyResult>> where T: Element; /// A view of `self` with a different order of axes determined by `axes`. /// /// If `axes` is `None`, the order of axes is reversed which corresponds to the standard matrix transpose. /// /// See also [`numpy.transpose`][numpy-transpose] and [`PyArray_Transpose`][PyArray_Transpose]. /// /// # Example /// /// ``` /// use numpy::prelude::*; /// use numpy::PyArray; /// use pyo3::Python; /// use ndarray::array; /// /// Python::attach(|py| { /// let array = array![[0, 1, 2], [3, 4, 5]].into_pyarray(py); /// /// let array = array.permute(Some([1, 0])).unwrap(); /// /// assert_eq!(array.readonly().as_array(), array![[0, 3], [1, 4], [2, 5]]); /// }); /// ``` /// /// [numpy-transpose]: https://numpy.org/doc/stable/reference/generated/numpy.transpose.html /// [PyArray_Transpose]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_Transpose fn permute(&self, axes: Option) -> PyResult>> where T: Element; /// Special case of [`permute`][Self::permute] which reverses the order the axes. fn transpose(&self) -> PyResult>> where T: Element, { self.permute::<()>(None) } /// Construct a new array which has same values as `self`, /// but has different dimensions specified by `shape` /// and a possibly different memory order specified by `order`. /// /// See also [`numpy.reshape`][numpy-reshape] and [`PyArray_Newshape`][PyArray_Newshape]. /// /// # Example /// /// ``` /// use numpy::prelude::*; /// use numpy::{npyffi::NPY_ORDER, PyArray}; /// use pyo3::Python; /// use ndarray::array; /// /// Python::attach(|py| { /// let array = /// PyArray::from_iter(py, 0..9).reshape_with_order([3, 3], NPY_ORDER::NPY_FORTRANORDER).unwrap(); /// /// assert_eq!(array.readonly().as_array(), array![[0, 3, 6], [1, 4, 7], [2, 5, 8]]); /// assert!(array.is_fortran_contiguous()); /// /// assert!(array.reshape([5]).is_err()); /// }); /// ``` /// /// [numpy-reshape]: https://numpy.org/doc/stable/reference/generated/numpy.reshape.html /// [PyArray_Newshape]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_Newshape fn reshape_with_order( &self, shape: ID, order: NPY_ORDER, ) -> PyResult>> where T: Element; /// Special case of [`reshape_with_order`][Self::reshape_with_order] which keeps the memory order the same. #[inline(always)] fn reshape(&self, shape: ID) -> PyResult>> where T: Element, { self.reshape_with_order(shape, NPY_ORDER::NPY_ANYORDER) } /// Extends or truncates the dimensions of an array. /// /// This method works only on [contiguous][PyUntypedArrayMethods::is_contiguous] arrays. /// Missing elements will be initialized as if calling [`zeros`][PyArray::zeros]. /// /// See also [`ndarray.resize`][ndarray-resize] and [`PyArray_Resize`][PyArray_Resize]. /// /// # Safety /// /// There should be no outstanding references (shared or exclusive) into the array /// as this method might re-allocate it and thereby invalidate all pointers into it. /// /// # Example /// /// ``` /// use numpy::prelude::*; /// use numpy::PyArray; /// use pyo3::Python; /// /// Python::attach(|py| { /// let pyarray = PyArray::::zeros(py, (10, 10), false); /// assert_eq!(pyarray.shape(), [10, 10]); /// /// unsafe { /// pyarray.resize((100, 100)).unwrap(); /// } /// assert_eq!(pyarray.shape(), [100, 100]); /// }); /// ``` /// /// [ndarray-resize]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.resize.html /// [PyArray_Resize]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_Resize unsafe fn resize(&self, newshape: ID) -> PyResult<()> where T: Element; /// Try to convert this array into a [`nalgebra::MatrixView`] using the given shape and strides. /// /// # Safety /// /// Calling this method invalidates all exclusive references to the internal data, e.g. `ArrayViewMut` or `MatrixSliceMut`. #[doc(alias = "nalgebra")] #[cfg(feature = "nalgebra")] unsafe fn try_as_matrix( &self, ) -> Option> where T: nalgebra::Scalar + Element, D: Dimension, R: nalgebra::Dim, C: nalgebra::Dim, RStride: nalgebra::Dim, CStride: nalgebra::Dim; /// Try to convert this array into a [`nalgebra::MatrixViewMut`] using the given shape and strides. /// /// # Safety /// /// Calling this method invalidates all other references to the internal data, e.g. `ArrayView`, `MatrixSlice`, `ArrayViewMut` or `MatrixSliceMut`. #[doc(alias = "nalgebra")] #[cfg(feature = "nalgebra")] unsafe fn try_as_matrix_mut( &self, ) -> Option> where T: nalgebra::Scalar + Element, D: Dimension, R: nalgebra::Dim, C: nalgebra::Dim, RStride: nalgebra::Dim, CStride: nalgebra::Dim; } /// Implementation of functionality for [`PyArray0`]. #[doc(alias = "PyArray", alias = "PyArray0")] pub trait PyArray0Methods<'py, T>: PyArrayMethods<'py, T, Ix0> { /// Get the single element of a zero-dimensional array. /// /// See [`inner`][crate::inner] for an example. fn item(&self) -> T where T: Element + Copy, { unsafe { *self.data() } } } #[inline(always)] fn get_raw(slf: &Bound<'_, PyArray>, index: Idx) -> Option<*mut T> where T: Element, D: Dimension, Idx: NpyIndex, { let offset = index.get_checked::(slf.shape(), slf.strides())?; Some(unsafe { slf.data().offset(offset) }) } fn as_view(slf: &Bound<'_, PyArray>, from_shape_ptr: F) -> ArrayBase where T: Element, D: Dimension, S: RawData, F: FnOnce(StrideShape, *mut T) -> ArrayBase, { fn inner( shape: &[usize], strides: &[isize], itemsize: usize, mut data_ptr: *mut u8, ) -> (StrideShape, u32, *mut u8) { let shape = D::from_dimension(&Dim(shape)).expect(DIMENSIONALITY_MISMATCH_ERR); assert!(strides.len() <= 32, "{}", MAX_DIMENSIONALITY_ERR); let mut new_strides = D::zeros(strides.len()); let mut inverted_axes = 0_u32; for i in 0..strides.len() { // FIXME(kngwyu): Replace this hacky negative strides support with // a proper constructor, when it's implemented. // See https://github.com/rust-ndarray/ndarray/issues/842 for more. if strides[i] >= 0 { new_strides[i] = strides[i] as usize / itemsize; } else { // Move the pointer to the start position. data_ptr = unsafe { data_ptr.offset(strides[i] * (shape[i] as isize - 1)) }; new_strides[i] = (-strides[i]) as usize / itemsize; inverted_axes |= 1 << i; } } (shape.strides(new_strides), inverted_axes, data_ptr) } let (shape, mut inverted_axes, data_ptr) = inner( slf.shape(), slf.strides(), mem::size_of::(), slf.data() as _, ); let mut array = from_shape_ptr(shape, data_ptr as _); while inverted_axes != 0 { let axis = inverted_axes.trailing_zeros() as usize; inverted_axes &= !(1 << axis); array.invert_axis(Axis(axis)); } array } #[cfg(feature = "nalgebra")] fn try_as_matrix_shape_strides( slf: &Bound<'_, PyArray>, ) -> Option<((R, C), (RStride, CStride))> where N: nalgebra::Scalar + Element, D: Dimension, R: nalgebra::Dim, C: nalgebra::Dim, RStride: nalgebra::Dim, CStride: nalgebra::Dim, { let ndim = slf.ndim(); let shape = slf.shape(); let strides = slf.strides(); if ndim != 1 && ndim != 2 { return None; } if strides.iter().any(|strides| *strides < 0) { return None; } let rows = shape[0]; let cols = *shape.get(1).unwrap_or(&1); if R::try_to_usize().map(|expected| rows == expected) == Some(false) { return None; } if C::try_to_usize().map(|expected| cols == expected) == Some(false) { return None; } let row_stride = strides[0] as usize / mem::size_of::(); let col_stride = strides .get(1) .map_or(rows, |stride| *stride as usize / mem::size_of::()); if RStride::try_to_usize().map(|expected| row_stride == expected) == Some(false) { return None; } if CStride::try_to_usize().map(|expected| col_stride == expected) == Some(false) { return None; } let shape = (R::from_usize(rows), C::from_usize(cols)); let strides = ( RStride::from_usize(row_stride), CStride::from_usize(col_stride), ); Some((shape, strides)) } impl<'py, T, D> PyArrayMethods<'py, T, D> for Bound<'py, PyArray> { #[inline(always)] fn as_untyped(&self) -> &Bound<'py, PyUntypedArray> { unsafe { self.cast_unchecked() } } #[inline(always)] fn data(&self) -> *mut T { unsafe { (*self.as_array_ptr()).data.cast() } } #[inline(always)] unsafe fn get(&self, index: impl NpyIndex) -> Option<&T> where T: Element, D: Dimension, { let ptr = get_raw(self, index)?; Some(&*ptr) } #[inline(always)] unsafe fn get_mut(&self, index: impl NpyIndex) -> Option<&mut T> where T: Element, D: Dimension, { let ptr = get_raw(self, index)?; Some(&mut *ptr) } fn get_owned(&self, index: Idx) -> Option where T: Element, D: Dimension, Idx: NpyIndex, { let element = unsafe { self.get(index) }; element.map(|elem| elem.clone_ref(self.py())) } fn to_dyn(&self) -> &Bound<'py, PyArray> { unsafe { self.cast_unchecked() } } fn to_vec(&self) -> Result, NotContiguousError> where T: Element, D: Dimension, { let slice = unsafe { self.as_slice() }; slice.map(|slc| T::vec_from_slice(self.py(), slc)) } fn try_readonly(&self) -> Result, BorrowError> where T: Element, D: Dimension, { PyReadonlyArray::try_new(self.clone()) } fn try_readwrite(&self) -> Result, BorrowError> where T: Element, D: Dimension, { PyReadwriteArray::try_new(self.clone()) } unsafe fn as_array(&self) -> ArrayView<'_, T, D> where T: Element, D: Dimension, { as_view(self, |shape, ptr| ArrayView::from_shape_ptr(shape, ptr)) } unsafe fn as_array_mut(&self) -> ArrayViewMut<'_, T, D> where T: Element, D: Dimension, { as_view(self, |shape, ptr| ArrayViewMut::from_shape_ptr(shape, ptr)) } fn as_raw_array(&self) -> RawArrayView where T: Element, D: Dimension, { as_view(self, |shape, ptr| unsafe { RawArrayView::from_shape_ptr(shape, ptr) }) } fn as_raw_array_mut(&self) -> RawArrayViewMut where T: Element, D: Dimension, { as_view(self, |shape, ptr| unsafe { RawArrayViewMut::from_shape_ptr(shape, ptr) }) } fn to_owned_array(&self) -> Array where T: Element, D: Dimension, { let view = unsafe { self.as_array() }; T::array_from_view(self.py(), view) } fn copy_to(&self, other: &Bound<'py, PyArray>) -> PyResult<()> where T: Element, { let self_ptr = self.as_array_ptr(); let other_ptr = other.as_array_ptr(); let result = unsafe { PY_ARRAY_API.PyArray_CopyInto(self.py(), other_ptr, self_ptr) }; if result != -1 { Ok(()) } else { Err(PyErr::fetch(self.py())) } } fn cast_array(&self, is_fortran: bool) -> PyResult>> where T: Element, { let ptr = unsafe { PY_ARRAY_API.PyArray_CastToType( self.py(), self.as_array_ptr(), U::get_dtype(self.py()).into_dtype_ptr(), if is_fortran { -1 } else { 0 }, ) }; unsafe { Bound::from_owned_ptr_or_err(self.py(), ptr).map(|ob| ob.cast_into_unchecked()) } } fn permute(&self, axes: Option) -> PyResult>> { let mut axes = axes.map(|axes| axes.into_dimension()); let mut axes = axes.as_mut().map(|axes| axes.to_npy_dims()); let axes = axes .as_mut() .map_or_else(ptr::null_mut, |axes| axes as *mut npyffi::PyArray_Dims); let py = self.py(); let ptr = unsafe { PY_ARRAY_API.PyArray_Transpose(py, self.as_array_ptr(), axes) }; unsafe { Bound::from_owned_ptr_or_err(py, ptr).map(|ob| ob.cast_into_unchecked()) } } fn reshape_with_order( &self, shape: ID, order: NPY_ORDER, ) -> PyResult>> where T: Element, { let mut shape = shape.into_dimension(); let mut shape = shape.to_npy_dims(); let py = self.py(); let ptr = unsafe { PY_ARRAY_API.PyArray_Newshape( py, self.as_array_ptr(), &mut shape as *mut npyffi::PyArray_Dims, order, ) }; unsafe { Bound::from_owned_ptr_or_err(py, ptr).map(|ob| ob.cast_into_unchecked()) } } unsafe fn resize(&self, newshape: ID) -> PyResult<()> where T: Element, { let mut newshape = newshape.into_dimension(); let mut newshape = newshape.to_npy_dims(); let py = self.py(); let res = PY_ARRAY_API.PyArray_Resize( py, self.as_array_ptr(), &mut newshape as *mut npyffi::PyArray_Dims, 1, NPY_ORDER::NPY_ANYORDER, ); if !res.is_null() { Ok(()) } else { Err(PyErr::fetch(py)) } } #[cfg(feature = "nalgebra")] unsafe fn try_as_matrix( &self, ) -> Option> where T: nalgebra::Scalar + Element, D: Dimension, R: nalgebra::Dim, C: nalgebra::Dim, RStride: nalgebra::Dim, CStride: nalgebra::Dim, { let (shape, strides) = try_as_matrix_shape_strides(self)?; let storage = nalgebra::ViewStorage::from_raw_parts(self.data(), shape, strides); Some(nalgebra::Matrix::from_data(storage)) } #[cfg(feature = "nalgebra")] unsafe fn try_as_matrix_mut( &self, ) -> Option> where T: nalgebra::Scalar + Element, D: Dimension, R: nalgebra::Dim, C: nalgebra::Dim, RStride: nalgebra::Dim, CStride: nalgebra::Dim, { let (shape, strides) = try_as_matrix_shape_strides(self)?; let storage = nalgebra::ViewStorageMut::from_raw_parts(self.data(), shape, strides); Some(nalgebra::Matrix::from_data(storage)) } } impl<'py, T> PyArray0Methods<'py, T> for Bound<'py, PyArray0> {} #[cfg(test)] mod tests { use super::*; use ndarray::array; use pyo3::{py_run, types::PyList}; #[test] fn test_dyn_to_owned_array() { Python::attach(|py| { let array = PyArray::from_vec2(py, &[vec![1, 2], vec![3, 4]]) .unwrap() .to_dyn() .to_owned_array(); assert_eq!(array, array![[1, 2], [3, 4]].into_dyn()); }); } #[test] fn test_hasobject_flag() { Python::attach(|py| { let array: Bound<'_, PyArray, _>> = PyArray1::from_slice(py, &[PyList::empty(py).into()]); py_run!(py, array, "assert array.dtype.hasobject"); }); } } numpy-0.27.1/src/array_like.rs000064400000000000000000000152631046102023000143510ustar 00000000000000use std::marker::PhantomData; use std::ops::Deref; use ndarray::{Array1, Dimension, Ix0, Ix1, Ix2, Ix3, Ix4, Ix5, Ix6, IxDyn}; use pyo3::{ intern, sync::PyOnceLock, types::{PyAnyMethods, PyDict}, Borrowed, FromPyObject, Py, PyAny, PyErr, PyResult, }; use crate::array::PyArrayMethods; use crate::{get_array_module, Element, IntoPyArray, PyArray, PyReadonlyArray}; pub trait Coerce: Sealed { const VAL: bool; } mod sealed { pub trait Sealed {} } use sealed::Sealed; /// Marker type to indicate that the element type received via [`PyArrayLike`] must match the specified type exactly. #[derive(Debug)] pub struct TypeMustMatch; impl Sealed for TypeMustMatch {} impl Coerce for TypeMustMatch { const VAL: bool = false; } /// Marker type to indicate that the element type received via [`PyArrayLike`] can be cast to the specified type by NumPy's [`asarray`](https://numpy.org/doc/stable/reference/generated/numpy.asarray.html). #[derive(Debug)] pub struct AllowTypeChange; impl Sealed for AllowTypeChange {} impl Coerce for AllowTypeChange { const VAL: bool = true; } /// Receiver for arrays or array-like types. /// /// When building API using NumPy in Python, it is common for functions to additionally accept any array-like type such as `list[float]` as arguments. /// `PyArrayLike` enables the same pattern in Rust extensions, i.e. by taking this type as the argument of a `#[pyfunction]`, /// one will always get access to a [`PyReadonlyArray`] that will either reference to the NumPy array originally passed into the function /// or a temporary one created by converting the input type into a NumPy array. /// /// Depending on whether [`TypeMustMatch`] or [`AllowTypeChange`] is used for the `C` type parameter, /// the element type must either match the specific type `T` exactly or will be cast to it by NumPy's [`asarray`](https://numpy.org/doc/stable/reference/generated/numpy.asarray.html). /// /// # Example /// /// `PyArrayLike1<'py, T, TypeMustMatch>` will enable you to receive both NumPy arrays and sequences /// /// ```rust /// # use pyo3::prelude::*; /// use pyo3::py_run; /// use numpy::{get_array_module, PyArrayLike1, TypeMustMatch}; /// /// #[pyfunction] /// fn sum_up<'py>(py: Python<'py>, array: PyArrayLike1<'py, f64, TypeMustMatch>) -> f64 { /// array.as_array().sum() /// } /// /// Python::attach(|py| { /// let np = get_array_module(py).unwrap(); /// let sum_up = wrap_pyfunction!(sum_up)(py).unwrap(); /// /// py_run!(py, np sum_up, r"assert sum_up(np.array([1., 2., 3.])) == 6."); /// py_run!(py, np sum_up, r"assert sum_up((1., 2., 3.)) == 6."); /// }); /// ``` /// /// but it will not cast the element type if that is required /// /// ```rust,should_panic /// use pyo3::prelude::*; /// use pyo3::py_run; /// use numpy::{get_array_module, PyArrayLike1, TypeMustMatch}; /// /// #[pyfunction] /// fn sum_up<'py>(py: Python<'py>, array: PyArrayLike1<'py, i32, TypeMustMatch>) -> i32 { /// array.as_array().sum() /// } /// /// Python::attach(|py| { /// let np = get_array_module(py).unwrap(); /// let sum_up = wrap_pyfunction!(sum_up)(py).unwrap(); /// /// py_run!(py, np sum_up, r"assert sum_up((1., 2., 3.)) == 6"); /// }); /// ``` /// /// whereas `PyArrayLike1<'py, T, AllowTypeChange>` will do even at the cost loosing precision /// /// ```rust /// use pyo3::prelude::*; /// use pyo3::py_run; /// use numpy::{get_array_module, AllowTypeChange, PyArrayLike1}; /// /// #[pyfunction] /// fn sum_up<'py>(py: Python<'py>, array: PyArrayLike1<'py, i32, AllowTypeChange>) -> i32 { /// array.as_array().sum() /// } /// /// Python::attach(|py| { /// let np = get_array_module(py).unwrap(); /// let sum_up = wrap_pyfunction!(sum_up)(py).unwrap(); /// /// py_run!(py, np sum_up, r"assert sum_up((1.5, 2.5)) == 3"); /// }); /// ``` #[derive(Debug)] #[repr(transparent)] pub struct PyArrayLike<'py, T, D, C = TypeMustMatch>(PyReadonlyArray<'py, T, D>, PhantomData) where T: Element, D: Dimension, C: Coerce; impl<'py, T, D, C> Deref for PyArrayLike<'py, T, D, C> where T: Element, D: Dimension, C: Coerce, { type Target = PyReadonlyArray<'py, T, D>; fn deref(&self) -> &Self::Target { &self.0 } } impl<'a, 'py, T, D, C> FromPyObject<'a, 'py> for PyArrayLike<'py, T, D, C> where T: Element + 'py, D: Dimension + 'py, C: Coerce, Vec: FromPyObject<'a, 'py>, { type Error = PyErr; fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult { if let Ok(array) = ob.cast::>() { return Ok(Self(array.readonly(), PhantomData)); } let py = ob.py(); if matches!(D::NDIM, None | Some(1)) { if let Ok(vec) = ob.extract::>() { let array = Array1::from(vec) .into_dimensionality() .expect("D being compatible to Ix1") .into_pyarray(py) .readonly(); return Ok(Self(array, PhantomData)); } } static AS_ARRAY: PyOnceLock> = PyOnceLock::new(); let as_array = AS_ARRAY .get_or_try_init(py, || { get_array_module(py)?.getattr("asarray").map(Into::into) })? .bind(py); let kwargs = if C::VAL { let kwargs = PyDict::new(py); kwargs.set_item(intern!(py, "dtype"), T::get_dtype(py))?; Some(kwargs) } else { None }; let array = as_array.call((ob,), kwargs.as_ref())?.extract()?; Ok(Self(array, PhantomData)) } } /// Receiver for zero-dimensional arrays or array-like types. pub type PyArrayLike0<'py, T, C = TypeMustMatch> = PyArrayLike<'py, T, Ix0, C>; /// Receiver for one-dimensional arrays or array-like types. pub type PyArrayLike1<'py, T, C = TypeMustMatch> = PyArrayLike<'py, T, Ix1, C>; /// Receiver for two-dimensional arrays or array-like types. pub type PyArrayLike2<'py, T, C = TypeMustMatch> = PyArrayLike<'py, T, Ix2, C>; /// Receiver for three-dimensional arrays or array-like types. pub type PyArrayLike3<'py, T, C = TypeMustMatch> = PyArrayLike<'py, T, Ix3, C>; /// Receiver for four-dimensional arrays or array-like types. pub type PyArrayLike4<'py, T, C = TypeMustMatch> = PyArrayLike<'py, T, Ix4, C>; /// Receiver for five-dimensional arrays or array-like types. pub type PyArrayLike5<'py, T, C = TypeMustMatch> = PyArrayLike<'py, T, Ix5, C>; /// Receiver for six-dimensional arrays or array-like types. pub type PyArrayLike6<'py, T, C = TypeMustMatch> = PyArrayLike<'py, T, Ix6, C>; /// Receiver for arrays or array-like types whose dimensionality is determined at runtime. pub type PyArrayLikeDyn<'py, T, C = TypeMustMatch> = PyArrayLike<'py, T, IxDyn, C>; numpy-0.27.1/src/borrow/mod.rs000064400000000000000000000631071046102023000143200ustar 00000000000000//! Types to safely create references into NumPy arrays //! //! It is assumed that unchecked code - which includes unsafe Rust and Python - is validated by its author //! which together with the dynamic borrow checking performed by this crate ensures that //! safe Rust code cannot cause undefined behaviour by creating references into NumPy arrays. //! //! With these borrows established, [references to individual elements][PyReadonlyArray::get] or [reference-based views of whole array][PyReadonlyArray::as_array] //! can be created safely. These are then the starting point for algorithms iteraing over and operating on the elements of the array. //! //! # Examples //! //! The first example shows that dynamic borrow checking works to constrain //! both what safe Rust code can invoke and how it is invoked. //! //! ```rust //! # use std::panic::{catch_unwind, AssertUnwindSafe}; //! # //! use numpy::{PyArray1, PyArrayMethods, npyffi::flags}; //! use ndarray::Zip; //! use pyo3::{Python, Bound}; //! //! fn add(x: &Bound<'_, PyArray1>, y: &Bound<'_, PyArray1>, z: &Bound<'_, PyArray1>) { //! let x1 = x.readonly(); //! let y1 = y.readonly(); //! let mut z1 = z.readwrite(); //! //! let x2 = x1.as_array(); //! let y2 = y1.as_array(); //! let z2 = z1.as_array_mut(); //! //! Zip::from(x2) //! .and(y2) //! .and(z2) //! .for_each(|x3, y3, z3| *z3 = x3 + y3); //! //! // Will fail at runtime due to conflict with `x1`. //! let res = catch_unwind(AssertUnwindSafe(|| { //! let _x4 = x.readwrite(); //! })); //! assert!(res.is_err()); //! } //! //! Python::attach(|py| { //! let x = PyArray1::::zeros(py, 42, false); //! let y = PyArray1::::zeros(py, 42, false); //! let z = PyArray1::::zeros(py, 42, false); //! //! // Will work as the three arrays are distinct. //! add(&x, &y, &z); //! //! // Will work as `x1` and `y1` are compatible borrows. //! add(&x, &x, &z); //! //! // Will fail at runtime due to conflict between `y1` and `z1`. //! let res = catch_unwind(AssertUnwindSafe(|| { //! add(&x, &y, &y); //! })); //! assert!(res.is_err()); //! }); //! ``` //! //! The second example shows that non-overlapping and interleaved views are also supported. //! //! ```rust //! use numpy::{PyArray1, PyArrayMethods}; //! use pyo3::{types::{IntoPyDict, PyAnyMethods}, Python, ffi::c_str}; //! //! # fn main() -> pyo3::PyResult<()> { //! Python::attach(|py| { //! let array = PyArray1::arange(py, 0.0, 10.0, 1.0); //! let locals = [("array", array)].into_py_dict(py)?; //! //! let view1 = py.eval(c_str!("array[:5]"), None, Some(&locals))?.cast_into::>()?; //! let view2 = py.eval(c_str!("array[5:]"), None, Some(&locals))?.cast_into::>()?; //! let view3 = py.eval(c_str!("array[::2]"), None, Some(&locals))?.cast_into::>()?; //! let view4 = py.eval(c_str!("array[1::2]"), None, Some(&locals))?.cast_into::>()?; //! //! { //! let _view1 = view1.readwrite(); //! let _view2 = view2.readwrite(); //! } //! //! { //! let _view3 = view3.readwrite(); //! let _view4 = view4.readwrite(); //! } //! # Ok(()) //! }) //! # } //! ``` //! //! The third example shows that some views are incorrectly rejected since the borrows are over-approximated. //! //! ```rust //! # use std::panic::{catch_unwind, AssertUnwindSafe}; //! # //! use numpy::{PyArray2, PyArrayMethods}; //! use pyo3::{types::{IntoPyDict, PyAnyMethods}, Python, ffi::c_str}; //! //! # fn main() -> pyo3::PyResult<()> { //! Python::attach(|py| { //! let array = PyArray2::::zeros(py, (10, 10), false); //! let locals = [("array", array)].into_py_dict(py)?; //! //! let view1 = py.eval(c_str!("array[:, ::3]"), None, Some(&locals))?.cast_into::>()?; //! let view2 = py.eval(c_str!("array[:, 1::3]"), None, Some(&locals))?.cast_into::>()?; //! //! // A false conflict as the views do not actually share any elements. //! let res = catch_unwind(AssertUnwindSafe(|| { //! let _view1 = view1.readwrite(); //! let _view2 = view2.readwrite(); //! })); //! assert!(res.is_err()); //! # Ok(()) //! }) //! # } //! ``` //! //! # Rationale //! //! Rust references require aliasing discipline to be maintained, i.e. there must always //! exist only a single mutable (aka exclusive) reference or multiple immutable (aka shared) references //! for each object, otherwise the program contains undefined behaviour. //! //! The aim of this module is to ensure that safe Rust code is unable to violate these requirements on its own. //! We cannot prevent unchecked code - this includes unsafe Rust, Python or other native code like C or Fortran - //! from violating them. Therefore the responsibility to avoid this lies with the author of that code instead of the compiler. //! However, assuming that the unchecked code is correct, we can ensure that safe Rust is unable to introduce mistakes //! into an otherwise correct program by dynamically checking which arrays are currently borrowed and in what manner. //! //! This means that we follow the [base object chain][base] of each array to the original allocation backing it and //! track which parts of that allocation are covered by the array and thereby ensure that only a single read-write array //! or multiple read-only arrays overlapping with that region are borrowed at any time. //! //! In contrast to Rust references, the mere existence of Python references or raw pointers is not an issue //! because these values are not assumed to follow aliasing discipline by the Rust compiler. //! //! This cannot prevent unchecked code from concurrently modifying an array via callbacks or using multiple threads, //! but that would lead to incorrect results even if the code that is interfered with is implemented in another language //! which does not require aliasing discipline. //! //! Concerning multi-threading in particular: While the GIL needs to be acquired to create borrows, they are not bound to the GIL //! and will stay active after the GIL is released, for example by calling [`detach`][pyo3::Python::detach]. //! Borrows also do not provide synchronization, i.e. multiple threads borrowing the same array will lead to runtime panics, //! it will not block those threads until already active borrows are released. //! //! In summary, this crate takes the position that all unchecked code - unsafe Rust, Python, C, Fortran, etc. - must be checked for correctness by its author. //! Safe Rust code can then rely on this correctness, but should not be able to introduce memory safety issues on its own. Additionally, dynamic borrow checking //! can catch _some_ mistakes introduced by unchecked code, e.g. Python calling a function with the same array as an input and as an output argument. //! //! # Limitations //! //! Note that the current implementation of this is an over-approximation: It will consider borrows //! potentially conflicting if the initial arrays have the same object at the end of their [base object chain][base]. //! Then, multiple conditions which are sufficient but not necessary to show the absence of conflicts are checked. //! //! While this is sufficient to handle common situations like slicing an array with a non-unit step size which divides //! the dimension along that axis, there are also cases which it does not handle. For example, if the step size does //! not divide the dimension along the sliced axis. Under such conditions, borrows are rejected even though the arrays //! do not actually share any elements. //! //! This does limit the set of programs that can be written using safe Rust in way similar to rustc itself //! which ensures that all accepted programs are memory safe but does not necessarily accept all memory safe programs. //! However, the unsafe method [`PyArray::as_array_mut`] can be used as an escape hatch. //! More involved cases like the example from above may be supported in the future. //! //! [base]: https://numpy.org/doc/stable/reference/c-api/types-and-structures.html#c.NPY_AO.base mod shared; use std::any::type_name; use std::fmt; use std::ops::Deref; use ndarray::{ ArrayView, ArrayViewMut, Dimension, IntoDimension, Ix0, Ix1, Ix2, Ix3, Ix4, Ix5, Ix6, IxDyn, }; use pyo3::{Borrowed, Bound, CastError, FromPyObject, PyAny, PyResult}; use crate::array::{PyArray, PyArrayMethods}; use crate::convert::NpyIndex; use crate::dtype::Element; use crate::error::{BorrowError, NotContiguousError}; use crate::npyffi::flags; use crate::untyped_array::PyUntypedArrayMethods; use shared::{acquire, acquire_mut, release, release_mut}; /// Read-only borrow of an array. /// /// An instance of this type ensures that there are no instances of [`PyReadwriteArray`], /// i.e. that only shared references into the interior of the array can be created safely. /// /// See the [module-level documentation](self) for more. #[repr(transparent)] pub struct PyReadonlyArray<'py, T, D> where T: Element, D: Dimension, { array: Bound<'py, PyArray>, } /// Read-only borrow of a zero-dimensional array. pub type PyReadonlyArray0<'py, T> = PyReadonlyArray<'py, T, Ix0>; /// Read-only borrow of a one-dimensional array. pub type PyReadonlyArray1<'py, T> = PyReadonlyArray<'py, T, Ix1>; /// Read-only borrow of a two-dimensional array. pub type PyReadonlyArray2<'py, T> = PyReadonlyArray<'py, T, Ix2>; /// Read-only borrow of a three-dimensional array. pub type PyReadonlyArray3<'py, T> = PyReadonlyArray<'py, T, Ix3>; /// Read-only borrow of a four-dimensional array. pub type PyReadonlyArray4<'py, T> = PyReadonlyArray<'py, T, Ix4>; /// Read-only borrow of a five-dimensional array. pub type PyReadonlyArray5<'py, T> = PyReadonlyArray<'py, T, Ix5>; /// Read-only borrow of a six-dimensional array. pub type PyReadonlyArray6<'py, T> = PyReadonlyArray<'py, T, Ix6>; /// Read-only borrow of an array whose dimensionality is determined at runtime. pub type PyReadonlyArrayDyn<'py, T> = PyReadonlyArray<'py, T, IxDyn>; impl<'py, T, D> Deref for PyReadonlyArray<'py, T, D> where T: Element, D: Dimension, { type Target = Bound<'py, PyArray>; fn deref(&self) -> &Self::Target { &self.array } } impl<'a, 'py, T: Element + 'a, D: Dimension + 'a> FromPyObject<'a, 'py> for PyReadonlyArray<'py, T, D> { type Error = CastError<'a, 'py>; fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { let array = obj.cast::>()?; Ok(array.readonly()) } } impl<'py, T, D> PyReadonlyArray<'py, T, D> where T: Element, D: Dimension, { pub(crate) fn try_new(array: Bound<'py, PyArray>) -> Result { acquire(array.py(), array.as_array_ptr())?; Ok(Self { array }) } /// Provides an immutable array view of the interior of the NumPy array. #[inline(always)] pub fn as_array(&self) -> ArrayView<'_, T, D> { // SAFETY: Global borrow flags ensure aliasing discipline. unsafe { self.array.as_array() } } /// Provide an immutable slice view of the interior of the NumPy array if it is contiguous. #[inline(always)] pub fn as_slice(&self) -> Result<&[T], NotContiguousError> { // SAFETY: Global borrow flags ensure aliasing discipline. unsafe { self.array.as_slice() } } /// Provide an immutable reference to an element of the NumPy array if the index is within bounds. #[inline(always)] pub fn get(&self, index: I) -> Option<&T> where I: NpyIndex, { unsafe { self.array.get(index) } } } #[cfg(feature = "nalgebra")] impl<'py, N, D> PyReadonlyArray<'py, N, D> where N: nalgebra::Scalar + Element, D: Dimension, { /// Try to convert this array into a [`nalgebra::MatrixView`] using the given shape and strides. /// /// Note that nalgebra's types default to Fortan/column-major standard strides whereas NumPy creates C/row-major strides by default. /// Furthermore, array views created by slicing into existing arrays will often have non-standard strides. /// /// If you do not fully control the memory layout of a given array, e.g. at your API entry points, /// it can be useful to opt into nalgebra's support for [dynamic strides][nalgebra::Dyn], for example /// /// ```rust /// # use pyo3::prelude::*; /// use pyo3::{py_run, ffi::c_str}; /// use numpy::{get_array_module, PyReadonlyArray2}; /// use nalgebra::{MatrixView, Const, Dyn}; /// /// #[pyfunction] /// fn sum_standard_layout<'py>(py: Python<'py>, array: PyReadonlyArray2<'py, f64>) -> Option { /// let matrix: Option, Const<2>>> = array.try_as_matrix(); /// matrix.map(|matrix| matrix.sum()) /// } /// /// #[pyfunction] /// fn sum_dynamic_strides<'py>(py: Python<'py>, array: PyReadonlyArray2<'py, f64>) -> Option { /// let matrix: Option, Const<2>, Dyn, Dyn>> = array.try_as_matrix(); /// matrix.map(|matrix| matrix.sum()) /// } /// /// # fn main() -> pyo3::PyResult<()> { /// Python::attach(|py| { /// let np = py.eval(c_str!("__import__('numpy')"), None, None)?; /// let sum_standard_layout = wrap_pyfunction!(sum_standard_layout)(py)?; /// let sum_dynamic_strides = wrap_pyfunction!(sum_dynamic_strides)(py)?; /// /// py_run!(py, np sum_standard_layout, r"assert sum_standard_layout(np.ones((2, 2), order='F')) == 4."); /// py_run!(py, np sum_standard_layout, r"assert sum_standard_layout(np.ones((2, 2, 2))[:,:,0]) is None"); /// /// py_run!(py, np sum_dynamic_strides, r"assert sum_dynamic_strides(np.ones((2, 2), order='F')) == 4."); /// py_run!(py, np sum_dynamic_strides, r"assert sum_dynamic_strides(np.ones((2, 2, 2))[:,:,0]) == 4."); /// # Ok(()) /// }) /// # } /// ``` #[doc(alias = "nalgebra")] pub fn try_as_matrix( &self, ) -> Option> where R: nalgebra::Dim, C: nalgebra::Dim, RStride: nalgebra::Dim, CStride: nalgebra::Dim, { unsafe { self.array.try_as_matrix() } } } #[cfg(feature = "nalgebra")] impl<'py, N> PyReadonlyArray<'py, N, Ix1> where N: nalgebra::Scalar + Element, { /// Convert this one-dimensional array into a [`nalgebra::DMatrixView`] using dynamic strides. /// /// # Panics /// /// Panics if the array has negative strides. #[doc(alias = "nalgebra")] pub fn as_matrix(&self) -> nalgebra::DMatrixView<'_, N, nalgebra::Dyn, nalgebra::Dyn> { self.try_as_matrix().unwrap() } } #[cfg(feature = "nalgebra")] impl<'py, N> PyReadonlyArray<'py, N, Ix2> where N: nalgebra::Scalar + Element, { /// Convert this two-dimensional array into a [`nalgebra::DMatrixView`] using dynamic strides. /// /// # Panics /// /// Panics if the array has negative strides. #[doc(alias = "nalgebra")] pub fn as_matrix(&self) -> nalgebra::DMatrixView<'_, N, nalgebra::Dyn, nalgebra::Dyn> { self.try_as_matrix().unwrap() } } impl<'py, T, D> Clone for PyReadonlyArray<'py, T, D> where T: Element, D: Dimension, { fn clone(&self) -> Self { acquire(self.array.py(), self.array.as_array_ptr()).unwrap(); Self { array: self.array.clone(), } } } impl<'py, T, D> Drop for PyReadonlyArray<'py, T, D> where T: Element, D: Dimension, { fn drop(&mut self) { release(self.array.py(), self.array.as_array_ptr()); } } impl<'py, T, D> fmt::Debug for PyReadonlyArray<'py, T, D> where T: Element, D: Dimension, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let name = format!( "PyReadonlyArray<{}, {}>", type_name::(), type_name::() ); f.debug_struct(&name).finish() } } /// Read-write borrow of an array. /// /// An instance of this type ensures that there are no instances of [`PyReadonlyArray`] and no other instances of [`PyReadwriteArray`], /// i.e. that only a single exclusive reference into the interior of the array can be created safely. /// /// See the [module-level documentation](self) for more. #[repr(transparent)] pub struct PyReadwriteArray<'py, T, D> where T: Element, D: Dimension, { array: Bound<'py, PyArray>, } /// Read-write borrow of a zero-dimensional array. pub type PyReadwriteArray0<'py, T> = PyReadwriteArray<'py, T, Ix0>; /// Read-write borrow of a one-dimensional array. pub type PyReadwriteArray1<'py, T> = PyReadwriteArray<'py, T, Ix1>; /// Read-write borrow of a two-dimensional array. pub type PyReadwriteArray2<'py, T> = PyReadwriteArray<'py, T, Ix2>; /// Read-write borrow of a three-dimensional array. pub type PyReadwriteArray3<'py, T> = PyReadwriteArray<'py, T, Ix3>; /// Read-write borrow of a four-dimensional array. pub type PyReadwriteArray4<'py, T> = PyReadwriteArray<'py, T, Ix4>; /// Read-write borrow of a five-dimensional array. pub type PyReadwriteArray5<'py, T> = PyReadwriteArray<'py, T, Ix5>; /// Read-write borrow of a six-dimensional array. pub type PyReadwriteArray6<'py, T> = PyReadwriteArray<'py, T, Ix6>; /// Read-write borrow of an array whose dimensionality is determined at runtime. pub type PyReadwriteArrayDyn<'py, T> = PyReadwriteArray<'py, T, IxDyn>; impl<'py, T, D> Deref for PyReadwriteArray<'py, T, D> where T: Element, D: Dimension, { type Target = PyReadonlyArray<'py, T, D>; fn deref(&self) -> &Self::Target { // SAFETY: Exclusive references decay implictly into shared references. unsafe { &*(self as *const Self as *const Self::Target) } } } impl<'py, T, D> From> for PyReadonlyArray<'py, T, D> where T: Element, D: Dimension, { fn from(value: PyReadwriteArray<'py, T, D>) -> Self { let array = value.array.clone(); ::std::mem::drop(value); Self::try_new(array) .expect("releasing an exclusive reference should immediately permit a shared reference") } } impl<'a, 'py, T: Element + 'a, D: Dimension + 'a> FromPyObject<'a, 'py> for PyReadwriteArray<'py, T, D> { type Error = CastError<'a, 'py>; fn extract(obj: Borrowed<'a, 'py, PyAny>) -> Result { let array = obj.cast::>()?; Ok(array.readwrite()) } } impl<'py, T, D> PyReadwriteArray<'py, T, D> where T: Element, D: Dimension, { pub(crate) fn try_new(array: Bound<'py, PyArray>) -> Result { acquire_mut(array.py(), array.as_array_ptr())?; Ok(Self { array }) } /// Provides a mutable array view of the interior of the NumPy array. #[inline(always)] pub fn as_array_mut(&mut self) -> ArrayViewMut<'_, T, D> { // SAFETY: Global borrow flags ensure aliasing discipline. unsafe { self.array.as_array_mut() } } /// Provide a mutable slice view of the interior of the NumPy array if it is contiguous. #[inline(always)] pub fn as_slice_mut(&mut self) -> Result<&mut [T], NotContiguousError> { // SAFETY: Global borrow flags ensure aliasing discipline. unsafe { self.array.as_slice_mut() } } /// Provide a mutable reference to an element of the NumPy array if the index is within bounds. #[inline(always)] pub fn get_mut(&mut self, index: I) -> Option<&mut T> where I: NpyIndex, { unsafe { self.array.get_mut(index) } } /// Clear the [`WRITEABLE` flag][writeable] from the underlying NumPy array. /// /// Calling this will prevent any further [PyReadwriteArray]s from being taken out. Python /// space can reset this flag, unless the additional flag [`OWNDATA`][owndata] is unset. Such /// an array can be created from Rust space by using [PyArray::borrow_from_array_bound]. /// /// [writeable]: https://numpy.org/doc/stable/reference/c-api/array.html#c.NPY_ARRAY_WRITEABLE /// [owndata]: https://numpy.org/doc/stable/reference/c-api/array.html#c.NPY_ARRAY_OWNDATA pub fn make_nonwriteable(self) -> PyReadonlyArray<'py, T, D> { // SAFETY: consuming the only extant mutable reference guarantees we cannot invalidate an // existing reference, nor allow the caller to keep hold of one. unsafe { (*self.as_array_ptr()).flags &= !flags::NPY_ARRAY_WRITEABLE; } self.into() } } #[cfg(feature = "nalgebra")] impl<'py, N, D> PyReadwriteArray<'py, N, D> where N: nalgebra::Scalar + Element, D: Dimension, { /// Try to convert this array into a [`nalgebra::MatrixViewMut`] using the given shape and strides. /// /// See [`PyReadonlyArray::try_as_matrix`] for a discussion of the memory layout requirements. #[doc(alias = "nalgebra")] pub fn try_as_matrix_mut( &self, ) -> Option> where R: nalgebra::Dim, C: nalgebra::Dim, RStride: nalgebra::Dim, CStride: nalgebra::Dim, { unsafe { self.array.try_as_matrix_mut() } } } #[cfg(feature = "nalgebra")] impl<'py, N> PyReadwriteArray<'py, N, Ix1> where N: nalgebra::Scalar + Element, { /// Convert this one-dimensional array into a [`nalgebra::DMatrixViewMut`] using dynamic strides. /// /// # Panics /// /// Panics if the array has negative strides. #[doc(alias = "nalgebra")] pub fn as_matrix_mut(&self) -> nalgebra::DMatrixViewMut<'_, N, nalgebra::Dyn, nalgebra::Dyn> { self.try_as_matrix_mut().unwrap() } } #[cfg(feature = "nalgebra")] impl<'py, N> PyReadwriteArray<'py, N, Ix2> where N: nalgebra::Scalar + Element, { /// Convert this two-dimensional array into a [`nalgebra::DMatrixViewMut`] using dynamic strides. /// /// # Panics /// /// Panics if the array has negative strides. #[doc(alias = "nalgebra")] pub fn as_matrix_mut(&self) -> nalgebra::DMatrixViewMut<'_, N, nalgebra::Dyn, nalgebra::Dyn> { self.try_as_matrix_mut().unwrap() } } impl<'py, T> PyReadwriteArray<'py, T, Ix1> where T: Element, { /// Extends or truncates the dimensions of an array. /// /// Safe wrapper for [`PyArray::resize`]. /// /// # Example /// /// ``` /// use numpy::{PyArray, PyArrayMethods, PyUntypedArrayMethods}; /// use pyo3::Python; /// /// Python::attach(|py| { /// let pyarray = PyArray::arange(py, 0, 10, 1); /// assert_eq!(pyarray.len(), 10); /// /// let pyarray = pyarray.readwrite(); /// let pyarray = pyarray.resize(100).unwrap(); /// assert_eq!(pyarray.len(), 100); /// }); /// ``` pub fn resize(self, dims: ID) -> PyResult { // SAFETY: Ownership of `self` proves exclusive access to the interior of the array. unsafe { self.array.resize(dims)?; } let py = self.array.py(); let ptr = self.array.as_array_ptr(); // Update the borrow metadata to match the shape change. release_mut(py, ptr); acquire_mut(py, ptr).unwrap(); Ok(self) } } impl<'py, T, D> Drop for PyReadwriteArray<'py, T, D> where T: Element, D: Dimension, { fn drop(&mut self) { release_mut(self.array.py(), self.array.as_array_ptr()); } } impl<'py, T, D> fmt::Debug for PyReadwriteArray<'py, T, D> where T: Element, D: Dimension, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let name = format!( "PyReadwriteArray<{}, {}>", type_name::(), type_name::() ); f.debug_struct(&name).finish() } } #[cfg(test)] mod tests { use super::*; use pyo3::{types::IntoPyDict, Python}; use crate::array::PyArray1; use pyo3::ffi::c_str; #[test] fn test_debug_formatting() { Python::attach(|py| { let array = PyArray::::zeros(py, (1, 2, 3), false); { let shared = array.readonly(); assert_eq!( format!("{shared:?}"), "PyReadonlyArray>" ); } { let exclusive = array.readwrite(); assert_eq!( format!("{exclusive:?}"), "PyReadwriteArray>" ); } }); } #[test] #[should_panic(expected = "AlreadyBorrowed")] fn cannot_clone_exclusive_borrow_via_deref() { Python::attach(|py| { let array = PyArray::::zeros(py, (3, 2, 1), false); let exclusive = array.readwrite(); let _shared = exclusive.clone(); }); } #[test] fn failed_resize_does_not_double_release() { Python::attach(|py| { let array = PyArray::::zeros(py, 10, false); // The view will make the internal reference check of `PyArray_Resize` fail. let locals = [("array", &array)].into_py_dict(py).unwrap(); let _view = py .eval(c_str!("array[:]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); let exclusive = array.readwrite(); assert!(exclusive.resize(100).is_err()); }); } #[test] fn ineffective_resize_does_not_conflict() { Python::attach(|py| { let array = PyArray::::zeros(py, 10, false); let exclusive = array.readwrite(); assert!(exclusive.resize(10).is_ok()); }); } } numpy-0.27.1/src/borrow/shared.rs000064400000000000000000001051771046102023000150130ustar 00000000000000use std::collections::hash_map::Entry; use std::ffi::{c_void, CString}; use std::mem::forget; use std::os::raw::{c_char, c_int}; use std::ptr::NonNull; use std::slice::from_raw_parts; use std::sync::Mutex; use num_integer::gcd; use pyo3::ffi::c_str; use pyo3::sync::PyOnceLock; use pyo3::types::{PyAnyMethods, PyCapsuleMethods}; use pyo3::{exceptions::PyTypeError, types::PyCapsule, PyResult, Python}; use rustc_hash::FxHashMap; use crate::array::get_array_module; use crate::cold; use crate::error::BorrowError; use crate::npyffi::{PyArrayObject, PyArray_Check, PyDataType_ELSIZE, NPY_ARRAY_WRITEABLE}; /// Defines the shared C API used for borrow checking /// /// This structure will be placed into a capsule at /// `numpy.core.multiarray._RUST_NUMPY_BORROW_CHECKING_API`. /// /// Versions are assumed to be backwards-compatible, i.e. /// an extension which knows version N will work using /// any API version M as long as M >= N holds. /// /// Put differently, the only valid changes are adding /// fields (data or functions) at the end of the structure. #[repr(C)] struct Shared { version: u64, flags: *mut c_void, acquire: unsafe extern "C" fn(flags: *mut c_void, array: *mut PyArrayObject) -> c_int, acquire_mut: unsafe extern "C" fn(flags: *mut c_void, array: *mut PyArrayObject) -> c_int, release: unsafe extern "C" fn(flags: *mut c_void, array: *mut PyArrayObject), release_mut: unsafe extern "C" fn(flags: *mut c_void, array: *mut PyArrayObject), } unsafe impl Send for Shared {} // These are the entry points which implement the shared borrow checking API: unsafe extern "C" fn acquire_shared(flags: *mut c_void, array: *mut PyArrayObject) -> c_int { // SAFETY: must be attached when calling `acquire_shared`. let py = Python::assume_attached(); let flags = &*(flags as *mut BorrowFlags); let address = base_address(py, array); let key = borrow_key(py, array); match flags.acquire(address, key) { Ok(()) => 0, Err(()) => -1, } } unsafe extern "C" fn acquire_mut_shared(flags: *mut c_void, array: *mut PyArrayObject) -> c_int { if (*array).flags & NPY_ARRAY_WRITEABLE == 0 { return -2; } // SAFETY: must be attached when calling `acquire_shared`. let py = Python::assume_attached(); let flags = &*(flags as *mut BorrowFlags); let address = base_address(py, array); let key = borrow_key(py, array); match flags.acquire_mut(address, key) { Ok(()) => 0, Err(()) => -1, } } unsafe extern "C" fn release_shared(flags: *mut c_void, array: *mut PyArrayObject) { // SAFETY: must be attached when calling `acquire_shared`. let py = Python::assume_attached(); let flags = &*(flags as *mut BorrowFlags); let address = base_address(py, array); let key = borrow_key(py, array); flags.release(address, key); } unsafe extern "C" fn release_mut_shared(flags: *mut c_void, array: *mut PyArrayObject) { // SAFETY: must be attached when calling `acquire_shared`. let py = Python::assume_attached(); let flags = &*(flags as *mut BorrowFlags); let address = base_address(py, array); let key = borrow_key(py, array); flags.release_mut(address, key); } // This global state is a cache used to access the shared borrow checking API from this extension: struct SharedPtr(PyOnceLock>); unsafe impl Send for SharedPtr {} unsafe impl Sync for SharedPtr {} static SHARED: SharedPtr = SharedPtr(PyOnceLock::new()); fn get_or_insert_shared<'py>(py: Python<'py>) -> PyResult<&'py Shared> { let shared = SHARED.0.get_or_try_init(py, || insert_shared(py))?; // SAFETY: We inserted the capsule if it was missing // and verified that it contains a compatible version. Ok(unsafe { shared.as_ref() }) } // This function will publish this extension's version of the shared borrow checking API // as a capsule placed at `numpy.core.multiarray._RUST_NUMPY_BORROW_CHECKING_API` and // immediately initialize the cache used access it from this extension. #[cold] fn insert_shared<'py>(py: Python<'py>) -> PyResult> { let module = get_array_module(py)?; let capsule = match module.getattr("_RUST_NUMPY_BORROW_CHECKING_API") { Ok(capsule) => capsule.cast_into::()?, Err(_err) => { let flags: *mut BorrowFlags = Box::into_raw(Box::default()); let shared = Shared { version: 1, flags: flags as *mut c_void, acquire: acquire_shared, acquire_mut: acquire_mut_shared, release: release_shared, release_mut: release_mut_shared, }; let capsule = PyCapsule::new_with_destructor( py, shared, Some(CString::new("_RUST_NUMPY_BORROW_CHECKING_API").unwrap()), |shared, _ctx| { // SAFETY: `shared.flags` was initialized using `Box::into_raw`. let _ = unsafe { Box::from_raw(shared.flags as *mut BorrowFlags) }; }, )?; module.setattr("_RUST_NUMPY_BORROW_CHECKING_API", &capsule)?; capsule } }; // SAFETY: All versions of the shared borrow checking API start with a version field. let version = unsafe { *capsule .pointer_checked(Some(c_str!("_RUST_NUMPY_BORROW_CHECKING_API")))? .cast::() .as_ptr() // FIXME(icxolu): use read on MSRV 1.80 }; if version < 1 { return Err(PyTypeError::new_err(format!( "Version {version} of borrow checking API is not supported by this version of rust-numpy" ))); } let ptr = capsule.pointer_checked(Some(c_str!("_RUST_NUMPY_BORROW_CHECKING_API")))?; // Intentionally leak a reference to the capsule // so we can safely cache a pointer into its interior. forget(capsule); Ok(ptr.cast()) } // These entry points will be used to access the shared borrow checking API from this extension: pub fn acquire<'py>(py: Python<'py>, array: *mut PyArrayObject) -> Result<(), BorrowError> { let shared = get_or_insert_shared(py).expect("Interal borrow checking API error"); let rc = unsafe { (shared.acquire)(shared.flags, array) }; match rc { 0 => Ok(()), -1 => Err(BorrowError::AlreadyBorrowed), rc => panic!("Unexpected return code {rc} from borrow checking API"), } } pub fn acquire_mut<'py>(py: Python<'py>, array: *mut PyArrayObject) -> Result<(), BorrowError> { let shared = get_or_insert_shared(py).expect("Interal borrow checking API error"); let rc = unsafe { (shared.acquire_mut)(shared.flags, array) }; match rc { 0 => Ok(()), -1 => Err(BorrowError::AlreadyBorrowed), -2 => Err(BorrowError::NotWriteable), rc => panic!("Unexpected return code {rc} from borrow checking API"), } } pub fn release<'py>(py: Python<'py>, array: *mut PyArrayObject) { let shared = get_or_insert_shared(py).expect("Interal borrow checking API error"); unsafe { (shared.release)(shared.flags, array); } } pub fn release_mut<'py>(py: Python<'py>, array: *mut PyArrayObject) { let shared = get_or_insert_shared(py).expect("Interal borrow checking API error"); unsafe { (shared.release_mut)(shared.flags, array); } } #[derive(Clone, Copy, PartialEq, Eq, Hash)] struct BorrowKey { /// exclusive range of lowest and highest address covered by array pub range: (*mut c_char, *mut c_char), /// the data address on which address computations are based pub data_ptr: *mut c_char, /// the greatest common divisor of the strides of the array pub gcd_strides: isize, } impl BorrowKey { fn conflicts(&self, other: &Self) -> bool { debug_assert!(self.range.0 <= self.range.1); debug_assert!(other.range.0 <= other.range.1); if other.range.0 >= self.range.1 || self.range.0 >= other.range.1 { return false; } // The Diophantine equation which describes whether any integers can combine the data pointers and strides of the two arrays s.t. // they yield the same element has a solution if and only if the GCD of all strides divides the difference of the data pointers. // // That solution could be out of bounds which mean that this is still an over-approximation. // It appears sufficient to handle typical cases like the color channels of an image, // but fails when slicing an array with a step size that does not divide the dimension along that axis. // // https://users.rust-lang.org/t/math-for-borrow-checking-numpy-arrays/73303 let ptr_diff = unsafe { self.data_ptr.offset_from(other.data_ptr).abs() }; let gcd_strides = gcd(self.gcd_strides, other.gcd_strides); if ptr_diff % gcd_strides != 0 { return false; } // By default, a conflict is assumed as it is the safe choice without actually solving the aliasing equation. true } } type BorrowFlagsInner = FxHashMap<*mut c_void, FxHashMap>; #[derive(Default)] struct BorrowFlags(Mutex); impl BorrowFlags { fn acquire(&self, address: *mut c_void, key: BorrowKey) -> Result<(), ()> { let mut borrow_flags = self.0.lock().unwrap(); match borrow_flags.entry(address) { Entry::Occupied(entry) => { let same_base_arrays = entry.into_mut(); if let Some(readers) = same_base_arrays.get_mut(&key) { // Zero flags are removed during release. assert_ne!(*readers, 0); let new_readers = readers.wrapping_add(1); if new_readers <= 0 { cold(); return Err(()); } *readers = new_readers; } else { if same_base_arrays .iter() .any(|(other, readers)| key.conflicts(other) && *readers < 0) { cold(); return Err(()); } same_base_arrays.insert(key, 1); } } Entry::Vacant(entry) => { let mut same_base_arrays = FxHashMap::with_capacity_and_hasher(1, Default::default()); same_base_arrays.insert(key, 1); entry.insert(same_base_arrays); } } Ok(()) } fn release(&self, address: *mut c_void, key: BorrowKey) { let mut borrow_flags = self.0.lock().unwrap(); let same_base_arrays = borrow_flags.get_mut(&address).unwrap(); let readers = same_base_arrays.get_mut(&key).unwrap(); *readers -= 1; if *readers == 0 { if same_base_arrays.len() > 1 { same_base_arrays.remove(&key).unwrap(); } else { borrow_flags.remove(&address).unwrap(); } } } fn acquire_mut(&self, address: *mut c_void, key: BorrowKey) -> Result<(), ()> { let mut borrow_flags = self.0.lock().unwrap(); match borrow_flags.entry(address) { Entry::Occupied(entry) => { let same_base_arrays = entry.into_mut(); if let Some(writers) = same_base_arrays.get_mut(&key) { // Zero flags are removed during release. assert_ne!(*writers, 0); cold(); return Err(()); } else { if same_base_arrays .iter() .any(|(other, writers)| key.conflicts(other) && *writers != 0) { cold(); return Err(()); } same_base_arrays.insert(key, -1); } } Entry::Vacant(entry) => { let mut same_base_arrays = FxHashMap::with_capacity_and_hasher(1, Default::default()); same_base_arrays.insert(key, -1); entry.insert(same_base_arrays); } } Ok(()) } fn release_mut(&self, address: *mut c_void, key: BorrowKey) { let mut borrow_flags = self.0.lock().unwrap(); let same_base_arrays = borrow_flags.get_mut(&address).unwrap(); if same_base_arrays.len() > 1 { same_base_arrays.remove(&key).unwrap(); } else { borrow_flags.remove(&address); } } } fn base_address<'py>(py: Python<'py>, mut array: *mut PyArrayObject) -> *mut c_void { loop { let base = unsafe { (*array).base }; if base.is_null() { return array as *mut c_void; } else if unsafe { PyArray_Check(py, base) } != 0 { array = base as *mut PyArrayObject; } else { return base as *mut c_void; } } } fn borrow_key<'py>(py: Python<'py>, array: *mut PyArrayObject) -> BorrowKey { let range = data_range(py, array); let data_ptr = unsafe { (*array).data }; let gcd_strides = gcd_strides(array); BorrowKey { range, data_ptr, gcd_strides, } } fn data_range<'py>(py: Python<'py>, array: *mut PyArrayObject) -> (*mut c_char, *mut c_char) { let nd = unsafe { (*array).nd } as usize; let data = unsafe { (*array).data }; if nd == 0 { return (data, data); } let shape = unsafe { from_raw_parts((*array).dimensions as *mut usize, nd) }; let strides = unsafe { from_raw_parts((*array).strides, nd) }; let itemsize = unsafe { PyDataType_ELSIZE(py, (*array).descr) } as isize; let mut start = 0; let mut end = 0; if shape.iter().all(|dim| *dim != 0) { for (&dim, &stride) in shape.iter().zip(strides) { let offset = (dim - 1) as isize * stride; if offset >= 0 { end += offset; } else { start += offset; } } end += itemsize; } let start = unsafe { data.offset(start) }; let end = unsafe { data.offset(end) }; (start, end) } fn gcd_strides(array: *mut PyArrayObject) -> isize { let nd = unsafe { (*array).nd } as usize; if nd == 0 { return 1; } let strides = unsafe { from_raw_parts((*array).strides, nd) }; strides.iter().copied().reduce(gcd).unwrap_or(1) } #[cfg(test)] mod tests { use super::*; use ndarray::Array; use pyo3::types::IntoPyDict; use crate::array::{PyArray, PyArray1, PyArray2, PyArray3, PyArrayMethods}; use crate::convert::IntoPyArray; use crate::untyped_array::PyUntypedArrayMethods; use pyo3::ffi::c_str; struct BorrowFlagsState { #[cfg(not(Py_GIL_DISABLED))] n_flags: usize, n_arrays: usize, flag: Option, } fn get_borrow_flags_state<'py>( py: Python<'py>, base: *mut c_void, key: &BorrowKey, ) -> BorrowFlagsState { let shared = get_or_insert_shared(py).unwrap(); assert_eq!(shared.version, 1); let inner = unsafe { &(*(shared.flags as *mut BorrowFlags)).0 } .lock() .unwrap(); if let Some(base_arrays) = inner.get(&base) { BorrowFlagsState { #[cfg(not(Py_GIL_DISABLED))] n_flags: inner.len(), n_arrays: base_arrays.len(), flag: base_arrays.get(key).copied(), } } else { BorrowFlagsState { #[cfg(not(Py_GIL_DISABLED))] n_flags: 0, n_arrays: 0, flag: None, } } } #[test] fn without_base_object() { Python::attach(|py| { let array = PyArray::::zeros(py, (1, 2, 3), false); let base = unsafe { (*array.as_array_ptr()).base }; assert!(base.is_null()); let base_address = base_address(py, array.as_array_ptr()); assert_eq!(base_address, array.as_ptr().cast()); let data_range = data_range(py, array.as_array_ptr()); assert_eq!(data_range.0, array.data() as *mut c_char); assert_eq!(data_range.1, unsafe { array.data().add(6) } as *mut c_char); }); } #[test] fn with_base_object() { Python::attach(|py| { let array = Array::::zeros((1, 2, 3)).into_pyarray(py); let base = unsafe { (*array.as_array_ptr()).base }; assert!(!base.is_null()); let base_address = base_address(py, array.as_array_ptr()); assert_ne!(base_address, array.as_ptr().cast()); assert_eq!(base_address, base.cast::()); let data_range = data_range(py, array.as_array_ptr()); assert_eq!(data_range.0, array.data().cast::()); assert_eq!(data_range.1, unsafe { array.data().add(6).cast::() }); }); } #[test] fn view_without_base_object() { Python::attach(|py| { let array = PyArray::::zeros(py, (1, 2, 3), false); let locals = [("array", &array)].into_py_dict(py).unwrap(); let view = py .eval(c_str!("array[:,:,0]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); assert_ne!( view.as_ptr().cast::(), array.as_ptr().cast::() ); let base = unsafe { (*view.as_array_ptr()).base }; assert_eq!(base as *mut c_void, array.as_ptr().cast::()); let base_address = base_address(py, view.as_array_ptr()); assert_ne!(base_address, view.as_ptr().cast::()); assert_eq!(base_address, base.cast::()); let data_range = data_range(py, view.as_array_ptr()); assert_eq!(data_range.0, array.data() as *mut c_char); assert_eq!(data_range.1, unsafe { array.data().add(4) } as *mut c_char); }); } #[test] fn view_with_base_object() { Python::attach(|py| { let array = Array::::zeros((1, 2, 3)).into_pyarray(py); let locals = [("array", &array)].into_py_dict(py).unwrap(); let view = py .eval(c_str!("array[:,:,0]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); assert_ne!( view.as_ptr().cast::(), array.as_ptr().cast::(), ); let base = unsafe { (*view.as_array_ptr()).base }; assert_eq!(base.cast::(), array.as_ptr().cast::()); let base = unsafe { (*array.as_array_ptr()).base }; assert!(!base.is_null()); let base_address = base_address(py, view.as_array_ptr()); assert_ne!(base_address, view.as_ptr().cast::()); assert_ne!(base_address, array.as_ptr().cast::()); assert_eq!(base_address, base.cast::()); let data_range = data_range(py, view.as_array_ptr()); assert_eq!(data_range.0, array.data().cast::()); assert_eq!(data_range.1, unsafe { array.data().add(4).cast::() }); }); } #[test] fn view_of_view_without_base_object() { Python::attach(|py| { let array = PyArray::::zeros(py, (1, 2, 3), false); let locals = [("array", &array)].into_py_dict(py).unwrap(); let view1 = py .eval(c_str!("array[:,:,0]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); assert_ne!( view1.as_ptr().cast::(), array.as_ptr().cast::() ); let locals = [("view1", &view1)].into_py_dict(py).unwrap(); let view2 = py .eval(c_str!("view1[:,0]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); assert_ne!( view2.as_ptr().cast::(), array.as_ptr().cast::() ); assert_ne!( view2.as_ptr().cast::(), view1.as_ptr().cast::() ); let base = unsafe { (*view2.as_array_ptr()).base }; assert_eq!(base as *mut c_void, array.as_ptr().cast::()); let base = unsafe { (*view1.as_array_ptr()).base }; assert_eq!(base as *mut c_void, array.as_ptr().cast::()); let base_address = base_address(py, view2.as_array_ptr()); assert_ne!(base_address, view2.as_ptr().cast::()); assert_ne!(base_address, view1.as_ptr().cast::()); assert_eq!(base_address, base as *mut c_void); let data_range = data_range(py, view2.as_array_ptr()); assert_eq!(data_range.0, array.data() as *mut c_char); assert_eq!(data_range.1, unsafe { array.data().add(1) } as *mut c_char); }); } #[test] fn view_of_view_with_base_object() { Python::attach(|py| { let array = Array::::zeros((1, 2, 3)).into_pyarray(py); let locals = [("array", &array)].into_py_dict(py).unwrap(); let view1 = py .eval(c_str!("array[:,:,0]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); assert_ne!( view1.as_ptr().cast::(), array.as_ptr().cast::(), ); let locals = [("view1", &view1)].into_py_dict(py).unwrap(); let view2 = py .eval(c_str!("view1[:,0]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); assert_ne!( view2.as_ptr().cast::(), array.as_ptr().cast::(), ); assert_ne!( view2.as_ptr().cast::(), view1.as_ptr().cast::(), ); let base = unsafe { (*view2.as_array_ptr()).base }; assert_eq!(base.cast::(), array.as_ptr().cast::()); let base = unsafe { (*view1.as_array_ptr()).base }; assert_eq!(base.cast::(), array.as_ptr().cast::()); let base = unsafe { (*array.as_array_ptr()).base }; assert!(!base.is_null()); let base_address = base_address(py, view2.as_array_ptr()); assert_ne!(base_address, view2.as_ptr().cast::()); assert_ne!(base_address, view1.as_ptr().cast::()); assert_ne!(base_address, array.as_ptr().cast::()); assert_eq!(base_address, base.cast::()); let data_range = data_range(py, view2.as_array_ptr()); assert_eq!(data_range.0, array.data().cast::()); assert_eq!(data_range.1, unsafe { array.data().add(1).cast::() }); }); } #[test] fn view_with_negative_strides() { Python::attach(|py| { let array = PyArray::::zeros(py, (1, 2, 3), false); let locals = [("array", &array)].into_py_dict(py).unwrap(); let view = py .eval(c_str!("array[::-1,:,::-1]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); assert_ne!( view.as_ptr().cast::(), array.as_ptr().cast::() ); let base = unsafe { (*view.as_array_ptr()).base }; assert_eq!(base.cast::(), array.as_ptr().cast::()); let base_address = base_address(py, view.as_array_ptr()); assert_ne!(base_address, view.as_ptr().cast::()); assert_eq!(base_address, base.cast::()); let data_range = data_range(py, view.as_array_ptr()); assert_eq!(view.data(), unsafe { array.data().offset(2) }); assert_eq!(data_range.0, unsafe { view.data().offset(-2) } as *mut c_char); assert_eq!(data_range.1, unsafe { view.data().offset(4) } as *mut c_char); }); } #[test] fn array_with_zero_dimensions() { Python::attach(|py| { let array = PyArray::::zeros(py, (1, 0, 3), false); let base = unsafe { (*array.as_array_ptr()).base }; assert!(base.is_null()); let base_address = base_address(py, array.as_array_ptr()); assert_eq!(base_address, array.as_ptr().cast::()); let data_range = data_range(py, array.as_array_ptr()); assert_eq!(data_range.0, array.data() as *mut c_char); assert_eq!(data_range.1, array.data() as *mut c_char); }); } #[test] fn view_with_non_dividing_strides() { Python::attach(|py| { let array = PyArray::::zeros(py, (10, 10), false); let locals = [("array", array)].into_py_dict(py).unwrap(); let view1 = py .eval(c_str!("array[:,::3]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); let key1 = borrow_key(py, view1.as_array_ptr()); assert_eq!(view1.strides(), &[80, 24]); assert_eq!(key1.gcd_strides, 8); let view2 = py .eval(c_str!("array[:,1::3]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); let key2 = borrow_key(py, view2.as_array_ptr()); assert_eq!(view2.strides(), &[80, 24]); assert_eq!(key2.gcd_strides, 8); let view3 = py .eval(c_str!("array[:,::2]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); let key3 = borrow_key(py, view3.as_array_ptr()); assert_eq!(view3.strides(), &[80, 16]); assert_eq!(key3.gcd_strides, 16); let view4 = py .eval(c_str!("array[:,1::2]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); let key4 = borrow_key(py, view4.as_array_ptr()); assert_eq!(view4.strides(), &[80, 16]); assert_eq!(key4.gcd_strides, 16); assert!(!key3.conflicts(&key4)); assert!(key1.conflicts(&key3)); assert!(key2.conflicts(&key4)); // This is a false conflict where all aliasing indices like (0,7) and (2,0) are out of bounds. assert!(key1.conflicts(&key2)); }); } #[test] fn borrow_multiple_arrays() { Python::attach(|py| { let array1 = PyArray::::zeros(py, 10, false); let array2 = PyArray::::zeros(py, 10, false); let base1 = base_address(py, array1.as_array_ptr()); let base2 = base_address(py, array2.as_array_ptr()); let key1 = borrow_key(py, array1.as_array_ptr()); let _exclusive1 = array1.readwrite(); { let state = get_borrow_flags_state(py, base1, &key1); #[cfg(not(Py_GIL_DISABLED))] // borrow checking state is shared and other tests might have registered a borrow assert_eq!(state.n_flags, 1); assert_eq!(state.n_arrays, 1); assert_eq!(state.flag, Some(-1)); } let key2 = borrow_key(py, array2.as_array_ptr()); let _shared2 = array2.readonly(); { let state = get_borrow_flags_state(py, base1, &key1); #[cfg(not(Py_GIL_DISABLED))] // borrow checking state is shared and other tests might have registered a borrow assert_eq!(state.n_flags, 2); assert_eq!(state.n_arrays, 1); assert_eq!(state.flag, Some(-1)); let state = get_borrow_flags_state(py, base2, &key2); assert_eq!(state.n_arrays, 1); assert_eq!(state.flag, Some(1)); } }); } #[test] fn borrow_multiple_views() { Python::attach(|py| { let array = PyArray::::zeros(py, 10, false); let base = base_address(py, array.as_array_ptr()); let locals = [("array", array)].into_py_dict(py).unwrap(); let view1 = py .eval(c_str!("array[:5]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); let key1 = borrow_key(py, view1.as_array_ptr()); let exclusive1 = view1.readwrite(); { let state = get_borrow_flags_state(py, base, &key1); #[cfg(not(Py_GIL_DISABLED))] // borrow checking state is shared and other tests might have registered a borrow assert_eq!(state.n_flags, 1); assert_eq!(state.n_arrays, 1); assert_eq!(state.flag, Some(-1)); } let view2 = py .eval(c_str!("array[5:]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); let key2 = borrow_key(py, view2.as_array_ptr()); let shared2 = view2.readonly(); { let state = get_borrow_flags_state(py, base, &key1); #[cfg(not(Py_GIL_DISABLED))] // borrow checking state is shared and other tests might have registered a borrow assert_eq!(state.n_flags, 1); assert_eq!(state.n_arrays, 2); assert_eq!(state.flag, Some(-1)); let state = get_borrow_flags_state(py, base, &key2); assert_eq!(state.flag, Some(1)); } let view3 = py .eval(c_str!("array[5:]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); let key3 = borrow_key(py, view3.as_array_ptr()); let shared3 = view3.readonly(); { let state = get_borrow_flags_state(py, base, &key1); #[cfg(not(Py_GIL_DISABLED))] // borrow checking state is shared and other tests might have registered a borrow assert_eq!(state.n_flags, 1); assert_eq!(state.n_arrays, 2); assert_eq!(state.flag, Some(-1)); let state = get_borrow_flags_state(py, base, &key2); assert_eq!(state.flag, Some(2)); let state = get_borrow_flags_state(py, base, &key3); assert_eq!(state.flag, Some(2)); } let view4 = py .eval(c_str!("array[7:]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); let key4 = borrow_key(py, view4.as_array_ptr()); let shared4 = view4.readonly(); { let state = get_borrow_flags_state(py, base, &key1); #[cfg(not(Py_GIL_DISABLED))] // borrow checking state is shared and other tests might have registered a borrow assert_eq!(state.n_flags, 1); assert_eq!(state.n_arrays, 3); assert_eq!(state.flag, Some(-1)); let state = get_borrow_flags_state(py, base, &key2); assert_eq!(state.flag, Some(2)); let state = get_borrow_flags_state(py, base, &key3); assert_eq!(state.flag, Some(2)); let state = get_borrow_flags_state(py, base, &key4); assert_eq!(state.flag, Some(1)); } drop(shared2); { let state = get_borrow_flags_state(py, base, &key1); #[cfg(not(Py_GIL_DISABLED))] // borrow checking state is shared and other tests might have registered a borrow assert_eq!(state.n_flags, 1); assert_eq!(state.n_arrays, 3); assert_eq!(state.flag, Some(-1)); let state = get_borrow_flags_state(py, base, &key2); assert_eq!(state.flag, Some(1)); let state = get_borrow_flags_state(py, base, &key3); assert_eq!(state.flag, Some(1)); let state = get_borrow_flags_state(py, base, &key4); assert_eq!(state.flag, Some(1)); } drop(shared3); { let state = get_borrow_flags_state(py, base, &key1); #[cfg(not(Py_GIL_DISABLED))] // borrow checking state is shared and other tests might have registered a borrow assert_eq!(state.n_flags, 1); assert_eq!(state.n_arrays, 2); assert_eq!(state.flag, Some(-1)); let state = get_borrow_flags_state(py, base, &key2); assert_eq!(state.flag, None); let state = get_borrow_flags_state(py, base, &key3); assert_eq!(state.flag, None); let state = get_borrow_flags_state(py, base, &key4); assert_eq!(state.flag, Some(1)); } drop(exclusive1); { let state = get_borrow_flags_state(py, base, &key1); #[cfg(not(Py_GIL_DISABLED))] // borrow checking state is shared and other tests might have registered a borrow assert_eq!(state.n_flags, 1); assert_eq!(state.n_arrays, 1); assert_eq!(state.flag, None); let state = get_borrow_flags_state(py, base, &key2); assert_eq!(state.flag, None); let state = get_borrow_flags_state(py, base, &key3); assert_eq!(state.flag, None); let state = get_borrow_flags_state(py, base, &key4); assert_eq!(state.flag, Some(1)); } drop(shared4); #[cfg(not(Py_GIL_DISABLED))] // borrow checking state is shared and other tests might have registered a borrow { assert_eq!(get_borrow_flags_state(py, base, &key1).n_flags, 0); } }); } } numpy-0.27.1/src/convert.rs000064400000000000000000000240721046102023000137050ustar 00000000000000//! Defines conversion traits between Rust types and NumPy data types. use std::{mem, os::raw::c_int, ptr}; use ndarray::{ArrayBase, Data, Dim, Dimension, IntoDimension, Ix1, OwnedRepr}; use pyo3::{Bound, Python}; use crate::array::{PyArray, PyArrayMethods}; use crate::dtype::Element; use crate::error::MAX_DIMENSIONALITY_ERR; use crate::npyffi::{self, npy_intp}; use crate::slice_container::PySliceContainer; /// Conversion trait from owning Rust types into [`PyArray`]. /// /// This trait takes ownership of `self`, which means it holds a pointer into the Rust heap. /// /// In addition, some destructive methods like `resize` cannot be used with NumPy arrays constructed using this trait. /// /// # Example /// /// ``` /// use numpy::{PyArray, IntoPyArray, PyArrayMethods}; /// use pyo3::Python; /// /// Python::attach(|py| { /// let py_array = vec![1, 2, 3].into_pyarray(py); /// /// assert_eq!(py_array.readonly().as_slice().unwrap(), &[1, 2, 3]); /// /// // Array cannot be resized when its data is owned by Rust. /// unsafe { /// assert!(py_array.resize(100).is_err()); /// } /// }); /// ``` pub trait IntoPyArray: Sized { /// The element type of resulting array. type Item: Element; /// The dimension type of the resulting array. type Dim: Dimension; /// Consumes `self` and moves its data into a NumPy array. fn into_pyarray<'py>(self, py: Python<'py>) -> Bound<'py, PyArray>; } impl IntoPyArray for Box<[T]> { type Item = T; type Dim = Ix1; fn into_pyarray<'py>(self, py: Python<'py>) -> Bound<'py, PyArray> { let container = PySliceContainer::from(self); let dims = Dim([container.len]); let strides = [mem::size_of::() as npy_intp]; // The data pointer is derived only after dissolving `Box` into `PySliceContainer` // to avoid unsound aliasing of Box<[T]> which is currently noalias, // c.f. https://github.com/rust-lang/unsafe-code-guidelines/issues/326 let data_ptr = container.ptr as *mut T; unsafe { PyArray::from_raw_parts(py, dims, strides.as_ptr(), data_ptr, container) } } } impl IntoPyArray for Vec { type Item = T; type Dim = Ix1; fn into_pyarray<'py>(mut self, py: Python<'py>) -> Bound<'py, PyArray> { let dims = Dim([self.len()]); let strides = [mem::size_of::() as npy_intp]; let data_ptr = self.as_mut_ptr(); unsafe { PyArray::from_raw_parts( py, dims, strides.as_ptr(), data_ptr, PySliceContainer::from(self), ) } } } impl IntoPyArray for ArrayBase, D> where A: Element, D: Dimension, { type Item = A; type Dim = D; fn into_pyarray<'py>(self, py: Python<'py>) -> Bound<'py, PyArray> { PyArray::from_owned_array(py, self) } } /// Conversion trait from borrowing Rust types to [`PyArray`]. /// /// This trait takes `&self` by reference, which means it allocates in Python heap and then copies the elements there. /// /// # Examples /// /// ``` /// use numpy::{PyArray, ToPyArray, PyArrayMethods}; /// use pyo3::Python; /// /// Python::attach(|py| { /// let py_array = vec![1, 2, 3].to_pyarray(py); /// /// assert_eq!(py_array.readonly().as_slice().unwrap(), &[1, 2, 3]); /// }); /// ``` /// /// Due to copying the elments, this method converts non-contiguous arrays to C-order contiguous arrays. /// /// ``` /// use numpy::prelude::*; /// use numpy::{PyArray, ToPyArray}; /// use ndarray::{arr3, s}; /// use pyo3::Python; /// /// Python::attach(|py| { /// let array = arr3(&[[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]]); /// let py_array = array.slice(s![.., 0..1, ..]).to_pyarray(py); /// /// assert_eq!(py_array.readonly().as_array(), arr3(&[[[1, 2, 3]], [[7, 8, 9]]])); /// assert!(py_array.is_c_contiguous()); /// }); /// ``` pub trait ToPyArray { /// The element type of resulting array. type Item: Element; /// The dimension type of the resulting array. type Dim: Dimension; /// Copies the content pointed to by `&self` into a newly allocated NumPy array. fn to_pyarray<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray>; } impl ToPyArray for [T] { type Item = T; type Dim = Ix1; fn to_pyarray<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray> { PyArray::from_slice(py, self) } } impl ToPyArray for ArrayBase where S: Data, D: Dimension, A: Element, { type Item = A; type Dim = D; fn to_pyarray<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray> { let len = self.len(); match self.order() { Some(flag) if A::IS_COPY => { // if the array is contiguous, copy it by `copy_nonoverlapping`. let strides = self.npy_strides(); unsafe { let array = PyArray::new_uninit(py, self.raw_dim(), strides.as_ptr(), flag); ptr::copy_nonoverlapping(self.as_ptr(), array.data(), len); array } } _ => { // if the array is not contiguous, copy all elements by `ArrayBase::iter`. let dim = self.raw_dim(); unsafe { let array = PyArray::::new(py, dim, false); let mut data_ptr = array.data(); for item in self.iter() { data_ptr.write(item.clone_ref(py)); data_ptr = data_ptr.add(1); } array } } } } } #[cfg(feature = "nalgebra")] impl ToPyArray for nalgebra::Matrix where N: nalgebra::Scalar + Element, R: nalgebra::Dim, C: nalgebra::Dim, S: nalgebra::Storage, { type Item = N; type Dim = crate::Ix2; /// Note that the NumPy array always has Fortran memory layout /// matching the [memory layout][memory-layout] used by [`nalgebra`]. /// /// [memory-layout]: https://nalgebra.org/docs/faq/#what-is-the-memory-layout-of-matrices fn to_pyarray<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray> { unsafe { let array = PyArray::::new(py, (self.nrows(), self.ncols()), true); let mut data_ptr = array.data(); if self.data.is_contiguous() { ptr::copy_nonoverlapping(self.data.ptr(), data_ptr, self.len()); } else { for item in self.iter() { data_ptr.write(item.clone_ref(py)); data_ptr = data_ptr.add(1); } } array } } } pub(crate) trait ArrayExt { fn npy_strides(&self) -> [npyffi::npy_intp; 32]; fn order(&self) -> Option; } impl ArrayExt for ArrayBase where S: Data, D: Dimension, { fn npy_strides(&self) -> [npyffi::npy_intp; 32] { let strides = self.strides(); let itemsize = mem::size_of::() as isize; assert!(strides.len() <= 32, "{}", MAX_DIMENSIONALITY_ERR); let mut new_strides = [0; 32]; for i in 0..strides.len() { new_strides[i] = (strides[i] * itemsize) as npyffi::npy_intp; } new_strides } fn order(&self) -> Option { if self.is_standard_layout() { Some(npyffi::NPY_ORDER::NPY_CORDER as _) } else if self.ndim() > 1 && self.raw_view().reversed_axes().is_standard_layout() { Some(npyffi::NPY_ORDER::NPY_FORTRANORDER as _) } else { None } } } /// Utility trait to specify the dimensions of an array. pub trait ToNpyDims: Dimension + Sealed { #[doc(hidden)] fn ndim_cint(&self) -> c_int { self.ndim() as c_int } #[doc(hidden)] fn as_dims_ptr(&mut self) -> *mut npyffi::npy_intp { self.slice_mut().as_ptr() as *mut npyffi::npy_intp } #[doc(hidden)] fn to_npy_dims(&mut self) -> npyffi::PyArray_Dims { npyffi::PyArray_Dims { ptr: self.as_dims_ptr(), len: self.ndim_cint(), } } } mod sealed { pub trait Sealed {} } use sealed::Sealed; impl ToNpyDims for D where D: Dimension {} /// Trait implemented by types that can be used to index an array. /// /// This is equivalent to [`ndarray::NdIndex`] but accounts for /// NumPy strides being in units of bytes instead of elements. /// /// All types which implement [`IntoDimension`] implement this trait as well. /// This includes at least /// - [tuple](https://doc.rust-lang.org/stable/std/primitive.tuple.html) /// - [array](https://doc.rust-lang.org/stable/std/primitive.array.html) /// - [slice](https://doc.rust-lang.org/stable/std/primitive.slice.html) pub trait NpyIndex: IntoDimension + Sealed { #[doc(hidden)] fn get_checked(self, dims: &[usize], strides: &[isize]) -> Option; #[doc(hidden)] fn get_unchecked(self, strides: &[isize]) -> isize; } impl Sealed for D {} impl NpyIndex for D { fn get_checked(self, dims: &[usize], strides: &[isize]) -> Option { let indices = self.into_dimension(); let indices = indices.slice(); if indices.len() != dims.len() { return None; } if indices.iter().zip(dims).any(|(i, d)| i >= d) { return None; } Some(get_unchecked_impl::(indices, strides)) } fn get_unchecked(self, strides: &[isize]) -> isize { let indices = self.into_dimension(); let indices = indices.slice(); get_unchecked_impl::(indices, strides) } } fn get_unchecked_impl(indices: &[usize], strides: &[isize]) -> isize { let size = mem::size_of::() as isize; indices .iter() .zip(strides) .map(|(&i, stride)| stride * i as isize / size) .sum() } numpy-0.27.1/src/datetime.rs000064400000000000000000000263451046102023000140260ustar 00000000000000//! Support datetimes and timedeltas //! //! This module provides wrappers for NumPy's [`datetime64`][scalars-datetime64] and [`timedelta64`][scalars-timedelta64] types //! which are used for time keeping with with an emphasis on scientific applications. //! This means that while these types differentiate absolute and relative quantities, they ignore calendars (a month is always 30.44 days) and time zones. //! On the other hand, their flexible units enable them to support either a large range (up to 264 years) or high precision (down to 10-18 seconds). //! //! [The corresponding section][datetime] of the NumPy documentation contains more information. //! //! # Example //! //! ``` //! use numpy::{datetime::{units, Datetime, Timedelta}, PyArray1, PyArrayMethods}; //! use pyo3::{Python, types::PyAnyMethods, ffi::c_str}; //! # use pyo3::types::PyDict; //! //! # fn main() -> pyo3::PyResult<()> { //! Python::attach(|py| { //! # let locals = py //! # .eval(c_str!("{ 'np': __import__('numpy') }"), None, None)? //! # .cast_into::()?; //! # //! let array = py //! .eval( //! c_str!("np.array([np.datetime64('2017-04-21')])"), //! None, //! Some(&locals), //! )? //! .cast_into::>>()?; //! //! assert_eq!( //! array.get_owned(0).unwrap(), //! Datetime::::from(17_277) //! ); //! //! let array = py //! .eval( //! c_str!("np.array([np.datetime64('2022-03-29')]) - np.array([np.datetime64('2017-04-21')])"), //! None, //! Some(&locals), //! )? //! .cast_into::>>()?; //! //! assert_eq!( //! array.get_owned(0).unwrap(), //! Timedelta::::from(1_803) //! ); //! # Ok(()) //! }) //! # } //! ``` //! //! [datetime]: https://numpy.org/doc/stable/reference/arrays.datetime.html //! [scalars-datetime64]: https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.datetime64 //! [scalars-timedelta64]: https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.timedelta64 use std::collections::hash_map::Entry; use std::fmt; use std::hash::Hash; use std::marker::PhantomData; use std::sync::Mutex; use pyo3::sync::MutexExt; use pyo3::{Bound, Py, Python}; use rustc_hash::FxHashMap; use crate::dtype::{clone_methods_impl, Element, PyArrayDescr, PyArrayDescrMethods}; use crate::npyffi::{ PyArray_DatetimeDTypeMetaData, PyDataType_C_METADATA, NPY_DATETIMEUNIT, NPY_TYPES, }; /// Represents the [datetime units][datetime-units] supported by NumPy /// /// [datetime-units]: https://numpy.org/doc/stable/reference/arrays.datetime.html#datetime-units pub trait Unit: Send + Sync + Clone + Copy + PartialEq + Eq + Hash + PartialOrd + Ord { /// The matching NumPy [datetime unit code][NPY_DATETIMEUNIT] /// /// [NPY_DATETIMEUNIT]: https://github.com/numpy/numpy/blob/4c60b3263ac50e5e72f6a909e156314fc3c9cba0/numpy/core/include/numpy/ndarraytypes.h#L276 const UNIT: NPY_DATETIMEUNIT; /// The abbrevation used for debug formatting const ABBREV: &'static str; } macro_rules! define_units { ($($(#[$meta:meta])* $struct:ident => $unit:ident $abbrev:literal,)+) => { $( $(#[$meta])* #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct $struct; impl Unit for $struct { const UNIT: NPY_DATETIMEUNIT = NPY_DATETIMEUNIT::$unit; const ABBREV: &'static str = $abbrev; } )+ }; } /// Predefined implementors of the [`Unit`] trait pub mod units { use super::*; define_units!( #[doc = "Years, i.e. 12 months"] Years => NPY_FR_Y "a", #[doc = "Months, i.e. 30 days"] Months => NPY_FR_M "mo", #[doc = "Weeks, i.e. 7 days"] Weeks => NPY_FR_W "w", #[doc = "Days, i.e. 24 hours"] Days => NPY_FR_D "d", #[doc = "Hours, i.e. 60 minutes"] Hours => NPY_FR_h "h", #[doc = "Minutes, i.e. 60 seconds"] Minutes => NPY_FR_m "min", #[doc = "Seconds"] Seconds => NPY_FR_s "s", #[doc = "Milliseconds, i.e. 10^-3 seconds"] Milliseconds => NPY_FR_ms "ms", #[doc = "Microseconds, i.e. 10^-6 seconds"] Microseconds => NPY_FR_us "µs", #[doc = "Nanoseconds, i.e. 10^-9 seconds"] Nanoseconds => NPY_FR_ns "ns", #[doc = "Picoseconds, i.e. 10^-12 seconds"] Picoseconds => NPY_FR_ps "ps", #[doc = "Femtoseconds, i.e. 10^-15 seconds"] Femtoseconds => NPY_FR_fs "fs", #[doc = "Attoseconds, i.e. 10^-18 seconds"] Attoseconds => NPY_FR_as "as", ); } /// Corresponds to the [`datetime64`][scalars-datetime64] scalar type /// /// [scalars-datetime64]: https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.datetime64 #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[repr(transparent)] pub struct Datetime(i64, PhantomData); impl From for Datetime { fn from(val: i64) -> Self { Self(val, PhantomData) } } impl From> for i64 { fn from(val: Datetime) -> Self { val.0 } } unsafe impl Element for Datetime { const IS_COPY: bool = true; fn get_dtype(py: Python<'_>) -> Bound<'_, PyArrayDescr> { static DTYPES: TypeDescriptors = unsafe { TypeDescriptors::new(NPY_TYPES::NPY_DATETIME) }; DTYPES.from_unit(py, U::UNIT) } clone_methods_impl!(Self); } impl fmt::Debug for Datetime { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Datetime({} {})", self.0, U::ABBREV) } } /// Corresponds to the [`timedelta64`][scalars-datetime64] scalar type /// /// [scalars-timedelta64]: https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.timedelta64 #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] #[repr(transparent)] pub struct Timedelta(i64, PhantomData); impl From for Timedelta { fn from(val: i64) -> Self { Self(val, PhantomData) } } impl From> for i64 { fn from(val: Timedelta) -> Self { val.0 } } unsafe impl Element for Timedelta { const IS_COPY: bool = true; fn get_dtype(py: Python<'_>) -> Bound<'_, PyArrayDescr> { static DTYPES: TypeDescriptors = unsafe { TypeDescriptors::new(NPY_TYPES::NPY_TIMEDELTA) }; DTYPES.from_unit(py, U::UNIT) } clone_methods_impl!(Self); } impl fmt::Debug for Timedelta { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Timedelta({} {})", self.0, U::ABBREV) } } struct TypeDescriptors { npy_type: NPY_TYPES, dtypes: Mutex>>>, } impl TypeDescriptors { /// `npy_type` must be either `NPY_DATETIME` or `NPY_TIMEDELTA`. const unsafe fn new(npy_type: NPY_TYPES) -> Self { Self { npy_type, dtypes: Mutex::new(None), } } #[allow(clippy::wrong_self_convention)] fn from_unit<'py>(&self, py: Python<'py>, unit: NPY_DATETIMEUNIT) -> Bound<'py, PyArrayDescr> { let mut dtypes = self .dtypes .lock_py_attached(py) .expect("dtype cache poisoned"); let dtype = match dtypes.get_or_insert_with(Default::default).entry(unit) { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => { let dtype = PyArrayDescr::new_from_npy_type(py, self.npy_type); // SAFETY: `self.npy_type` is either `NPY_DATETIME` or `NPY_TIMEDELTA` which implies the type of `c_metadata`. unsafe { let metadata = &mut *(PyDataType_C_METADATA(py, dtype.as_dtype_ptr()) as *mut PyArray_DatetimeDTypeMetaData); metadata.meta.base = unit; metadata.meta.num = 1; } entry.insert(dtype.into()) } }; dtype.bind(py).to_owned() } } #[cfg(test)] mod tests { use super::*; use pyo3::{ ffi::c_str, py_run, types::{PyDict, PyModule}, }; use crate::array::{PyArray1, PyArrayMethods}; #[test] fn from_python_to_rust() { Python::attach(|py| { let locals = py .eval(c_str!("{ 'np': __import__('numpy') }"), None, None) .unwrap() .cast_into::() .unwrap(); let array = py .eval( c_str!("np.array([np.datetime64('1970-01-01')])"), None, Some(&locals), ) .unwrap() .cast_into::>>() .unwrap(); let value: i64 = array.get_owned(0).unwrap().into(); assert_eq!(value, 0); }); } #[test] fn from_rust_to_python() { Python::attach(|py| { let array = PyArray1::>::zeros(py, 1, false); *array.readwrite().get_mut(0).unwrap() = Timedelta::::from(5); let np = py .eval(c_str!("__import__('numpy')"), None, None) .unwrap() .cast_into::() .unwrap(); py_run!(py, array np, "assert array.dtype == np.dtype('timedelta64[m]')"); py_run!(py, array np, "assert array[0] == np.timedelta64(5, 'm')"); }); } #[test] fn debug_formatting() { assert_eq!( format!("{:?}", Datetime::::from(28)), "Datetime(28 d)" ); assert_eq!( format!("{:?}", Timedelta::::from(160)), "Timedelta(160 ms)" ); } #[test] fn unit_conversion() { #[track_caller] fn convert<'py, S: Unit, D: Unit>(py: Python<'py>, expected_value: i64) { let array = PyArray1::>::from_slice(py, &[Timedelta::::from(1)]); let array = array.cast_array::>(false).unwrap(); let value: i64 = array.get_owned(0).unwrap().into(); assert_eq!(value, expected_value); } Python::attach(|py| { convert::(py, (97 + 400 * 365) / 400); convert::(py, (97 + 400 * 365) / 400 / 12); convert::(py, 7 * 24 * 60 * 60); convert::(py, 24 * 60 * 60); convert::(py, 60 * 60); convert::(py, 60); convert::(py, 1_000); convert::(py, 1_000_000); convert::(py, 1_000_000_000); convert::(py, 1_000_000_000_000); convert::(py, 1_000_000_000_000_000); convert::(py, 1_000); }); } } numpy-0.27.1/src/dtype.rs000064400000000000000000000736461046102023000133650ustar 00000000000000use std::mem::size_of; use std::os::raw::{c_int, c_long, c_longlong, c_short, c_uint, c_ulong, c_ulonglong, c_ushort}; use std::ptr; #[cfg(feature = "half")] use half::{bf16, f16}; use num_traits::{Bounded, Zero}; #[cfg(feature = "half")] use pyo3::sync::PyOnceLock; use pyo3::{ conversion::IntoPyObject, exceptions::{PyIndexError, PyValueError}, ffi::{self, PyTuple_Size}, pyobject_native_type_named, types::{PyAnyMethods, PyDict, PyDictMethods, PyTuple, PyType}, Borrowed, Bound, Py, PyAny, PyResult, PyTypeInfo, Python, }; use crate::npyffi::{ NpyTypes, PyArray_Descr, PyDataType_ALIGNMENT, PyDataType_ELSIZE, PyDataType_FIELDS, PyDataType_FLAGS, PyDataType_NAMES, PyDataType_SUBARRAY, NPY_ALIGNED_STRUCT, NPY_BYTEORDER_CHAR, NPY_ITEM_HASOBJECT, NPY_TYPES, PY_ARRAY_API, }; pub use num_complex::{Complex32, Complex64}; /// Binding of [`numpy.dtype`][dtype]. /// /// # Example /// /// ``` /// use numpy::{dtype, get_array_module, PyArrayDescr, PyArrayDescrMethods}; /// use numpy::pyo3::{types::{IntoPyDict, PyAnyMethods}, Python, ffi::c_str}; /// /// # fn main() -> pyo3::PyResult<()> { /// Python::attach(|py| { /// let locals = [("np", get_array_module(py)?)].into_py_dict(py)?; /// /// let dt = py /// .eval(c_str!("np.array([1, 2, 3.0]).dtype"), Some(&locals), None)? /// .cast_into::()?; /// /// assert!(dt.is_equiv_to(&dtype::(py))); /// # Ok(()) /// }) /// # } /// ``` /// /// [dtype]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.html #[repr(transparent)] pub struct PyArrayDescr(PyAny); pyobject_native_type_named!(PyArrayDescr); unsafe impl PyTypeInfo for PyArrayDescr { const NAME: &'static str = "PyArrayDescr"; const MODULE: Option<&'static str> = Some("numpy"); #[inline] fn type_object_raw<'py>(py: Python<'py>) -> *mut ffi::PyTypeObject { unsafe { PY_ARRAY_API.get_type_object(py, NpyTypes::PyArrayDescr_Type) } } } /// Returns the type descriptor ("dtype") for a registered type. #[inline] pub fn dtype<'py, T: Element>(py: Python<'py>) -> Bound<'py, PyArrayDescr> { T::get_dtype(py) } impl PyArrayDescr { /// Creates a new type descriptor ("dtype") object from an arbitrary object. /// /// Equivalent to invoking the constructor of [`numpy.dtype`][dtype]. /// /// [dtype]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.html #[inline] pub fn new<'a, 'py, T>(py: Python<'py>, ob: T) -> PyResult> where T: IntoPyObject<'py>, { fn inner<'py>( py: Python<'py>, obj: Borrowed<'_, 'py, PyAny>, ) -> PyResult> { let mut descr: *mut PyArray_Descr = ptr::null_mut(); unsafe { // None is an invalid input here and is not converted to NPY_DEFAULT_TYPE PY_ARRAY_API.PyArray_DescrConverter2(py, obj.as_ptr(), &mut descr); Bound::from_owned_ptr_or_err(py, descr.cast()).map(|any| any.cast_into_unchecked()) } } inner( py, ob.into_pyobject(py) .map_err(Into::into)? .into_any() .as_borrowed(), ) } /// Shortcut for creating a type descriptor of `object` type. #[inline] pub fn object(py: Python<'_>) -> Bound<'_, Self> { Self::from_npy_type(py, NPY_TYPES::NPY_OBJECT) } /// Returns the type descriptor for a registered type. #[inline] pub fn of<'py, T: Element>(py: Python<'py>) -> Bound<'py, Self> { T::get_dtype(py) } fn from_npy_type<'py>(py: Python<'py>, npy_type: NPY_TYPES) -> Bound<'py, Self> { unsafe { let descr = PY_ARRAY_API.PyArray_DescrFromType(py, npy_type as _); Bound::from_owned_ptr(py, descr.cast()).cast_into_unchecked() } } pub(crate) fn new_from_npy_type<'py>(py: Python<'py>, npy_type: NPY_TYPES) -> Bound<'py, Self> { unsafe { let descr = PY_ARRAY_API.PyArray_DescrNewFromType(py, npy_type as _); Bound::from_owned_ptr(py, descr.cast()).cast_into_unchecked() } } } /// Implementation of functionality for [`PyArrayDescr`]. #[doc(alias = "PyArrayDescr")] pub trait PyArrayDescrMethods<'py>: Sealed { /// Returns `self` as `*mut PyArray_Descr`. fn as_dtype_ptr(&self) -> *mut PyArray_Descr; /// Returns `self` as `*mut PyArray_Descr` while increasing the reference count. /// /// Useful in cases where the descriptor is stolen by the API. fn into_dtype_ptr(self) -> *mut PyArray_Descr; /// Returns true if two type descriptors are equivalent. fn is_equiv_to(&self, other: &Self) -> bool; /// Returns the [array scalar][arrays-scalars] corresponding to this type descriptor. /// /// Equivalent to [`numpy.dtype.type`][dtype-type]. /// /// [arrays-scalars]: https://numpy.org/doc/stable/reference/arrays.scalars.html /// [dtype-type]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.type.html fn typeobj(&self) -> Bound<'py, PyType>; /// Returns a unique number for each of the 21 different built-in /// [enumerated types][enumerated-types]. /// /// These are roughly ordered from least-to-most precision. /// /// Equivalent to [`numpy.dtype.num`][dtype-num]. /// /// [enumerated-types]: https://numpy.org/doc/stable/reference/c-api/dtype.html#enumerated-types /// [dtype-num]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.num.html fn num(&self) -> c_int { unsafe { &*self.as_dtype_ptr() }.type_num } /// Returns the element size of this type descriptor. /// /// Equivalent to [`numpy.dtype.itemsize`][dtype-itemsize]. /// /// [dtype-itemsiize]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.itemsize.html fn itemsize(&self) -> usize; /// Returns the required alignment (bytes) of this type descriptor according to the compiler. /// /// Equivalent to [`numpy.dtype.alignment`][dtype-alignment]. /// /// [dtype-alignment]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.alignment.html fn alignment(&self) -> usize; /// Returns an ASCII character indicating the byte-order of this type descriptor object. /// /// All built-in data-type objects have byteorder either `=` or `|`. /// /// Equivalent to [`numpy.dtype.byteorder`][dtype-byteorder]. /// /// [dtype-byteorder]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.byteorder.html fn byteorder(&self) -> u8 { unsafe { &*self.as_dtype_ptr() }.byteorder.max(0) as _ } /// Returns a unique ASCII character for each of the 21 different built-in types. /// /// Note that structured data types are categorized as `V` (void). /// /// Equivalent to [`numpy.dtype.char`][dtype-char]. /// /// [dtype-char]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.char.html fn char(&self) -> u8 { unsafe { &*self.as_dtype_ptr() }.type_.max(0) as _ } /// Returns an ASCII character (one of `biufcmMOSUV`) identifying the general kind of data. /// /// Note that structured data types are categorized as `V` (void). /// /// Equivalent to [`numpy.dtype.kind`][dtype-kind]. /// /// [dtype-kind]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.kind.html fn kind(&self) -> u8 { unsafe { &*self.as_dtype_ptr() }.kind.max(0) as _ } /// Returns bit-flags describing how this type descriptor is to be interpreted. /// /// Equivalent to [`numpy.dtype.flags`][dtype-flags]. /// /// [dtype-flags]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.flags.html fn flags(&self) -> u64; /// Returns the number of dimensions if this type descriptor represents a sub-array, and zero otherwise. /// /// Equivalent to [`numpy.dtype.ndim`][dtype-ndim]. /// /// [dtype-ndim]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.ndim.html fn ndim(&self) -> usize; /// Returns the type descriptor for the base element of subarrays, regardless of their dimension or shape. /// /// If the dtype is not a subarray, returns self. /// /// Equivalent to [`numpy.dtype.base`][dtype-base]. /// /// [dtype-base]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.base.html fn base(&self) -> Bound<'py, PyArrayDescr>; /// Returns the shape of the sub-array. /// /// If the dtype is not a sub-array, an empty vector is returned. /// /// Equivalent to [`numpy.dtype.shape`][dtype-shape]. /// /// [dtype-shape]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.shape.html fn shape(&self) -> Vec; /// Returns true if the type descriptor contains any reference-counted objects in any fields or sub-dtypes. /// /// Equivalent to [`numpy.dtype.hasobject`][dtype-hasobject]. /// /// [dtype-hasobject]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.hasobject.html fn has_object(&self) -> bool { self.flags() & NPY_ITEM_HASOBJECT != 0 } /// Returns true if the type descriptor is a struct which maintains field alignment. /// /// This flag is sticky, so when combining multiple structs together, it is preserved /// and produces new dtypes which are also aligned. /// /// Equivalent to [`numpy.dtype.isalignedstruct`][dtype-isalignedstruct]. /// /// [dtype-isalignedstruct]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.isalignedstruct.html fn is_aligned_struct(&self) -> bool { self.flags() & NPY_ALIGNED_STRUCT != 0 } /// Returns true if the type descriptor is a sub-array. /// /// Equivalent to PyDataType_HASSUBARRAY(self). fn has_subarray(&self) -> bool; /// Returns true if the type descriptor is a structured type. /// /// Equivalent to PyDataType_HASFIELDS(self). fn has_fields(&self) -> bool; /// Returns true if type descriptor byteorder is native, or `None` if not applicable. fn is_native_byteorder(&self) -> Option { // based on PyArray_ISNBO(self->byteorder) match self.byteorder() { b'=' => Some(true), b'|' => None, byteorder => Some(byteorder == NPY_BYTEORDER_CHAR::NPY_NATBYTE as u8), } } /// Returns an ordered list of field names, or `None` if there are no fields. /// /// The names are ordered according to increasing byte offset. /// /// Equivalent to [`numpy.dtype.names`][dtype-names]. /// /// [dtype-names]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.names.html fn names(&self) -> Option>; /// Returns the type descriptor and offset of the field with the given name. /// /// This method will return an error if this type descriptor is not structured, /// or if it does not contain a field with a given name. /// /// The list of all names can be found via [`PyArrayDescr::names`]. /// /// Equivalent to retrieving a single item from [`numpy.dtype.fields`][dtype-fields]. /// /// [dtype-fields]: https://numpy.org/doc/stable/reference/generated/numpy.dtype.fields.html fn get_field(&self, name: &str) -> PyResult<(Bound<'py, PyArrayDescr>, usize)>; } mod sealed { pub trait Sealed {} } use sealed::Sealed; impl<'py> PyArrayDescrMethods<'py> for Bound<'py, PyArrayDescr> { fn as_dtype_ptr(&self) -> *mut PyArray_Descr { self.as_ptr() as _ } fn into_dtype_ptr(self) -> *mut PyArray_Descr { self.into_ptr() as _ } fn is_equiv_to(&self, other: &Self) -> bool { let self_ptr = self.as_dtype_ptr(); let other_ptr = other.as_dtype_ptr(); unsafe { self_ptr == other_ptr || PY_ARRAY_API.PyArray_EquivTypes(self.py(), self_ptr, other_ptr) != 0 } } fn typeobj(&self) -> Bound<'py, PyType> { let dtype_type_ptr = unsafe { &*self.as_dtype_ptr() }.typeobj; unsafe { PyType::from_borrowed_type_ptr(self.py(), dtype_type_ptr) } } fn itemsize(&self) -> usize { unsafe { PyDataType_ELSIZE(self.py(), self.as_dtype_ptr()).max(0) as _ } } fn alignment(&self) -> usize { unsafe { PyDataType_ALIGNMENT(self.py(), self.as_dtype_ptr()).max(0) as _ } } fn flags(&self) -> u64 { unsafe { PyDataType_FLAGS(self.py(), self.as_dtype_ptr()) as _ } } fn ndim(&self) -> usize { let subarray = unsafe { PyDataType_SUBARRAY(self.py(), self.as_dtype_ptr()).as_ref() }; match subarray { None => 0, Some(subarray) => unsafe { PyTuple_Size(subarray.shape) }.max(0) as _, } } fn base(&self) -> Bound<'py, PyArrayDescr> { let subarray = unsafe { PyDataType_SUBARRAY(self.py(), self.as_dtype_ptr()).as_ref() }; match subarray { None => self.clone(), Some(subarray) => unsafe { Bound::from_borrowed_ptr(self.py(), subarray.base.cast()).cast_into_unchecked() }, } } fn shape(&self) -> Vec { let subarray = unsafe { PyDataType_SUBARRAY(self.py(), self.as_dtype_ptr()).as_ref() }; match subarray { None => Vec::new(), Some(subarray) => { // NumPy guarantees that shape is a tuple of non-negative integers so this should never panic. let shape = unsafe { Borrowed::from_ptr(self.py(), subarray.shape) }; shape.extract().unwrap() } } } fn has_subarray(&self) -> bool { unsafe { !PyDataType_SUBARRAY(self.py(), self.as_dtype_ptr()).is_null() } } fn has_fields(&self) -> bool { unsafe { !PyDataType_NAMES(self.py(), self.as_dtype_ptr()).is_null() } } fn names(&self) -> Option> { if !self.has_fields() { return None; } let names = unsafe { Borrowed::from_ptr(self.py(), PyDataType_NAMES(self.py(), self.as_dtype_ptr())) }; names.extract().ok() } fn get_field(&self, name: &str) -> PyResult<(Bound<'py, PyArrayDescr>, usize)> { if !self.has_fields() { return Err(PyValueError::new_err( "cannot get field information: type descriptor has no fields", )); } let dict = unsafe { Borrowed::from_ptr(self.py(), PyDataType_FIELDS(self.py(), self.as_dtype_ptr())) }; let dict = unsafe { dict.cast_unchecked::() }; // NumPy guarantees that fields are tuples of proper size and type, so this should never panic. let tuple = dict .get_item(name)? .ok_or_else(|| PyIndexError::new_err(name.to_owned()))? .cast_into::() .unwrap(); // Note that we cannot just extract the entire tuple since the third element can be a title. let dtype = tuple .get_item(0) .unwrap() .cast_into::() .unwrap(); let offset = tuple.get_item(1).unwrap().extract().unwrap(); Ok((dtype, offset)) } } impl Sealed for Bound<'_, PyArrayDescr> {} /// Represents that a type can be an element of `PyArray`. /// /// Currently, only integer/float/complex/object types are supported. The [NumPy documentation][enumerated-types] /// list the other built-in types which we are not yet implemented. /// /// Note that NumPy's integer types like `numpy.int_` and `numpy.uint` are based on C's integer hierarchy /// which implies that their widths change depending on the platform's [data model][data-models]. /// For example, `numpy.int_` matches C's `long` which is 32 bits wide on Windows (using the LLP64 data model) /// but 64 bits wide on Linux (using the LP64 data model). /// /// In contrast, Rust's [`isize`] and [`usize`] types are defined to have the same width as a pointer /// and are therefore always 64 bits wide on 64-bit platforms. If you want to match NumPy's behaviour, /// consider using the [`c_long`][std::ffi::c_long] and [`c_ulong`][std::ffi::c_ulong] type aliases. /// /// # Safety /// /// A type `T` that implements this trait should be safe when managed by a NumPy /// array, thus implementing this trait is marked unsafe. Data types that don't /// contain Python objects (i.e., either the object type itself or record types /// containing object-type fields) are assumed to be trivially copyable, which /// is reflected in the `IS_COPY` flag. Furthermore, it is assumed that for /// the object type the elements are pointers into the Python heap and that the /// corresponding `Clone` implemenation will never panic as it only increases /// the reference count. /// /// # Custom element types /// /// Note that we cannot safely store `Py` where `T: PyClass`, because the type information would be /// eliminated in the resulting NumPy array. /// In other words, objects are always treated as `Py` (a.k.a. `PyObject`) by Python code, /// and only `Py` can be stored in a type safe manner. /// /// You can however create [`Array, D>`][ndarray::Array] and turn that into a NumPy array /// safely and efficiently using [`from_owned_object_array`][crate::PyArray::from_owned_object_array]. /// /// [enumerated-types]: https://numpy.org/doc/stable/reference/c-api/dtype.html#enumerated-types /// [data-models]: https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models pub unsafe trait Element: Sized + Send + Sync { /// Flag that indicates whether this type is trivially copyable. /// /// It should be set to true for all trivially copyable types (like scalar types /// and record/array types only containing trivially copyable fields and elements). /// /// This flag should *always* be set to `false` for object types or record types /// that contain object-type fields. const IS_COPY: bool; /// Returns the associated type descriptor ("dtype") for the given element type. fn get_dtype(py: Python<'_>) -> Bound<'_, PyArrayDescr>; /// Create a clone of the value while the GIL is guaranteed to be held. fn clone_ref(&self, py: Python<'_>) -> Self; /// Create an owned copy of the slice while the GIL is guaranteed to be held. /// /// Some types may provide implementations of this method that are more efficient /// than simply mapping the `py_clone` method to each element in the slice. #[inline] fn vec_from_slice(py: Python<'_>, slc: &[Self]) -> Vec { slc.iter().map(|elem| elem.clone_ref(py)).collect() } /// Create an owned copy of the array while the GIL is guaranteed to be held. /// /// Some types may provide implementations of this method that are more efficient /// than simply mapping the `py_clone` method to each element in the view. #[inline] fn array_from_view( py: Python<'_>, view: ::ndarray::ArrayView<'_, Self, D>, ) -> ::ndarray::Array where D: ::ndarray::Dimension, { view.map(|elem| elem.clone_ref(py)) } } fn npy_int_type_lookup(npy_types: [NPY_TYPES; 3]) -> NPY_TYPES { // `npy_common.h` defines the integer aliases. In order, it checks: // NPY_BITSOF_LONG, NPY_BITSOF_LONGLONG, NPY_BITSOF_INT, NPY_BITSOF_SHORT, NPY_BITSOF_CHAR // and assigns the alias to the first matching size, so we should check in this order. match size_of::() { x if x == size_of::() => npy_types[0], x if x == size_of::() => npy_types[1], x if x == size_of::() => npy_types[2], _ => panic!("Unable to match integer type descriptor: {npy_types:?}"), } } fn npy_int_type() -> NPY_TYPES { let is_unsigned = T::min_value() == T::zero(); let bit_width = 8 * size_of::(); match (is_unsigned, bit_width) { (false, 8) => NPY_TYPES::NPY_BYTE, (false, 16) => NPY_TYPES::NPY_SHORT, (false, 32) => npy_int_type_lookup::([ NPY_TYPES::NPY_LONG, NPY_TYPES::NPY_INT, NPY_TYPES::NPY_SHORT, ]), (false, 64) => npy_int_type_lookup::([ NPY_TYPES::NPY_LONG, NPY_TYPES::NPY_LONGLONG, NPY_TYPES::NPY_INT, ]), (true, 8) => NPY_TYPES::NPY_UBYTE, (true, 16) => NPY_TYPES::NPY_USHORT, (true, 32) => npy_int_type_lookup::([ NPY_TYPES::NPY_ULONG, NPY_TYPES::NPY_UINT, NPY_TYPES::NPY_USHORT, ]), (true, 64) => npy_int_type_lookup::([ NPY_TYPES::NPY_ULONG, NPY_TYPES::NPY_ULONGLONG, NPY_TYPES::NPY_UINT, ]), _ => unreachable!(), } } // Invoke within the `Element` impl for a `Clone` type to provide an efficient // implementation of the cloning methods macro_rules! clone_methods_impl { ($Self:ty) => { #[inline] fn clone_ref(&self, _py: ::pyo3::Python<'_>) -> $Self { ::std::clone::Clone::clone(self) } #[inline] fn vec_from_slice(_py: ::pyo3::Python<'_>, slc: &[$Self]) -> Vec<$Self> { ::std::borrow::ToOwned::to_owned(slc) } #[inline] fn array_from_view( _py: ::pyo3::Python<'_>, view: ::ndarray::ArrayView<'_, $Self, D>, ) -> ::ndarray::Array<$Self, D> where D: ::ndarray::Dimension, { ::ndarray::ArrayView::to_owned(&view) } }; } pub(crate) use clone_methods_impl; use pyo3::BoundObject; macro_rules! impl_element_scalar { (@impl: $ty:ty, $npy_type:expr $(,#[$meta:meta])*) => { $(#[$meta])* unsafe impl Element for $ty { const IS_COPY: bool = true; fn get_dtype(py: Python<'_>) -> Bound<'_, PyArrayDescr> { PyArrayDescr::from_npy_type(py, $npy_type) } clone_methods_impl!($ty); } }; ($ty:ty => $npy_type:ident $(,#[$meta:meta])*) => { impl_element_scalar!(@impl: $ty, NPY_TYPES::$npy_type $(,#[$meta])*); }; ($($tys:ty),+) => { $(impl_element_scalar!(@impl: $tys, npy_int_type::<$tys>());)+ }; } impl_element_scalar!(bool => NPY_BOOL); impl_element_scalar!(i8, i16, i32, i64); impl_element_scalar!(u8, u16, u32, u64); impl_element_scalar!(f32 => NPY_FLOAT); impl_element_scalar!(f64 => NPY_DOUBLE); #[cfg(feature = "half")] impl_element_scalar!(f16 => NPY_HALF); #[cfg(feature = "half")] unsafe impl Element for bf16 { const IS_COPY: bool = true; fn get_dtype(py: Python<'_>) -> Bound<'_, PyArrayDescr> { static DTYPE: PyOnceLock> = PyOnceLock::new(); DTYPE .get_or_init(py, || { PyArrayDescr::new(py, "bfloat16").expect("A package which provides a `bfloat16` data type for NumPy is required to use the `half::bf16` element type.").unbind() }) .clone_ref(py) .into_bound(py) } clone_methods_impl!(Self); } impl_element_scalar!(Complex32 => NPY_CFLOAT, #[doc = "Complex type with `f32` components which maps to `numpy.csingle` (`numpy.complex64`)."]); impl_element_scalar!(Complex64 => NPY_CDOUBLE, #[doc = "Complex type with `f64` components which maps to `numpy.cdouble` (`numpy.complex128`)."]); #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] impl_element_scalar!(usize, isize); unsafe impl Element for Py { const IS_COPY: bool = false; fn get_dtype(py: Python<'_>) -> Bound<'_, PyArrayDescr> { PyArrayDescr::object(py) } #[inline] fn clone_ref(&self, py: Python<'_>) -> Self { Py::clone_ref(self, py) } } #[cfg(test)] mod tests { use super::*; use pyo3::types::PyString; use pyo3::{py_run, types::PyTypeMethods}; use crate::npyffi::{is_numpy_2, NPY_NEEDS_PYAPI}; #[test] fn test_dtype_new() { Python::attach(|py| { assert!(PyArrayDescr::new(py, "float64") .unwrap() .is(dtype::(py))); let dt = PyArrayDescr::new(py, [("a", "O"), ("b", "?")].as_ref()).unwrap(); assert_eq!(dt.names(), Some(vec!["a".to_owned(), "b".to_owned()])); assert!(dt.has_object()); assert!(dt.get_field("a").unwrap().0.is(dtype::>(py))); assert!(dt.get_field("b").unwrap().0.is(dtype::(py))); assert!(PyArrayDescr::new(py, 123_usize).is_err()); }); } #[test] fn test_dtype_names() { fn type_name(py: Python<'_>) -> Bound<'_, PyString> { dtype::(py).typeobj().qualname().unwrap() } Python::attach(|py| { if is_numpy_2(py) { assert_eq!(type_name::(py), "bool"); } else { assert_eq!(type_name::(py), "bool_"); } assert_eq!(type_name::(py), "int8"); assert_eq!(type_name::(py), "int16"); assert_eq!(type_name::(py), "int32"); assert_eq!(type_name::(py), "int64"); assert_eq!(type_name::(py), "uint8"); assert_eq!(type_name::(py), "uint16"); assert_eq!(type_name::(py), "uint32"); assert_eq!(type_name::(py), "uint64"); assert_eq!(type_name::(py), "float32"); assert_eq!(type_name::(py), "float64"); assert_eq!(type_name::(py), "complex64"); assert_eq!(type_name::(py), "complex128"); #[cfg(target_pointer_width = "32")] { assert_eq!(type_name::(py), "uint32"); assert_eq!(type_name::(py), "int32"); } #[cfg(target_pointer_width = "64")] { assert_eq!(type_name::(py), "uint64"); assert_eq!(type_name::(py), "int64"); } }); } #[test] fn test_dtype_methods_scalar() { Python::attach(|py| { let dt = dtype::(py); assert_eq!(dt.num(), NPY_TYPES::NPY_DOUBLE as c_int); assert_eq!(dt.flags(), 0); assert_eq!(dt.typeobj().qualname().unwrap(), "float64"); assert_eq!(dt.char(), b'd'); assert_eq!(dt.kind(), b'f'); assert_eq!(dt.byteorder(), b'='); assert_eq!(dt.is_native_byteorder(), Some(true)); assert_eq!(dt.itemsize(), 8); assert_eq!(dt.alignment(), 8); assert!(!dt.has_object()); assert!(dt.names().is_none()); assert!(!dt.has_fields()); assert!(!dt.is_aligned_struct()); assert!(!dt.has_subarray()); assert!(dt.base().is_equiv_to(&dt)); assert_eq!(dt.ndim(), 0); assert_eq!(dt.shape(), Vec::::new()); }); } #[test] fn test_dtype_methods_subarray() { Python::attach(|py| { let locals = PyDict::new(py); py_run!( py, *locals, "dtype = __import__('numpy').dtype(('f8', (2, 3)))" ); let dt = locals .get_item("dtype") .unwrap() .unwrap() .cast_into::() .unwrap(); assert_eq!(dt.num(), NPY_TYPES::NPY_VOID as c_int); assert_eq!(dt.flags(), 0); assert_eq!(dt.typeobj().qualname().unwrap(), "void"); assert_eq!(dt.char(), b'V'); assert_eq!(dt.kind(), b'V'); assert_eq!(dt.byteorder(), b'|'); assert_eq!(dt.is_native_byteorder(), None); assert_eq!(dt.itemsize(), 48); assert_eq!(dt.alignment(), 8); assert!(!dt.has_object()); assert!(dt.names().is_none()); assert!(!dt.has_fields()); assert!(!dt.is_aligned_struct()); assert!(dt.has_subarray()); assert_eq!(dt.ndim(), 2); assert_eq!(dt.shape(), vec![2, 3]); assert!(dt.base().is_equiv_to(&dtype::(py))); }); } #[test] fn test_dtype_methods_record() { Python::attach(|py| { let locals = PyDict::new(py); py_run!( py, *locals, "dtype = __import__('numpy').dtype([('x', 'u1'), ('y', 'f8'), ('z', 'O')], align=True)" ); let dt = locals .get_item("dtype") .unwrap() .unwrap() .cast_into::() .unwrap(); assert_eq!(dt.num(), NPY_TYPES::NPY_VOID as c_int); assert_ne!(dt.flags() & NPY_ITEM_HASOBJECT, 0); assert_ne!(dt.flags() & NPY_NEEDS_PYAPI, 0); assert_ne!(dt.flags() & NPY_ALIGNED_STRUCT, 0); assert_eq!(dt.typeobj().qualname().unwrap(), "void"); assert_eq!(dt.char(), b'V'); assert_eq!(dt.kind(), b'V'); assert_eq!(dt.byteorder(), b'|'); assert_eq!(dt.is_native_byteorder(), None); assert_eq!(dt.itemsize(), 24); assert_eq!(dt.alignment(), 8); assert!(dt.has_object()); assert_eq!( dt.names(), Some(vec!["x".to_owned(), "y".to_owned(), "z".to_owned()]) ); assert!(dt.has_fields()); assert!(dt.is_aligned_struct()); assert!(!dt.has_subarray()); assert_eq!(dt.ndim(), 0); assert_eq!(dt.shape(), Vec::::new()); assert!(dt.base().is_equiv_to(&dt)); let x = dt.get_field("x").unwrap(); assert!(x.0.is_equiv_to(&dtype::(py))); assert_eq!(x.1, 0); let y = dt.get_field("y").unwrap(); assert!(y.0.is_equiv_to(&dtype::(py))); assert_eq!(y.1, 8); let z = dt.get_field("z").unwrap(); assert!(z.0.is_equiv_to(&dtype::>(py))); assert_eq!(z.1, 16); }); } } numpy-0.27.1/src/error.rs000064400000000000000000000115351046102023000133560ustar 00000000000000//! Defines error types. use std::error::Error; use std::fmt; use pyo3::{ conversion::IntoPyObject, exceptions::PyTypeError, Bound, Py, PyAny, PyErr, PyErrArguments, Python, }; use crate::dtype::PyArrayDescr; /// Array dimensionality should be limited by [`NPY_MAXDIMS`][NPY_MAXDIMS] which is currently 32.´ /// /// [NPY_MAXDIMS]: https://github.com/numpy/numpy/blob/4c60b3263ac50e5e72f6a909e156314fc3c9cba0/numpy/core/include/numpy/ndarraytypes.h#L40 pub(crate) const MAX_DIMENSIONALITY_ERR: &str = "unexpected dimensionality: NumPy is expected to limit arrays to 32 or fewer dimensions.\nPlease report a bug against the `rust-numpy` crate."; pub(crate) const DIMENSIONALITY_MISMATCH_ERR: &str = "inconsistent dimensionalities: The dimensionality expected by `PyArray` does not match that given by NumPy.\nPlease report a bug against the `rust-numpy` crate."; macro_rules! impl_pyerr { ($err_type:ty) => { impl Error for $err_type {} impl PyErrArguments for $err_type { fn arguments<'py>(self, py: Python<'py>) -> Py { self.to_string() .into_pyobject(py) .unwrap() .into_any() .unbind() } } impl From<$err_type> for PyErr { fn from(err: $err_type) -> PyErr { PyTypeError::new_err(err) } } }; } /// Represents that dimensionalities of the given arrays do not match. #[derive(Debug)] pub struct DimensionalityError { from: usize, to: usize, } impl DimensionalityError { pub(crate) fn new(from: usize, to: usize) -> Self { Self { from, to } } } impl fmt::Display for DimensionalityError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "dimensionality mismatch:\n from={}, to={}", self.from, self.to ) } } impl_pyerr!(DimensionalityError); /// Represents that types of the given arrays do not match. #[derive(Debug)] pub struct TypeError<'py> { from: Bound<'py, PyArrayDescr>, to: Bound<'py, PyArrayDescr>, } impl<'py> TypeError<'py> { pub(crate) fn new(from: Bound<'py, PyArrayDescr>, to: Bound<'py, PyArrayDescr>) -> Self { Self { from, to } } } impl fmt::Display for TypeError<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "type mismatch:\n from={}, to={}", self.from, self.to) } } impl Error for TypeError<'_> {} struct TypeErrorArguments { from: Py, to: Py, } impl PyErrArguments for TypeErrorArguments { fn arguments<'py>(self, py: Python<'py>) -> Py { let err = TypeError { from: self.from.into_bound(py), to: self.to.into_bound(py), }; err.to_string() .into_pyobject(py) .unwrap() .into_any() .unbind() } } impl From> for PyErr { fn from(err: TypeError<'_>) -> PyErr { let args = TypeErrorArguments { from: err.from.into(), to: err.to.into(), }; PyTypeError::new_err(args) } } /// Represents that given `Vec` cannot be treated as an array. #[derive(Debug)] pub struct FromVecError { len: usize, exp_len: usize, } impl FromVecError { pub(crate) fn new(len: usize, exp_len: usize) -> Self { Self { len, exp_len } } } impl fmt::Display for FromVecError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "invalid length: {}, but expected {}", self.len, self.exp_len ) } } impl_pyerr!(FromVecError); /// Represents that the given array is not contiguous. #[derive(Debug)] pub struct NotContiguousError; impl fmt::Display for NotContiguousError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "The given array is not contiguous") } } impl_pyerr!(NotContiguousError); /// Inidcates why borrowing an array failed. #[derive(Debug)] #[non_exhaustive] pub enum BorrowError { /// The given array is already borrowed AlreadyBorrowed, /// The given array is not writeable NotWriteable, } impl fmt::Display for BorrowError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::AlreadyBorrowed => write!(f, "The given array is already borrowed"), Self::NotWriteable => write!(f, "The given array is not writeable"), } } } impl_pyerr!(BorrowError); /// An internal type used to ignore certain error conditions /// /// This is beneficial when those errors will never reach a public API anyway /// but dropping them will improve performance. pub(crate) struct IgnoreError; impl From for IgnoreError where PyErr: From, { fn from(_err: E) -> Self { Self } } numpy-0.27.1/src/lib.rs000064400000000000000000000131661046102023000127750ustar 00000000000000//! This crate provides Rust interfaces for [NumPy C APIs][c-api], //! especially for the [ndarray][ndarray] class. //! //! It uses [`pyo3`] for Rust bindings to CPython, and uses //! [`ndarray`] as the Rust matrix library. //! //! To resolve its dependency on NumPy, it calls `import numpy.core` internally. //! This means that this crate should work if you can use NumPy in your Python environment, //! e.g. after installing it by `pip install numpy`. It does not matter whether you use //! the system environment or a dedicated virtual environment. //! //! Loading NumPy is done automatically and on demand. So if it is not installed, the functions //! provided by this crate will panic instead of returning a result. //! #![cfg_attr( feature = "nalgebra", doc = "Integration with [`nalgebra`] is provided via an implementation of [`ToPyArray`] for [`nalgebra::Matrix`] to convert nalgebra matrices into NumPy arrays as well as the [`PyReadonlyArray::try_as_matrix`] and [`PyReadwriteArray::try_as_matrix_mut`] methods to treat NumPy array as nalgebra matrix slices. " )] //! # Example //! //! ``` //! use numpy::pyo3::Python; //! use numpy::ndarray::array; //! use numpy::{ToPyArray, PyArray, PyArrayMethods}; //! //! Python::attach(|py| { //! let py_array = array![[1i64, 2], [3, 4]].to_pyarray(py); //! //! assert_eq!( //! py_array.readonly().as_array(), //! array![[1i64, 2], [3, 4]] //! ); //! }); //! ``` //! #![cfg_attr(feature = "nalgebra", doc = "```")] #![cfg_attr(not(feature = "nalgebra"), doc = "```rust,ignore")] //! use numpy::pyo3::Python; //! use numpy::nalgebra::Matrix3; //! use numpy::{pyarray, ToPyArray, PyArrayMethods}; //! //! Python::attach(|py| { //! let py_array = pyarray![py, [0, 1, 2], [3, 4, 5], [6, 7, 8]]; //! //! let py_array_square; //! //! { //! let py_array = py_array.readwrite(); //! let mut na_matrix = py_array.as_matrix_mut(); //! //! na_matrix.add_scalar_mut(1); //! //! py_array_square = na_matrix.pow(2).to_pyarray(py); //! } //! //! assert_eq!( //! py_array.readonly().as_matrix(), //! Matrix3::new(1, 2, 3, 4, 5, 6, 7, 8, 9) //! ); //! //! assert_eq!( //! py_array_square.readonly().as_matrix(), //! Matrix3::new(30, 36, 42, 66, 81, 96, 102, 126, 150) //! ); //! }); #![doc = "```"] //! //! [c-api]: https://numpy.org/doc/stable/reference/c-api //! [ndarray]: https://numpy.org/doc/stable/reference/arrays.ndarray.html #![deny(missing_docs)] pub mod array; mod array_like; pub mod borrow; pub mod convert; pub mod datetime; mod dtype; mod error; pub mod npyffi; mod slice_container; mod strings; mod sum_products; mod untyped_array; pub use ndarray; pub use pyo3; #[cfg(feature = "nalgebra")] pub use nalgebra; pub use crate::array::{ get_array_module, PyArray, PyArray0, PyArray0Methods, PyArray1, PyArray2, PyArray3, PyArray4, PyArray5, PyArray6, PyArrayDyn, PyArrayMethods, }; pub use crate::array_like::{ AllowTypeChange, PyArrayLike, PyArrayLike0, PyArrayLike1, PyArrayLike2, PyArrayLike3, PyArrayLike4, PyArrayLike5, PyArrayLike6, PyArrayLikeDyn, TypeMustMatch, }; pub use crate::borrow::{ PyReadonlyArray, PyReadonlyArray0, PyReadonlyArray1, PyReadonlyArray2, PyReadonlyArray3, PyReadonlyArray4, PyReadonlyArray5, PyReadonlyArray6, PyReadonlyArrayDyn, PyReadwriteArray, PyReadwriteArray0, PyReadwriteArray1, PyReadwriteArray2, PyReadwriteArray3, PyReadwriteArray4, PyReadwriteArray5, PyReadwriteArray6, PyReadwriteArrayDyn, }; pub use crate::convert::{IntoPyArray, NpyIndex, ToNpyDims, ToPyArray}; pub use crate::dtype::{dtype, Complex32, Complex64, Element, PyArrayDescr, PyArrayDescrMethods}; pub use crate::error::{BorrowError, FromVecError, NotContiguousError}; pub use crate::npyffi::{PY_ARRAY_API, PY_UFUNC_API}; pub use crate::strings::{PyFixedString, PyFixedUnicode}; pub use crate::sum_products::{dot, einsum, inner}; pub use crate::untyped_array::{PyUntypedArray, PyUntypedArrayMethods}; pub use ndarray::{array, Ix1, Ix2, Ix3, Ix4, Ix5, Ix6, IxDyn}; /// A prelude /// /// The purpose of this module is to avoid direct imports of /// the method traits defined by this crate via a glob import: /// /// ``` /// # #![allow(unused_imports)] /// use numpy::prelude::*; /// ``` pub mod prelude { pub use crate::array::{PyArray0Methods, PyArrayMethods}; pub use crate::convert::{IntoPyArray, ToPyArray}; pub use crate::dtype::PyArrayDescrMethods; pub use crate::untyped_array::PyUntypedArrayMethods; } #[cfg(doctest)] mod doctest { macro_rules! doc_comment { ($doc_string:expr, $mod_name:ident) => { #[doc = $doc_string] mod $mod_name {} }; } doc_comment!(include_str!("../README.md"), readme); } #[cold] #[inline(always)] fn cold() {} /// Create a [`PyArray`] with one, two or three dimensions. /// /// This macro is backed by [`ndarray::array`]. /// /// # Example /// /// ``` /// use numpy::pyo3::Python; /// use numpy::ndarray::array; /// use numpy::{pyarray, PyArrayMethods}; /// /// Python::attach(|py| { /// let array = pyarray![py, [1, 2], [3, 4]]; /// /// assert_eq!( /// array.readonly().as_array(), /// array![[1, 2], [3, 4]] /// ); /// }); #[macro_export] macro_rules! pyarray { ($py: ident, $([$([$($x:expr),* $(,)*]),+ $(,)*]),+ $(,)*) => {{ $crate::IntoPyArray::into_pyarray($crate::array![$([$([$($x,)*],)*],)*], $py) }}; ($py: ident, $([$($x:expr),* $(,)*]),+ $(,)*) => {{ $crate::IntoPyArray::into_pyarray($crate::array![$([$($x,)*],)*], $py) }}; ($py: ident, $($x:expr),* $(,)*) => {{ $crate::IntoPyArray::into_pyarray($crate::array![$($x,)*], $py) }}; } numpy-0.27.1/src/npyffi/array.rs000064400000000000000000001103121046102023000146270ustar 00000000000000//! Low-Level binding for [Array API](https://numpy.org/doc/stable/reference/c-api/array.html) //! //! Note that NumPy's low-level allocation functions `PyArray_{malloc,realloc,free}` are not part of this module. //! The reason is that they would be re-exports of the `PyMem_Raw{Malloc,Realloc,Free}` functions from PyO3, //! but those are not unconditionally exported, i.e. they are not available when using the limited Python C-API. use std::os::raw::*; use libc::FILE; use pyo3::{ ffi::{self, PyObject, PyTypeObject}, sync::PyOnceLock, }; use crate::npyffi::*; pub(crate) fn numpy_core_name(py: Python<'_>) -> PyResult<&'static str> { static MOD_NAME: PyOnceLock<&'static str> = PyOnceLock::new(); MOD_NAME .get_or_try_init(py, || { // numpy 2 renamed to numpy._core // strategy mirrored from https://github.com/pybind/pybind11/blob/af67e87393b0f867ccffc2702885eea12de063fc/include/pybind11/numpy.h#L175-L195 let numpy = PyModule::import(py, "numpy")?; let version_string = numpy.getattr("__version__")?; let numpy_lib = PyModule::import(py, "numpy.lib")?; let numpy_version = numpy_lib .getattr("NumpyVersion")? .call1((version_string,))?; let major_version: u8 = numpy_version.getattr("major")?.extract()?; Ok(if major_version >= 2 { "numpy._core" } else { "numpy.core" }) }) .copied() } pub(crate) fn mod_name(py: Python<'_>) -> PyResult<&'static str> { static MOD_NAME: PyOnceLock = PyOnceLock::new(); MOD_NAME .get_or_try_init(py, || { let numpy_core = numpy_core_name(py)?; Ok(format!("{numpy_core}.multiarray")) }) .map(String::as_str) } const CAPSULE_NAME: &str = "_ARRAY_API"; /// A global variable which stores a ['capsule'](https://docs.python.org/3/c-api/capsule.html) /// pointer to [Numpy Array API](https://numpy.org/doc/stable/reference/c-api/array.html). /// /// You can access raw C APIs via this variable. /// /// See [PyArrayAPI](struct.PyArrayAPI.html) for what methods you can use via this variable. /// /// # Example /// ``` /// use numpy::prelude::*; /// use numpy::{PyArray, npyffi::types::NPY_SORTKIND, PY_ARRAY_API}; /// pyo3::Python::attach(|py| { /// let array = PyArray::from_slice(py, &[3, 2, 4]); /// unsafe { /// PY_ARRAY_API.PyArray_Sort(py, array.as_array_ptr(), 0, NPY_SORTKIND::NPY_QUICKSORT); /// } /// assert_eq!(array.readonly().as_slice().unwrap(), &[2, 3, 4]); /// }) /// ``` pub static PY_ARRAY_API: PyArrayAPI = PyArrayAPI(PyOnceLock::new()); /// See [PY_ARRAY_API] for more. pub struct PyArrayAPI(PyOnceLock<*const *const c_void>); unsafe impl Send for PyArrayAPI {} unsafe impl Sync for PyArrayAPI {} impl PyArrayAPI { unsafe fn get<'py>(&self, py: Python<'py>, offset: isize) -> *const *const c_void { let api = self .0 .get_or_try_init(py, || get_numpy_api(py, mod_name(py)?, CAPSULE_NAME)) .expect("Failed to access NumPy array API capsule"); api.offset(offset) } } impl PyArrayAPI { impl_api![0; PyArray_GetNDArrayCVersion() -> c_uint]; impl_api![40; PyArray_SetNumericOps(dict: *mut PyObject) -> c_int]; impl_api![41; PyArray_GetNumericOps() -> *mut PyObject]; impl_api![42; PyArray_INCREF(mp: *mut PyArrayObject) -> c_int]; impl_api![43; PyArray_XDECREF(mp: *mut PyArrayObject) -> c_int]; impl_api![44; PyArray_SetStringFunction(op: *mut PyObject, repr: c_int)]; impl_api![45; PyArray_DescrFromType(type_: c_int) -> *mut PyArray_Descr]; impl_api![46; PyArray_TypeObjectFromType(type_: c_int) -> *mut PyObject]; impl_api![47; PyArray_Zero(arr: *mut PyArrayObject) -> *mut c_char]; impl_api![48; PyArray_One(arr: *mut PyArrayObject) -> *mut c_char]; impl_api![49; PyArray_CastToType(arr: *mut PyArrayObject, dtype: *mut PyArray_Descr, is_f_order: c_int) -> *mut PyObject]; impl_api![50; NumPy1; PyArray_CastTo(out: *mut PyArrayObject, mp: *mut PyArrayObject) -> c_int]; impl_api![52; PyArray_CanCastSafely(fromtype: c_int, totype: c_int) -> c_int]; impl_api![53; PyArray_CanCastTo(from: *mut PyArray_Descr, to: *mut PyArray_Descr) -> npy_bool]; impl_api![54; PyArray_ObjectType(op: *mut PyObject, minimum_type: c_int) -> c_int]; impl_api![55; PyArray_DescrFromObject(op: *mut PyObject, mintype: *mut PyArray_Descr) -> *mut PyArray_Descr]; impl_api![56; PyArray_ConvertToCommonType(op: *mut PyObject, retn: *mut c_int) -> *mut *mut PyArrayObject]; impl_api![57; PyArray_DescrFromScalar(sc: *mut PyObject) -> *mut PyArray_Descr]; impl_api![58; PyArray_DescrFromTypeObject(type_: *mut PyObject) -> *mut PyArray_Descr]; impl_api![59; PyArray_Size(op: *mut PyObject) -> npy_intp]; impl_api![60; PyArray_Scalar(data: *mut c_void, descr: *mut PyArray_Descr, base: *mut PyObject) -> *mut PyObject]; impl_api![61; PyArray_FromScalar(scalar: *mut PyObject, outcode: *mut PyArray_Descr) -> *mut PyObject]; impl_api![62; PyArray_ScalarAsCtype(scalar: *mut PyObject, ctypeptr: *mut c_void)]; impl_api![63; PyArray_CastScalarToCtype(scalar: *mut PyObject, ctypeptr: *mut c_void, outcode: *mut PyArray_Descr) -> c_int]; impl_api![64; PyArray_CastScalarDirect(scalar: *mut PyObject, indescr: *mut PyArray_Descr, ctypeptr: *mut c_void, outtype: c_int) -> c_int]; impl_api![65; NumPy1; PyArray_ScalarFromObject(object: *mut PyObject) -> *mut PyObject]; impl_api![65; NumPy2; PyArray_Pack(descr: *mut PyArray_Descr, item: *mut c_void, value: *const PyObject) -> *mut PyObject]; impl_api![66; NumPy1; PyArray_GetCastFunc(descr: *mut PyArray_Descr, type_num: c_int) -> PyArray_VectorUnaryFunc]; impl_api![67; NumPy1; PyArray_FromDims(nd: c_int, d: *mut c_int, type_: c_int) -> *mut PyObject]; impl_api![68; NumPy1; PyArray_FromDimsAndDataAndDescr(nd: c_int, d: *mut c_int, descr: *mut PyArray_Descr, data: *mut c_char) -> *mut PyObject]; impl_api![69; PyArray_FromAny(op: *mut PyObject, newtype: *mut PyArray_Descr, min_depth: c_int, max_depth: c_int, flags: c_int, context: *mut PyObject) -> *mut PyObject]; impl_api![70; PyArray_EnsureArray(op: *mut PyObject) -> *mut PyObject]; impl_api![71; PyArray_EnsureAnyArray(op: *mut PyObject) -> *mut PyObject]; impl_api![72; PyArray_FromFile(fp: *mut FILE, dtype: *mut PyArray_Descr, num: npy_intp, sep: *mut c_char) -> *mut PyObject]; impl_api![73; PyArray_FromString(data: *mut c_char, slen: npy_intp, dtype: *mut PyArray_Descr, num: npy_intp, sep: *mut c_char) -> *mut PyObject]; impl_api![74; PyArray_FromBuffer(buf: *mut PyObject, type_: *mut PyArray_Descr, count: npy_intp, offset: npy_intp) -> *mut PyObject]; impl_api![75; PyArray_FromIter(obj: *mut PyObject, dtype: *mut PyArray_Descr, count: npy_intp) -> *mut PyObject]; impl_api![76; PyArray_Return(mp: *mut PyArrayObject) -> *mut PyObject]; impl_api![77; PyArray_GetField(self_: *mut PyArrayObject, typed: *mut PyArray_Descr, offset: c_int) -> *mut PyObject]; impl_api![78; PyArray_SetField(self_: *mut PyArrayObject, dtype: *mut PyArray_Descr, offset: c_int, val: *mut PyObject) -> c_int]; impl_api![79; PyArray_Byteswap(self_: *mut PyArrayObject, inplace: npy_bool) -> *mut PyObject]; impl_api![80; PyArray_Resize(self_: *mut PyArrayObject, newshape: *mut PyArray_Dims, refcheck: c_int, order: NPY_ORDER) -> *mut PyObject]; impl_api![81; NumPy1; PyArray_MoveInto(dst: *mut PyArrayObject, src: *mut PyArrayObject) -> c_int]; impl_api![84; PyArray_CopyObject(dest: *mut PyArrayObject, src_object: *mut PyObject) -> c_int]; impl_api![85; PyArray_NewCopy(obj: *mut PyArrayObject, order: NPY_ORDER) -> *mut PyObject]; impl_api![86; PyArray_ToList(self_: *mut PyArrayObject) -> *mut PyObject]; impl_api![87; PyArray_ToString(self_: *mut PyArrayObject, order: NPY_ORDER) -> *mut PyObject]; impl_api![88; PyArray_ToFile(self_: *mut PyArrayObject, fp: *mut FILE, sep: *mut c_char, format: *mut c_char) -> c_int]; impl_api![89; PyArray_Dump(self_: *mut PyObject, file: *mut PyObject, protocol: c_int) -> c_int]; impl_api![90; PyArray_Dumps(self_: *mut PyObject, protocol: c_int) -> *mut PyObject]; impl_api![91; PyArray_ValidType(type_: c_int) -> c_int]; impl_api![92; PyArray_UpdateFlags(ret: *mut PyArrayObject, flagmask: c_int)]; impl_api![93; PyArray_New(subtype: *mut PyTypeObject, nd: c_int, dims: *mut npy_intp, type_num: c_int, strides: *mut npy_intp, data: *mut c_void, itemsize: c_int, flags: c_int, obj: *mut PyObject) -> *mut PyObject]; impl_api![94; PyArray_NewFromDescr(subtype: *mut PyTypeObject, descr: *mut PyArray_Descr, nd: c_int, dims: *mut npy_intp, strides: *mut npy_intp, data: *mut c_void, flags: c_int, obj: *mut PyObject) -> *mut PyObject]; impl_api![95; PyArray_DescrNew(base: *mut PyArray_Descr) -> *mut PyArray_Descr]; impl_api![96; PyArray_DescrNewFromType(type_num: c_int) -> *mut PyArray_Descr]; impl_api![97; PyArray_GetPriority(obj: *mut PyObject, default_: f64) -> f64]; impl_api![98; PyArray_IterNew(obj: *mut PyObject) -> *mut PyObject]; // impl_api![99; PyArray_MultiIterNew(n: c_int, ...) -> *mut PyObject]; impl_api![100; PyArray_PyIntAsInt(o: *mut PyObject) -> c_int]; impl_api![101; PyArray_PyIntAsIntp(o: *mut PyObject) -> npy_intp]; impl_api![102; PyArray_Broadcast(mit: *mut PyArrayMultiIterObject) -> c_int]; impl_api![103; NumPy1; PyArray_FillObjectArray(arr: *mut PyArrayObject, obj: *mut PyObject)]; impl_api![104; PyArray_FillWithScalar(arr: *mut PyArrayObject, obj: *mut PyObject) -> c_int]; impl_api![105; PyArray_CheckStrides(elsize: c_int, nd: c_int, numbytes: npy_intp, offset: npy_intp, dims: *mut npy_intp, newstrides: *mut npy_intp) -> npy_bool]; impl_api![106; PyArray_DescrNewByteorder(self_: *mut PyArray_Descr, newendian: c_char) -> *mut PyArray_Descr]; impl_api![107; PyArray_IterAllButAxis(obj: *mut PyObject, inaxis: *mut c_int) -> *mut PyObject]; impl_api![108; PyArray_CheckFromAny(op: *mut PyObject, descr: *mut PyArray_Descr, min_depth: c_int, max_depth: c_int, requires: c_int, context: *mut PyObject) -> *mut PyObject]; impl_api![109; PyArray_FromArray(arr: *mut PyArrayObject, newtype: *mut PyArray_Descr, flags: c_int) -> *mut PyObject]; impl_api![110; PyArray_FromInterface(origin: *mut PyObject) -> *mut PyObject]; impl_api![111; PyArray_FromStructInterface(input: *mut PyObject) -> *mut PyObject]; impl_api![112; PyArray_FromArrayAttr(op: *mut PyObject, typecode: *mut PyArray_Descr, context: *mut PyObject) -> *mut PyObject]; impl_api![113; PyArray_ScalarKind(typenum: c_int, arr: *mut *mut PyArrayObject) -> NPY_SCALARKIND]; impl_api![114; PyArray_CanCoerceScalar(thistype: c_int, neededtype: c_int, scalar: NPY_SCALARKIND) -> c_int]; impl_api![115; NumPy1; PyArray_NewFlagsObject(obj: *mut PyObject) -> *mut PyObject]; impl_api![116; PyArray_CanCastScalar(from: *mut PyTypeObject, to: *mut PyTypeObject) -> npy_bool]; impl_api![117; NumPy1; PyArray_CompareUCS4(s1: *mut npy_ucs4, s2: *mut npy_ucs4, len: usize) -> c_int]; impl_api![118; PyArray_RemoveSmallest(multi: *mut PyArrayMultiIterObject) -> c_int]; impl_api![119; PyArray_ElementStrides(obj: *mut PyObject) -> c_int]; impl_api![120; PyArray_Item_INCREF(data: *mut c_char, descr: *mut PyArray_Descr)]; impl_api![121; PyArray_Item_XDECREF(data: *mut c_char, descr: *mut PyArray_Descr)]; impl_api![122; NumPy1; PyArray_FieldNames(fields: *mut PyObject) -> *mut PyObject]; impl_api![123; PyArray_Transpose(ap: *mut PyArrayObject, permute: *mut PyArray_Dims) -> *mut PyObject]; impl_api![124; PyArray_TakeFrom(self0: *mut PyArrayObject, indices0: *mut PyObject, axis: c_int, out: *mut PyArrayObject, clipmode: NPY_CLIPMODE) -> *mut PyObject]; impl_api![125; PyArray_PutTo(self_: *mut PyArrayObject, values0: *mut PyObject, indices0: *mut PyObject, clipmode: NPY_CLIPMODE) -> *mut PyObject]; impl_api![126; PyArray_PutMask(self_: *mut PyArrayObject, values0: *mut PyObject, mask0: *mut PyObject) -> *mut PyObject]; impl_api![127; PyArray_Repeat(aop: *mut PyArrayObject, op: *mut PyObject, axis: c_int) -> *mut PyObject]; impl_api![128; PyArray_Choose(ip: *mut PyArrayObject, op: *mut PyObject, out: *mut PyArrayObject, clipmode: NPY_CLIPMODE) -> *mut PyObject]; impl_api![129; PyArray_Sort(op: *mut PyArrayObject, axis: c_int, which: NPY_SORTKIND) -> c_int]; impl_api![130; PyArray_ArgSort(op: *mut PyArrayObject, axis: c_int, which: NPY_SORTKIND) -> *mut PyObject]; impl_api![131; PyArray_SearchSorted(op1: *mut PyArrayObject, op2: *mut PyObject, side: NPY_SEARCHSIDE, perm: *mut PyObject) -> *mut PyObject]; impl_api![132; PyArray_ArgMax(op: *mut PyArrayObject, axis: c_int, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![133; PyArray_ArgMin(op: *mut PyArrayObject, axis: c_int, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![134; PyArray_Reshape(self_: *mut PyArrayObject, shape: *mut PyObject) -> *mut PyObject]; impl_api![135; PyArray_Newshape(self_: *mut PyArrayObject, newdims: *mut PyArray_Dims, order: NPY_ORDER) -> *mut PyObject]; impl_api![136; PyArray_Squeeze(self_: *mut PyArrayObject) -> *mut PyObject]; impl_api![137; PyArray_View(self_: *mut PyArrayObject, type_: *mut PyArray_Descr, pytype: *mut PyTypeObject) -> *mut PyObject]; impl_api![138; PyArray_SwapAxes(ap: *mut PyArrayObject, a1: c_int, a2: c_int) -> *mut PyObject]; impl_api![139; PyArray_Max(ap: *mut PyArrayObject, axis: c_int, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![140; PyArray_Min(ap: *mut PyArrayObject, axis: c_int, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![141; PyArray_Ptp(ap: *mut PyArrayObject, axis: c_int, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![142; PyArray_Mean(self_: *mut PyArrayObject, axis: c_int, rtype: c_int, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![143; PyArray_Trace(self_: *mut PyArrayObject, offset: c_int, axis1: c_int, axis2: c_int, rtype: c_int, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![144; PyArray_Diagonal(self_: *mut PyArrayObject, offset: c_int, axis1: c_int, axis2: c_int) -> *mut PyObject]; impl_api![145; PyArray_Clip(self_: *mut PyArrayObject, min: *mut PyObject, max: *mut PyObject, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![146; PyArray_Conjugate(self_: *mut PyArrayObject, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![147; PyArray_Nonzero(self_: *mut PyArrayObject) -> *mut PyObject]; impl_api![148; PyArray_Std(self_: *mut PyArrayObject, axis: c_int, rtype: c_int, out: *mut PyArrayObject, variance: c_int) -> *mut PyObject]; impl_api![149; PyArray_Sum(self_: *mut PyArrayObject, axis: c_int, rtype: c_int, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![150; PyArray_CumSum(self_: *mut PyArrayObject, axis: c_int, rtype: c_int, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![151; PyArray_Prod(self_: *mut PyArrayObject, axis: c_int, rtype: c_int, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![152; PyArray_CumProd(self_: *mut PyArrayObject, axis: c_int, rtype: c_int, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![153; PyArray_All(self_: *mut PyArrayObject, axis: c_int, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![154; PyArray_Any(self_: *mut PyArrayObject, axis: c_int, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![155; PyArray_Compress(self_: *mut PyArrayObject, condition: *mut PyObject, axis: c_int, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![156; PyArray_Flatten(a: *mut PyArrayObject, order: NPY_ORDER) -> *mut PyObject]; impl_api![157; PyArray_Ravel(arr: *mut PyArrayObject, order: NPY_ORDER) -> *mut PyObject]; impl_api![158; PyArray_MultiplyList(l1: *mut npy_intp, n: c_int) -> npy_intp]; impl_api![159; PyArray_MultiplyIntList(l1: *mut c_int, n: c_int) -> c_int]; impl_api![160; PyArray_GetPtr(obj: *mut PyArrayObject, ind: *mut npy_intp) -> *mut c_void]; impl_api![161; PyArray_CompareLists(l1: *mut npy_intp, l2: *mut npy_intp, n: c_int) -> c_int]; impl_api![162; PyArray_AsCArray(op: *mut *mut PyObject, ptr: *mut c_void, dims: *mut npy_intp, nd: c_int, typedescr: *mut PyArray_Descr) -> c_int]; impl_api![163; NumPy1; PyArray_As1D(op: *mut *mut PyObject, ptr: *mut *mut c_char, d1: *mut c_int, typecode: c_int) -> c_int]; impl_api![164; NumPy1; PyArray_As2D(op: *mut *mut PyObject, ptr: *mut *mut *mut c_char, d1: *mut c_int, d2: *mut c_int, typecode: c_int) -> c_int]; impl_api![165; PyArray_Free(op: *mut PyObject, ptr: *mut c_void) -> c_int]; impl_api![166; PyArray_Converter(object: *mut PyObject, address: *mut *mut PyObject) -> c_int]; impl_api![167; PyArray_IntpFromSequence(seq: *mut PyObject, vals: *mut npy_intp, maxvals: c_int) -> c_int]; impl_api![168; PyArray_Concatenate(op: *mut PyObject, axis: c_int) -> *mut PyObject]; impl_api![169; PyArray_InnerProduct(op1: *mut PyObject, op2: *mut PyObject) -> *mut PyObject]; impl_api![170; PyArray_MatrixProduct(op1: *mut PyObject, op2: *mut PyObject) -> *mut PyObject]; impl_api![171; NumPy1; PyArray_CopyAndTranspose(op: *mut PyObject) -> *mut PyObject]; impl_api![172; PyArray_Correlate(op1: *mut PyObject, op2: *mut PyObject, mode: c_int) -> *mut PyObject]; impl_api![173; NumPy1; PyArray_TypestrConvert(itemsize: c_int, gentype: c_int) -> c_int]; impl_api![174; PyArray_DescrConverter(obj: *mut PyObject, at: *mut *mut PyArray_Descr) -> c_int]; impl_api![175; PyArray_DescrConverter2(obj: *mut PyObject, at: *mut *mut PyArray_Descr) -> c_int]; impl_api![176; PyArray_IntpConverter(obj: *mut PyObject, seq: *mut PyArray_Dims) -> c_int]; impl_api![177; PyArray_BufferConverter(obj: *mut PyObject, buf: *mut PyArray_Chunk) -> c_int]; impl_api![178; PyArray_AxisConverter(obj: *mut PyObject, axis: *mut c_int) -> c_int]; impl_api![179; PyArray_BoolConverter(object: *mut PyObject, val: *mut npy_bool) -> c_int]; impl_api![180; PyArray_ByteorderConverter(obj: *mut PyObject, endian: *mut c_char) -> c_int]; impl_api![181; PyArray_OrderConverter(object: *mut PyObject, val: *mut NPY_ORDER) -> c_int]; impl_api![182; PyArray_EquivTypes(type1: *mut PyArray_Descr, type2: *mut PyArray_Descr) -> c_uchar]; impl_api![183; PyArray_Zeros(nd: c_int, dims: *mut npy_intp, type_: *mut PyArray_Descr, is_f_order: c_int) -> *mut PyObject]; impl_api![184; PyArray_Empty(nd: c_int, dims: *mut npy_intp, type_: *mut PyArray_Descr, is_f_order: c_int) -> *mut PyObject]; impl_api![185; PyArray_Where(condition: *mut PyObject, x: *mut PyObject, y: *mut PyObject) -> *mut PyObject]; impl_api![186; PyArray_Arange(start: f64, stop: f64, step: f64, type_num: c_int) -> *mut PyObject]; impl_api![187; PyArray_ArangeObj(start: *mut PyObject, stop: *mut PyObject, step: *mut PyObject, dtype: *mut PyArray_Descr) -> *mut PyObject]; impl_api![188; PyArray_SortkindConverter(obj: *mut PyObject, sortkind: *mut NPY_SORTKIND) -> c_int]; impl_api![189; PyArray_LexSort(sort_keys: *mut PyObject, axis: c_int) -> *mut PyObject]; impl_api![190; PyArray_Round(a: *mut PyArrayObject, decimals: c_int, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![191; PyArray_EquivTypenums(typenum1: c_int, typenum2: c_int) -> c_uchar]; impl_api![192; PyArray_RegisterDataType(descr: *mut PyArray_DescrProto) -> c_int]; impl_api![193; PyArray_RegisterCastFunc(descr: *mut PyArray_Descr, totype: c_int, castfunc: PyArray_VectorUnaryFunc) -> c_int]; impl_api![194; PyArray_RegisterCanCast(descr: *mut PyArray_Descr, totype: c_int, scalar: NPY_SCALARKIND) -> c_int]; impl_api![195; PyArray_InitArrFuncs(f: *mut PyArray_ArrFuncs)]; impl_api![196; PyArray_IntTupleFromIntp(len: c_int, vals: *mut npy_intp) -> *mut PyObject]; impl_api![197; NumPy1; PyArray_ElementFromName(str: *mut c_char) -> c_int]; impl_api![198; PyArray_ClipmodeConverter(object: *mut PyObject, val: *mut NPY_CLIPMODE) -> c_int]; impl_api![199; PyArray_OutputConverter(object: *mut PyObject, address: *mut *mut PyArrayObject) -> c_int]; impl_api![200; PyArray_BroadcastToShape(obj: *mut PyObject, dims: *mut npy_intp, nd: c_int) -> *mut PyObject]; impl_api![201; NumPy1; _PyArray_SigintHandler(signum: c_int)]; impl_api![202; NumPy1; _PyArray_GetSigintBuf() -> *mut c_void]; impl_api![203; PyArray_DescrAlignConverter(obj: *mut PyObject, at: *mut *mut PyArray_Descr) -> c_int]; impl_api![204; PyArray_DescrAlignConverter2(obj: *mut PyObject, at: *mut *mut PyArray_Descr) -> c_int]; impl_api![205; PyArray_SearchsideConverter(obj: *mut PyObject, addr: *mut c_void) -> c_int]; impl_api![206; PyArray_CheckAxis(arr: *mut PyArrayObject, axis: *mut c_int, flags: c_int) -> *mut PyObject]; impl_api![207; PyArray_OverflowMultiplyList(l1: *mut npy_intp, n: c_int) -> npy_intp]; impl_api![208; NumPy1; PyArray_CompareString(s1: *mut c_char, s2: *mut c_char, len: usize) -> c_int]; // impl_api![209; PyArray_MultiIterFromObjects(mps: *mut *mut PyObject, n: c_int, nadd: c_int, ...) -> *mut PyObject]; impl_api![210; PyArray_GetEndianness() -> c_int]; impl_api![211; PyArray_GetNDArrayCFeatureVersion() -> c_uint]; impl_api![212; PyArray_Correlate2(op1: *mut PyObject, op2: *mut PyObject, mode: c_int) -> *mut PyObject]; impl_api![213; PyArray_NeighborhoodIterNew(x: *mut PyArrayIterObject, bounds: *mut npy_intp, mode: c_int, fill: *mut PyArrayObject) -> *mut PyObject]; impl_api![219; NumPy1; PyArray_SetDatetimeParseFunction(op: *mut PyObject)]; impl_api![220; NumPy1; PyArray_DatetimeToDatetimeStruct(val: npy_datetime, fr: NPY_DATETIMEUNIT, result: *mut npy_datetimestruct)]; impl_api![221; NumPy1; PyArray_TimedeltaToTimedeltaStruct(val: npy_timedelta, fr: NPY_DATETIMEUNIT, result: *mut npy_timedeltastruct)]; impl_api![222; NumPy1; PyArray_DatetimeStructToDatetime(fr: NPY_DATETIMEUNIT, d: *mut npy_datetimestruct) -> npy_datetime]; impl_api![223; NumPy1; PyArray_TimedeltaStructToTimedelta(fr: NPY_DATETIMEUNIT, d: *mut npy_timedeltastruct) -> npy_datetime]; impl_api![224; NpyIter_New(op: *mut PyArrayObject, flags: npy_uint32, order: NPY_ORDER, casting: NPY_CASTING, dtype: *mut PyArray_Descr) -> *mut NpyIter]; impl_api![225; NpyIter_MultiNew(nop: c_int, op_in: *mut *mut PyArrayObject, flags: npy_uint32, order: NPY_ORDER, casting: NPY_CASTING, op_flags: *mut npy_uint32, op_request_dtypes: *mut *mut PyArray_Descr) -> *mut NpyIter]; impl_api![226; NpyIter_AdvancedNew(nop: c_int, op_in: *mut *mut PyArrayObject, flags: npy_uint32, order: NPY_ORDER, casting: NPY_CASTING, op_flags: *mut npy_uint32, op_request_dtypes: *mut *mut PyArray_Descr, oa_ndim: c_int, op_axes: *mut *mut c_int, itershape: *mut npy_intp, buffersize: npy_intp) -> *mut NpyIter]; impl_api![227; NpyIter_Copy(iter: *mut NpyIter) -> *mut NpyIter]; impl_api![228; NpyIter_Deallocate(iter: *mut NpyIter) -> c_int]; impl_api![229; NpyIter_HasDelayedBufAlloc(iter: *mut NpyIter) -> npy_bool]; impl_api![230; NpyIter_HasExternalLoop(iter: *mut NpyIter) -> npy_bool]; impl_api![231; NpyIter_EnableExternalLoop(iter: *mut NpyIter) -> c_int]; impl_api![232; NpyIter_GetInnerStrideArray(iter: *mut NpyIter) -> *mut npy_intp]; impl_api![233; NpyIter_GetInnerLoopSizePtr(iter: *mut NpyIter) -> *mut npy_intp]; impl_api![234; NpyIter_Reset(iter: *mut NpyIter, errmsg: *mut *mut c_char) -> c_int]; impl_api![235; NpyIter_ResetBasePointers(iter: *mut NpyIter, baseptrs: *mut *mut c_char, errmsg: *mut *mut c_char) -> c_int]; impl_api![236; NpyIter_ResetToIterIndexRange(iter: *mut NpyIter, istart: npy_intp, iend: npy_intp, errmsg: *mut *mut c_char) -> c_int]; impl_api![237; NpyIter_GetNDim(iter: *mut NpyIter) -> c_int]; impl_api![238; NpyIter_GetNOp(iter: *mut NpyIter) -> c_int]; impl_api![239; NpyIter_GetIterNext(iter: *mut NpyIter, errmsg: *mut *mut c_char) -> NpyIter_IterNextFunc]; impl_api![240; NpyIter_GetIterSize(iter: *mut NpyIter) -> npy_intp]; impl_api![241; NpyIter_GetIterIndexRange(iter: *mut NpyIter, istart: *mut npy_intp, iend: *mut npy_intp)]; impl_api![242; NpyIter_GetIterIndex(iter: *mut NpyIter) -> npy_intp]; impl_api![243; NpyIter_GotoIterIndex(iter: *mut NpyIter, iterindex: npy_intp) -> c_int]; impl_api![244; NpyIter_HasMultiIndex(iter: *mut NpyIter) -> npy_bool]; impl_api![245; NpyIter_GetShape(iter: *mut NpyIter, outshape: *mut npy_intp) -> c_int]; impl_api![246; NpyIter_GetGetMultiIndex(iter: *mut NpyIter, errmsg: *mut *mut c_char) -> NpyIter_GetMultiIndexFunc]; impl_api![247; NpyIter_GotoMultiIndex(iter: *mut NpyIter, multi_index: *mut npy_intp) -> c_int]; impl_api![248; NpyIter_RemoveMultiIndex(iter: *mut NpyIter) -> c_int]; impl_api![249; NpyIter_HasIndex(iter: *mut NpyIter) -> npy_bool]; impl_api![250; NpyIter_IsBuffered(iter: *mut NpyIter) -> npy_bool]; impl_api![251; NpyIter_IsGrowInner(iter: *mut NpyIter) -> npy_bool]; impl_api![252; NpyIter_GetBufferSize(iter: *mut NpyIter) -> npy_intp]; impl_api![253; NpyIter_GetIndexPtr(iter: *mut NpyIter) -> *mut npy_intp]; impl_api![254; NpyIter_GotoIndex(iter: *mut NpyIter, flat_index: npy_intp) -> c_int]; impl_api![255; NpyIter_GetDataPtrArray(iter: *mut NpyIter) -> *mut *mut c_char]; impl_api![256; NpyIter_GetDescrArray(iter: *mut NpyIter) -> *mut *mut PyArray_Descr]; impl_api![257; NpyIter_GetOperandArray(iter: *mut NpyIter) -> *mut *mut PyArrayObject]; impl_api![258; NpyIter_GetIterView(iter: *mut NpyIter, i: npy_intp) -> *mut PyArrayObject]; impl_api![259; NpyIter_GetReadFlags(iter: *mut NpyIter, outreadflags: *mut c_char)]; impl_api![260; NpyIter_GetWriteFlags(iter: *mut NpyIter, outwriteflags: *mut c_char)]; impl_api![261; NpyIter_DebugPrint(iter: *mut NpyIter)]; impl_api![262; NpyIter_IterationNeedsAPI(iter: *mut NpyIter) -> npy_bool]; impl_api![263; NpyIter_GetInnerFixedStrideArray(iter: *mut NpyIter, out_strides: *mut npy_intp)]; impl_api![264; NpyIter_RemoveAxis(iter: *mut NpyIter, axis: c_int) -> c_int]; impl_api![265; NpyIter_GetAxisStrideArray(iter: *mut NpyIter, axis: c_int) -> *mut npy_intp]; impl_api![266; NpyIter_RequiresBuffering(iter: *mut NpyIter) -> npy_bool]; impl_api![267; NpyIter_GetInitialDataPtrArray(iter: *mut NpyIter) -> *mut *mut c_char]; impl_api![268; NpyIter_CreateCompatibleStrides(iter: *mut NpyIter, itemsize: npy_intp, outstrides: *mut npy_intp) -> c_int]; impl_api![269; PyArray_CastingConverter(obj: *mut PyObject, casting: *mut NPY_CASTING) -> c_int]; impl_api![270; PyArray_CountNonzero(self_: *mut PyArrayObject) -> npy_intp]; impl_api![271; PyArray_PromoteTypes(type1: *mut PyArray_Descr, type2: *mut PyArray_Descr) -> *mut PyArray_Descr]; impl_api![272; PyArray_MinScalarType(arr: *mut PyArrayObject) -> *mut PyArray_Descr]; impl_api![273; PyArray_ResultType(narrs: npy_intp, arr: *mut *mut PyArrayObject, ndtypes: npy_intp, dtypes: *mut *mut PyArray_Descr) -> *mut PyArray_Descr]; impl_api![274; PyArray_CanCastArrayTo(arr: *mut PyArrayObject, to: *mut PyArray_Descr, casting: NPY_CASTING) -> npy_bool]; impl_api![275; PyArray_CanCastTypeTo(from: *mut PyArray_Descr, to: *mut PyArray_Descr, casting: NPY_CASTING) -> npy_bool]; impl_api![276; PyArray_EinsteinSum(subscripts: *mut c_char, nop: npy_intp, op_in: *mut *mut PyArrayObject, dtype: *mut PyArray_Descr, order: NPY_ORDER, casting: NPY_CASTING, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![277; PyArray_NewLikeArray(prototype: *mut PyArrayObject, order: NPY_ORDER, dtype: *mut PyArray_Descr, subok: c_int) -> *mut PyObject]; impl_api![278; NumPy1; PyArray_GetArrayParamsFromObject(op: *mut PyObject, requested_dtype: *mut PyArray_Descr, writeable: npy_bool, out_dtype: *mut *mut PyArray_Descr, out_ndim: *mut c_int, out_dims: *mut npy_intp, out_arr: *mut *mut PyArrayObject, context: *mut PyObject) -> c_int]; impl_api![279; PyArray_ConvertClipmodeSequence(object: *mut PyObject, modes: *mut NPY_CLIPMODE, n: c_int) -> c_int]; impl_api![280; PyArray_MatrixProduct2(op1: *mut PyObject, op2: *mut PyObject, out: *mut PyArrayObject) -> *mut PyObject]; impl_api![281; NpyIter_IsFirstVisit(iter: *mut NpyIter, iop: c_int) -> npy_bool]; impl_api![282; PyArray_SetBaseObject(arr: *mut PyArrayObject, obj: *mut PyObject) -> c_int]; impl_api![283; PyArray_CreateSortedStridePerm(ndim: c_int, strides: *mut npy_intp, out_strideperm: *mut npy_stride_sort_item)]; impl_api![284; PyArray_RemoveAxesInPlace(arr: *mut PyArrayObject, flags: *mut npy_bool)]; impl_api![285; PyArray_DebugPrint(obj: *mut PyArrayObject)]; impl_api![286; PyArray_FailUnlessWriteable(obj: *mut PyArrayObject, name: *const c_char) -> c_int]; impl_api![287; PyArray_SetUpdateIfCopyBase(arr: *mut PyArrayObject, base: *mut PyArrayObject) -> c_int]; impl_api![288; PyDataMem_NEW(size: usize) -> *mut c_void]; impl_api![289; PyDataMem_FREE(ptr: *mut c_void)]; impl_api![290; PyDataMem_RENEW(ptr: *mut c_void, size: usize) -> *mut c_void]; impl_api![291; NumPy1; PyDataMem_SetEventHook(newhook: PyDataMem_EventHookFunc, user_data: *mut c_void, old_data: *mut *mut c_void) -> PyDataMem_EventHookFunc]; impl_api![293; NumPy1; PyArray_MapIterSwapAxes(mit: *mut PyArrayMapIterObject, ret: *mut *mut PyArrayObject, getmap: c_int)]; impl_api![294; NumPy1; PyArray_MapIterArray(a: *mut PyArrayObject, index: *mut PyObject) -> *mut PyObject]; impl_api![295; NumPy1; PyArray_MapIterNext(mit: *mut PyArrayMapIterObject)]; impl_api![296; PyArray_Partition(op: *mut PyArrayObject, ktharray: *mut PyArrayObject, axis: c_int, which: NPY_SELECTKIND) -> c_int]; impl_api![297; PyArray_ArgPartition(op: *mut PyArrayObject, ktharray: *mut PyArrayObject, axis: c_int, which: NPY_SELECTKIND) -> *mut PyObject]; impl_api![298; PyArray_SelectkindConverter(obj: *mut PyObject, selectkind: *mut NPY_SELECTKIND) -> c_int]; impl_api![299; PyDataMem_NEW_ZEROED(size: usize, elsize: usize) -> *mut c_void]; impl_api![300; PyArray_CheckAnyScalarExact(obj: *mut PyObject) -> c_int]; impl_api![301; NumPy1; PyArray_MapIterArrayCopyIfOverlap(a: *mut PyArrayObject, index: *mut PyObject, copy_if_overlap: c_int, extra_op: *mut PyArrayObject) -> *mut PyObject]; impl_api![302; PyArray_ResolveWritebackIfCopy(self_: *mut PyArrayObject) -> c_int]; impl_api![303; PyArray_SetWritebackIfCopyBase(arr: *mut PyArrayObject, base: *mut PyArrayObject) -> c_int]; impl_api![304; PyDataMem_SetHandler(handler: *mut PyObject) -> *mut PyObject]; impl_api![305; PyDataMem_GetHandler() -> *mut PyObject]; impl_api![307; NumPy2; NpyDatetime_ConvertDatetime64ToDatetimeStruct(meta: *mut PyArray_DatetimeMetaData, dt: npy_datetime, out: *mut npy_datetimestruct) -> c_int]; impl_api![308; NumPy2; NpyDatetime_ConvertDatetimeStructToDatetime64(meta: *mut PyArray_DatetimeMetaData, dts: *const npy_datetimestruct, out: *mut npy_datetime) -> c_int]; impl_api![309; NumPy2; NpyDatetime_ConvertPyDateTimeToDatetimeStruct(obj: *mut PyObject, out: *mut npy_datetimestruct, out_bestunit: *mut NPY_DATETIMEUNIT, apply_tzinfo: c_int) -> c_int]; impl_api![310; NumPy2; NpyDatetime_GetDatetimeISO8601StrLen(local: c_int, base: NPY_DATETIMEUNIT) -> c_int]; impl_api![311; NumPy2; NpyDatetime_MakeISO8601Datetime(dts: *mut npy_datetimestruct, outstr: *mut c_char, outlen: npy_intp, local: c_int, utc: c_int, base: NPY_DATETIMEUNIT, tzoffset: c_int, casting: NPY_CASTING) -> c_int]; impl_api![312; NumPy2; NpyDatetime_ParseISO8601Datetime(str: *const c_char, len: pyo3::ffi::Py_ssize_t, unit: NPY_DATETIMEUNIT, casting: NPY_CASTING, out: *mut npy_datetimestruct, out_bestunit: *mut NPY_DATETIMEUNIT, out_special: *mut npy_bool) -> c_int]; impl_api![313; NumPy2; NpyString_load(allocator: *mut npy_string_allocator, packed_string: *const npy_packed_static_string, unpacked_string: *mut npy_static_string) -> c_int]; impl_api![314; NumPy2; NpyString_pack(out: *mut npy_packed_static_string) -> c_int]; impl_api![315; NumPy2; NpyString_pack_null(allocator: *mut npy_string_allocator, packed_string: *mut npy_packed_static_string) -> c_int]; impl_api![316; NumPy2; NpyString_acquire_allocator(descr: *const PyArray_StringDTypeObject) -> *mut npy_string_allocator]; impl_api![317; NumPy2; NpyString_acquire_allocators(n_descriptors: usize, descrs: *const *mut PyArray_Descr, allocators: *mut *mut npy_string_allocator)]; impl_api![318; NumPy2; NpyString_release_allocator(allocator: *mut npy_string_allocator)]; impl_api![319; NumPy2; NpyString_release_allocators(length: usize, allocators: *mut *mut npy_string_allocator)]; impl_api![361; NumPy2; PyArray_GetDefaultDescr(DType: *mut PyArray_DTypeMeta) -> *mut PyArray_Descr]; impl_api![362; NumPy2; PyArrayInitDTypeMeta_FromSpec(DType: *mut PyArray_DTypeMeta, spec: *mut PyArrayDTypeMeta_Spec) -> c_int]; impl_api![363; NumPy2; PyArray_CommonDType(dtype1: *mut PyArray_DTypeMeta, dtype2: *mut PyArray_DTypeMeta) -> PyArray_DTypeMeta]; impl_api![364; NumPy2; PyArray_PromoteDTypeSequence(length: npy_intp, dtypes_in: *mut *mut PyArray_DTypeMeta) -> *mut PyArray_DTypeMeta]; impl_api![365; NumPy2; _PyDataType_GetArrFuncs(descr: *const PyArray_Descr) -> *mut PyArray_ArrFuncs]; #[allow(non_snake_case)] pub unsafe fn PyArray_CopyInto<'py>( &self, py: Python<'py>, dst: *mut PyArrayObject, src: *mut PyArrayObject, ) -> c_int { let offset = if is_numpy_2(py) { 50 } else { 82 }; let fptr = self.get(py, offset) as *const extern "C" fn(dst: *mut PyArrayObject, src: *mut PyArrayObject) -> c_int; (*fptr)(dst, src) } #[allow(non_snake_case)] pub unsafe fn PyArray_CastAnyTo<'py>( &self, py: Python<'py>, out: *mut PyArrayObject, mp: *mut PyArrayObject, ) -> c_int { let offset = if is_numpy_2(py) { 51 } else { 83 }; let fptr = self.get(py, offset) as *const extern "C" fn(out: *mut PyArrayObject, mp: *mut PyArrayObject) -> c_int; (*fptr)(out, mp) } } // Define type objects associated with the NumPy API macro_rules! impl_array_type { ($(($offset:expr, $tname:ident)),*) => { /// All type objects exported by the NumPy API. #[allow(non_camel_case_types)] pub enum NpyTypes { $($tname),* } impl PyArrayAPI { /// Get a pointer of the type object assocaited with `ty`. pub unsafe fn get_type_object<'py>(&self, py: Python<'py>, ty: NpyTypes) -> *mut PyTypeObject { match ty { $( NpyTypes::$tname => *(self.get(py, $offset)) as _ ),* } } } } } impl_array_type! { (1, PyBigArray_Type), (2, PyArray_Type), (3, PyArrayDescr_Type), (4, PyArrayFlags_Type), (5, PyArrayIter_Type), (6, PyArrayMultiIter_Type), (7, NPY_NUMUSERTYPES), (8, PyBoolArrType_Type), (9, _PyArrayScalar_BoolValues), (10, PyGenericArrType_Type), (11, PyNumberArrType_Type), (12, PyIntegerArrType_Type), (13, PySignedIntegerArrType_Type), (14, PyUnsignedIntegerArrType_Type), (15, PyInexactArrType_Type), (16, PyFloatingArrType_Type), (17, PyComplexFloatingArrType_Type), (18, PyFlexibleArrType_Type), (19, PyCharacterArrType_Type), (20, PyByteArrType_Type), (21, PyShortArrType_Type), (22, PyIntArrType_Type), (23, PyLongArrType_Type), (24, PyLongLongArrType_Type), (25, PyUByteArrType_Type), (26, PyUShortArrType_Type), (27, PyUIntArrType_Type), (28, PyULongArrType_Type), (29, PyULongLongArrType_Type), (30, PyFloatArrType_Type), (31, PyDoubleArrType_Type), (32, PyLongDoubleArrType_Type), (33, PyCFloatArrType_Type), (34, PyCDoubleArrType_Type), (35, PyCLongDoubleArrType_Type), (36, PyObjectArrType_Type), (37, PyStringArrType_Type), (38, PyUnicodeArrType_Type), (39, PyVoidArrType_Type) } /// Checks that `op` is an instance of `PyArray` or not. #[allow(non_snake_case)] pub unsafe fn PyArray_Check<'py>(py: Python<'py>, op: *mut PyObject) -> c_int { ffi::PyObject_TypeCheck(op, PY_ARRAY_API.get_type_object(py, NpyTypes::PyArray_Type)) } /// Checks that `op` is an exact instance of `PyArray` or not. #[allow(non_snake_case)] pub unsafe fn PyArray_CheckExact<'py>(py: Python<'py>, op: *mut PyObject) -> c_int { (ffi::Py_TYPE(op) == PY_ARRAY_API.get_type_object(py, NpyTypes::PyArray_Type)) as _ } #[cfg(test)] mod tests { use super::*; #[test] fn call_api() { Python::attach(|py| unsafe { assert_eq!( PY_ARRAY_API.PyArray_MultiplyIntList(py, [1, 2, 3].as_mut_ptr(), 3), 6 ); }) } } numpy-0.27.1/src/npyffi/flags.rs000064400000000000000000000102431046102023000146070ustar 00000000000000use super::npy_uint32; use std::os::raw::c_int; pub const NPY_ARRAY_C_CONTIGUOUS: c_int = 0x0001; pub const NPY_ARRAY_F_CONTIGUOUS: c_int = 0x0002; pub const NPY_ARRAY_OWNDATA: c_int = 0x0004; pub const NPY_ARRAY_FORCECAST: c_int = 0x0010; pub const NPY_ARRAY_ENSURECOPY: c_int = 0x0020; pub const NPY_ARRAY_ENSUREARRAY: c_int = 0x0040; pub const NPY_ARRAY_ELEMENTSTRIDES: c_int = 0x0080; pub const NPY_ARRAY_ALIGNED: c_int = 0x0100; pub const NPY_ARRAY_NOTSWAPPED: c_int = 0x0200; pub const NPY_ARRAY_WRITEABLE: c_int = 0x0400; pub const NPY_ARRAY_UPDATEIFCOPY: c_int = 0x1000; pub const NPY_ARRAY_WRITEBACKIFCOPY: c_int = 0x2000; pub const NPY_ARRAY_BEHAVED: c_int = NPY_ARRAY_ALIGNED | NPY_ARRAY_WRITEABLE; pub const NPY_ARRAY_BEHAVED_NS: c_int = NPY_ARRAY_BEHAVED | NPY_ARRAY_NOTSWAPPED; pub const NPY_ARRAY_CARRAY: c_int = NPY_ARRAY_C_CONTIGUOUS | NPY_ARRAY_BEHAVED; pub const NPY_ARRAY_CARRAY_RO: c_int = NPY_ARRAY_C_CONTIGUOUS | NPY_ARRAY_ALIGNED; pub const NPY_ARRAY_FARRAY: c_int = NPY_ARRAY_F_CONTIGUOUS | NPY_ARRAY_BEHAVED; pub const NPY_ARRAY_FARRAY_RO: c_int = NPY_ARRAY_F_CONTIGUOUS | NPY_ARRAY_ALIGNED; pub const NPY_ARRAY_DEFAULT: c_int = NPY_ARRAY_CARRAY; pub const NPY_ARRAY_IN_ARRAY: c_int = NPY_ARRAY_CARRAY_RO; pub const NPY_ARRAY_OUT_ARRAY: c_int = NPY_ARRAY_CARRAY; pub const NPY_ARRAY_INOUT_ARRAY: c_int = NPY_ARRAY_CARRAY | NPY_ARRAY_UPDATEIFCOPY; pub const NPY_ARRAY_INOUT_ARRAY2: c_int = NPY_ARRAY_CARRAY | NPY_ARRAY_WRITEBACKIFCOPY; pub const NPY_ARRAY_IN_FARRAY: c_int = NPY_ARRAY_FARRAY_RO; pub const NPY_ARRAY_OUT_FARRAY: c_int = NPY_ARRAY_FARRAY; pub const NPY_ARRAY_INOUT_FARRAY: c_int = NPY_ARRAY_FARRAY | NPY_ARRAY_UPDATEIFCOPY; pub const NPY_ARRAY_INOUT_FARRAY2: c_int = NPY_ARRAY_FARRAY | NPY_ARRAY_WRITEBACKIFCOPY; pub const NPY_ARRAY_UPDATE_ALL: c_int = NPY_ARRAY_C_CONTIGUOUS | NPY_ARRAY_F_CONTIGUOUS; pub const NPY_ITER_C_INDEX: npy_uint32 = 0x00000001; pub const NPY_ITER_F_INDEX: npy_uint32 = 0x00000002; pub const NPY_ITER_MULTI_INDEX: npy_uint32 = 0x00000004; pub const NPY_ITER_EXTERNAL_LOOP: npy_uint32 = 0x00000008; pub const NPY_ITER_COMMON_DTYPE: npy_uint32 = 0x00000010; pub const NPY_ITER_REFS_OK: npy_uint32 = 0x00000020; pub const NPY_ITER_ZEROSIZE_OK: npy_uint32 = 0x00000040; pub const NPY_ITER_REDUCE_OK: npy_uint32 = 0x00000080; pub const NPY_ITER_RANGED: npy_uint32 = 0x00000100; pub const NPY_ITER_BUFFERED: npy_uint32 = 0x00000200; pub const NPY_ITER_GROWINNER: npy_uint32 = 0x00000400; pub const NPY_ITER_DELAY_BUFALLOC: npy_uint32 = 0x00000800; pub const NPY_ITER_DONT_NEGATE_STRIDES: npy_uint32 = 0x00001000; pub const NPY_ITER_COPY_IF_OVERLAP: npy_uint32 = 0x00002000; pub const NPY_ITER_READWRITE: npy_uint32 = 0x00010000; pub const NPY_ITER_READONLY: npy_uint32 = 0x00020000; pub const NPY_ITER_WRITEONLY: npy_uint32 = 0x00040000; pub const NPY_ITER_NBO: npy_uint32 = 0x00080000; pub const NPY_ITER_ALIGNED: npy_uint32 = 0x00100000; pub const NPY_ITER_CONTIG: npy_uint32 = 0x00200000; pub const NPY_ITER_COPY: npy_uint32 = 0x00400000; pub const NPY_ITER_UPDATEIFCOPY: npy_uint32 = 0x00800000; pub const NPY_ITER_ALLOCATE: npy_uint32 = 0x01000000; pub const NPY_ITER_NO_SUBTYPE: npy_uint32 = 0x02000000; pub const NPY_ITER_VIRTUAL: npy_uint32 = 0x04000000; pub const NPY_ITER_NO_BROADCAST: npy_uint32 = 0x08000000; pub const NPY_ITER_WRITEMASKED: npy_uint32 = 0x10000000; pub const NPY_ITER_ARRAYMASK: npy_uint32 = 0x20000000; pub const NPY_ITER_OVERLAP_ASSUME_ELEMENTWISE: npy_uint32 = 0x40000000; pub const NPY_ITER_GLOBAL_FLAGS: npy_uint32 = 0x0000ffff; pub const NPY_ITER_PER_OP_FLAGS: npy_uint32 = 0xffff0000; pub const NPY_ITEM_REFCOUNT: u64 = 0x01; pub const NPY_ITEM_HASOBJECT: u64 = 0x01; pub const NPY_LIST_PICKLE: u64 = 0x02; pub const NPY_ITEM_IS_POINTER: u64 = 0x04; pub const NPY_NEEDS_INIT: u64 = 0x08; pub const NPY_NEEDS_PYAPI: u64 = 0x10; pub const NPY_USE_GETITEM: u64 = 0x20; pub const NPY_USE_SETITEM: u64 = 0x40; #[allow(overflowing_literals)] pub const NPY_ALIGNED_STRUCT: u64 = 0x80; pub const NPY_FROM_FIELDS: u64 = NPY_NEEDS_INIT | NPY_LIST_PICKLE | NPY_ITEM_REFCOUNT | NPY_NEEDS_PYAPI; pub const NPY_OBJECT_DTYPE_FLAGS: u64 = NPY_LIST_PICKLE | NPY_USE_GETITEM | NPY_ITEM_IS_POINTER | NPY_ITEM_REFCOUNT | NPY_NEEDS_INIT | NPY_NEEDS_PYAPI; numpy-0.27.1/src/npyffi/mod.rs000064400000000000000000000065551046102023000143050ustar 00000000000000//! Low-Level bindings for NumPy C API. //! //! #![allow( non_camel_case_types, missing_docs, missing_debug_implementations, clippy::too_many_arguments, clippy::missing_safety_doc )] use std::mem::forget; use std::os::raw::{c_uint, c_void}; use pyo3::{ sync::PyOnceLock, types::{PyAnyMethods, PyCapsule, PyCapsuleMethods, PyModule}, PyResult, Python, }; pub const API_VERSION_2_0: c_uint = 0x00000012; static API_VERSION: PyOnceLock = PyOnceLock::new(); fn get_numpy_api<'py>( py: Python<'py>, module: &str, capsule: &str, ) -> PyResult<*const *const c_void> { let module = PyModule::import(py, module)?; let capsule = module.getattr(capsule)?.cast_into::()?; let api = capsule .pointer_checked(None)? .cast::<*const c_void>() .as_ptr() .cast_const(); // Intentionally leak a reference to the capsule // so we can safely cache a pointer into its interior. forget(capsule); Ok(api) } /// Returns whether the runtime `numpy` version is 2.0 or greater. pub fn is_numpy_2<'py>(py: Python<'py>) -> bool { let api_version = *API_VERSION.get_or_init(py, || unsafe { PY_ARRAY_API.PyArray_GetNDArrayCFeatureVersion(py) }); api_version >= API_VERSION_2_0 } // Implements wrappers for NumPy's Array and UFunc API macro_rules! impl_api { // API available on all versions [$offset: expr; $fname: ident ($($arg: ident: $t: ty),* $(,)?) $(-> $ret: ty)?] => { #[allow(non_snake_case)] pub unsafe fn $fname<'py>(&self, py: Python<'py>, $($arg : $t), *) $(-> $ret)* { let fptr = self.get(py, $offset) as *const extern "C" fn ($($arg : $t), *) $(-> $ret)*; (*fptr)($($arg), *) } }; // API with version constraints, checked at runtime [$offset: expr; NumPy1; $fname: ident ($($arg: ident: $t: ty),* $(,)?) $(-> $ret: ty)?] => { #[allow(non_snake_case)] pub unsafe fn $fname<'py>(&self, py: Python<'py>, $($arg : $t), *) $(-> $ret)* { assert!( !is_numpy_2(py), "{} requires API < {:08X} (NumPy 1) but the runtime version is API {:08X}", stringify!($fname), API_VERSION_2_0, *API_VERSION.get(py).expect("API_VERSION is initialized"), ); let fptr = self.get(py, $offset) as *const extern "C" fn ($($arg: $t), *) $(-> $ret)*; (*fptr)($($arg), *) } }; [$offset: expr; NumPy2; $fname: ident ($($arg: ident: $t: ty),* $(,)?) $(-> $ret: ty)?] => { #[allow(non_snake_case)] pub unsafe fn $fname<'py>(&self, py: Python<'py>, $($arg : $t), *) $(-> $ret)* { assert!( is_numpy_2(py), "{} requires API {:08X} or greater (NumPy 2) but the runtime version is API {:08X}", stringify!($fname), API_VERSION_2_0, *API_VERSION.get(py).expect("API_VERSION is initialized"), ); let fptr = self.get(py, $offset) as *const extern "C" fn ($($arg: $t), *) $(-> $ret)*; (*fptr)($($arg), *) } }; } pub mod array; pub mod flags; pub mod objects; pub mod types; pub mod ufunc; pub use self::array::*; pub use self::flags::*; pub use self::objects::*; pub use self::types::*; pub use self::ufunc::*; numpy-0.27.1/src/npyffi/objects.rs000064400000000000000000000407631046102023000151560ustar 00000000000000//! Low-Lebel binding for NumPy C API C-objects //! //! #![allow(non_camel_case_types)] use libc::FILE; use pyo3::ffi::*; use std::os::raw::*; use super::types::*; use crate::npyffi::*; #[repr(C)] pub struct PyArrayObject { pub ob_base: PyObject, pub data: *mut c_char, pub nd: c_int, pub dimensions: *mut npy_intp, pub strides: *mut npy_intp, pub base: *mut PyObject, pub descr: *mut PyArray_Descr, pub flags: c_int, pub weakreflist: *mut PyObject, } #[repr(C)] pub struct PyArray_Descr { pub ob_base: PyObject, pub typeobj: *mut PyTypeObject, pub kind: c_char, pub type_: c_char, pub byteorder: c_char, pub _former_flags: c_char, pub type_num: c_int, } #[repr(C)] pub struct PyArray_DescrProto { pub ob_base: PyObject, pub typeobj: *mut PyTypeObject, pub kind: c_char, pub type_: c_char, pub byteorder: c_char, pub flags: c_char, pub type_num: c_int, pub elsize: c_int, pub alignment: c_int, pub subarray: *mut PyArray_ArrayDescr, pub fields: *mut PyObject, pub names: *mut PyObject, pub f: *mut PyArray_ArrFuncs, pub metadata: *mut PyObject, pub c_metadata: *mut NpyAuxData, pub hash: npy_hash_t, } #[repr(C)] pub struct _PyArray_DescrNumPy2 { pub ob_base: PyObject, pub typeobj: *mut PyTypeObject, pub kind: c_char, pub type_: c_char, pub byteorder: c_char, pub _former_flags: c_char, pub type_num: c_int, pub flags: npy_uint64, pub elsize: npy_intp, pub alignment: npy_intp, pub metadata: *mut PyObject, pub hash: npy_hash_t, pub reserved_null: [*mut std::ffi::c_void; 2], } #[repr(C)] struct _PyArray_LegacyDescr { pub ob_base: PyObject, pub typeobj: *mut PyTypeObject, pub kind: c_char, pub type_: c_char, pub byteorder: c_char, pub _former_flags: c_char, pub type_num: c_int, pub flags: npy_uint64, pub elsize: npy_intp, pub alignment: npy_intp, pub metadata: *mut PyObject, pub hash: npy_hash_t, pub reserved_null: [*mut std::ffi::c_void; 2], pub subarray: *mut PyArray_ArrayDescr, pub fields: *mut PyObject, pub names: *mut PyObject, pub c_metadata: *mut NpyAuxData, } #[allow(non_snake_case)] #[inline(always)] pub unsafe fn PyDataType_ISLEGACY(dtype: *const PyArray_Descr) -> bool { (*dtype).type_num < NPY_TYPES::NPY_VSTRING as _ && (*dtype).type_num >= 0 } #[allow(non_snake_case)] #[inline(always)] pub unsafe fn PyDataType_SET_ELSIZE<'py>( py: Python<'py>, dtype: *mut PyArray_Descr, size: npy_intp, ) { if is_numpy_2(py) { unsafe { (*(dtype as *mut _PyArray_DescrNumPy2)).elsize = size; } } else { unsafe { (*(dtype as *mut PyArray_DescrProto)).elsize = size as c_int; } } } #[allow(non_snake_case)] #[inline(always)] pub unsafe fn PyDataType_FLAGS<'py>(py: Python<'py>, dtype: *const PyArray_Descr) -> npy_uint64 { if is_numpy_2(py) { unsafe { (*(dtype as *mut _PyArray_DescrNumPy2)).flags } } else { unsafe { (*(dtype as *mut PyArray_DescrProto)).flags as c_uchar as npy_uint64 } } } macro_rules! define_descr_accessor { ($name:ident, $property:ident, $type:ty, $legacy_only:literal, $default:expr) => { #[allow(non_snake_case)] #[inline(always)] pub unsafe fn $name<'py>(py: Python<'py>, dtype: *const PyArray_Descr) -> $type { if $legacy_only && !PyDataType_ISLEGACY(dtype) { $default } else { if is_numpy_2(py) { unsafe { (*(dtype as *const _PyArray_LegacyDescr)).$property } } else { unsafe { (*(dtype as *mut PyArray_DescrProto)).$property as $type } } } } }; } define_descr_accessor!(PyDataType_ELSIZE, elsize, npy_intp, false, 0); define_descr_accessor!(PyDataType_ALIGNMENT, alignment, npy_intp, false, 0); define_descr_accessor!( PyDataType_METADATA, metadata, *mut PyObject, true, std::ptr::null_mut() ); define_descr_accessor!( PyDataType_SUBARRAY, subarray, *mut PyArray_ArrayDescr, true, std::ptr::null_mut() ); define_descr_accessor!( PyDataType_NAMES, names, *mut PyObject, true, std::ptr::null_mut() ); define_descr_accessor!( PyDataType_FIELDS, fields, *mut PyObject, true, std::ptr::null_mut() ); define_descr_accessor!( PyDataType_C_METADATA, c_metadata, *mut NpyAuxData, true, std::ptr::null_mut() ); #[repr(C)] #[derive(Copy, Clone)] pub struct PyArray_ArrayDescr { pub base: *mut PyArray_Descr, pub shape: *mut PyObject, } #[repr(C)] #[derive(Copy, Clone)] pub struct PyArray_ArrFuncs { pub cast: [PyArray_VectorUnaryFunc; 21usize], pub getitem: PyArray_GetItemFunc, pub setitem: PyArray_SetItemFunc, pub copyswapn: PyArray_CopySwapNFunc, pub copyswap: PyArray_CopySwapFunc, pub compare: PyArray_CompareFunc, pub argmax: PyArray_ArgFunc, pub dotfunc: PyArray_DotFunc, pub scanfunc: PyArray_ScanFunc, pub fromstr: PyArray_FromStrFunc, pub nonzero: PyArray_NonzeroFunc, pub fill: PyArray_FillFunc, pub fillwithscalar: PyArray_FillWithScalarFunc, pub sort: [PyArray_SortFunc; 3usize], pub argsort: [PyArray_ArgSortFunc; 3usize], pub castdict: *mut PyObject, pub scalarkind: PyArray_ScalarKindFunc, pub cancastscalarkindto: *mut *mut c_int, pub cancastto: *mut c_int, pub fastclip: PyArray_FastClipFunc, pub fastputmask: PyArray_FastPutmaskFunc, pub fasttake: PyArray_FastTakeFunc, pub argmin: PyArray_ArgFunc, } pub type PyArray_GetItemFunc = Option *mut PyObject>; pub type PyArray_SetItemFunc = Option c_int>; pub type PyArray_CopySwapNFunc = Option< unsafe extern "C" fn( *mut c_void, npy_intp, *mut c_void, npy_intp, npy_intp, c_int, *mut c_void, ), >; pub type PyArray_CopySwapFunc = Option; pub type PyArray_NonzeroFunc = Option c_uchar>; pub type PyArray_CompareFunc = Option c_int>; pub type PyArray_ArgFunc = Option c_int>; pub type PyArray_DotFunc = Option< unsafe extern "C" fn( *mut c_void, npy_intp, *mut c_void, npy_intp, *mut c_void, npy_intp, *mut c_void, ), >; pub type PyArray_VectorUnaryFunc = Option; pub type PyArray_ScanFunc = Option c_int>; pub type PyArray_FromStrFunc = Option< unsafe extern "C" fn(*mut c_char, *mut c_void, *mut *mut c_char, *mut PyArray_Descr) -> c_int, >; pub type PyArray_FillFunc = Option c_int>; pub type PyArray_SortFunc = Option c_int>; pub type PyArray_ArgSortFunc = Option c_int>; pub type PyArray_PartitionFunc = Option< unsafe extern "C" fn( *mut c_void, npy_intp, npy_intp, *mut npy_intp, *mut npy_intp, *mut c_void, ) -> c_int, >; pub type PyArray_ArgPartitionFunc = Option< unsafe extern "C" fn( *mut c_void, *mut npy_intp, npy_intp, npy_intp, *mut npy_intp, *mut npy_intp, *mut c_void, ) -> c_int, >; pub type PyArray_FillWithScalarFunc = Option c_int>; pub type PyArray_ScalarKindFunc = Option c_int>; pub type PyArray_FastClipFunc = Option; pub type PyArray_FastPutmaskFunc = Option; pub type PyArray_FastTakeFunc = Option< unsafe extern "C" fn( *mut c_void, *mut c_void, *mut npy_intp, npy_intp, npy_intp, npy_intp, npy_intp, NPY_CLIPMODE, ) -> c_int, >; #[repr(C)] pub struct PyArrayFlagsObject { pub ob_base: PyObject, pub arr: *mut PyObject, pub flags: c_int, } #[repr(C)] #[derive(Clone, Copy)] pub struct PyArray_Dims { pub ptr: *mut npy_intp, pub len: c_int, } #[repr(C)] pub struct PyArray_Chunk { pub ob_base: PyObject, pub base: *mut PyObject, pub ptr: *mut c_void, pub len: npy_intp, pub flags: c_int, } #[repr(C)] #[derive(Clone, Copy)] pub struct PyArrayInterface { pub two: c_int, pub nd: c_int, pub typekind: c_char, pub itemsize: c_int, pub flags: c_int, pub shape: *mut npy_intp, pub strides: *mut npy_intp, pub data: *mut c_void, pub descr: *mut PyObject, } #[repr(C)] pub struct PyUFuncObject { pub ob_base: PyObject, pub nin: c_int, pub nout: c_int, pub nargs: c_int, pub identity: c_int, pub functions: *mut PyUFuncGenericFunction, pub data: *mut *mut c_void, pub ntypes: c_int, pub reserved1: c_int, pub name: *const c_char, pub types: *mut c_char, pub doc: *const c_char, pub ptr: *mut c_void, pub obj: *mut PyObject, pub userloops: *mut PyObject, pub core_enabled: c_int, pub core_num_dim_ix: c_int, pub core_num_dims: *mut c_int, pub core_dim_ixs: *mut c_int, pub core_offsets: *mut c_int, pub core_signature: *mut c_char, pub type_resolver: PyUFunc_TypeResolutionFunc, pub legacy_inner_loop_selector: PyUFunc_LegacyInnerLoopSelectionFunc, pub reserved2: *mut c_void, pub masked_inner_loop_selector: PyUFunc_MaskedInnerLoopSelectionFunc, pub op_flags: *mut npy_uint32, pub iter_flags: npy_uint32, } pub type PyUFuncGenericFunction = Option; pub type PyUFunc_MaskedStridedInnerLoopFunc = Option< unsafe extern "C" fn( *mut *mut c_char, *mut npy_intp, *mut c_char, npy_intp, npy_intp, *mut NpyAuxData, ), >; pub type PyUFunc_TypeResolutionFunc = Option< unsafe extern "C" fn( *mut PyUFuncObject, NPY_CASTING, *mut *mut PyArrayObject, *mut PyObject, *mut *mut PyArray_Descr, ) -> c_int, >; pub type PyUFunc_LegacyInnerLoopSelectionFunc = Option< unsafe extern "C" fn( *mut PyUFuncObject, *mut *mut PyArray_Descr, *mut PyUFuncGenericFunction, *mut *mut c_void, *mut c_int, ) -> c_int, >; pub type PyUFunc_MaskedInnerLoopSelectionFunc = Option< unsafe extern "C" fn( *mut PyUFuncObject, *mut *mut PyArray_Descr, *mut PyArray_Descr, *mut npy_intp, npy_intp, *mut PyUFunc_MaskedStridedInnerLoopFunc, *mut *mut NpyAuxData, *mut c_int, ) -> c_int, >; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct NpyIter([u8; 0]); #[repr(C)] pub struct PyArrayIterObject { pub ob_base: PyObject, pub nd_m1: c_int, pub index: npy_intp, pub size: npy_intp, pub coordinates: [npy_intp; 32usize], pub dims_m1: [npy_intp; 32usize], pub strides: [npy_intp; 32usize], pub backstrides: [npy_intp; 32usize], pub factors: [npy_intp; 32usize], pub ao: *mut PyArrayObject, pub dataptr: *mut c_char, pub contiguous: npy_bool, pub bounds: [[npy_intp; 2usize]; 32usize], pub limits: [[npy_intp; 2usize]; 32usize], pub limits_sizes: [npy_intp; 32usize], pub translate: npy_iter_get_dataptr_t, } #[repr(C)] pub struct PyArrayMultiIterObject { pub ob_base: PyObject, pub numiter: c_int, pub size: npy_intp, pub index: npy_intp, pub nd: c_int, pub dimensions: [npy_intp; 32usize], pub iters: [*mut PyArrayIterObject; 32usize], } #[repr(C)] pub struct PyArrayNeighborhoodIterObject { pub ob_base: PyObject, pub nd_m1: c_int, pub index: npy_intp, pub size: npy_intp, pub coordinates: [npy_intp; 32usize], pub dims_m1: [npy_intp; 32usize], pub strides: [npy_intp; 32usize], pub backstrides: [npy_intp; 32usize], pub factors: [npy_intp; 32usize], pub ao: *mut PyArrayObject, pub dataptr: *mut c_char, pub contiguous: npy_bool, pub bounds: [[npy_intp; 2usize]; 32usize], pub limits: [[npy_intp; 2usize]; 32usize], pub limits_sizes: [npy_intp; 32usize], pub translate: npy_iter_get_dataptr_t, pub nd: npy_intp, pub dimensions: [npy_intp; 32usize], pub _internal_iter: *mut PyArrayIterObject, pub constant: *mut c_char, pub mode: c_int, } #[repr(C)] pub struct PyArrayMapIterObject { pub ob_base: PyObject, pub numiter: c_int, pub size: npy_intp, pub index: npy_intp, pub nd: c_int, pub dimensions: [npy_intp; 32usize], pub outer: *mut NpyIter, pub unused: [*mut c_void; 30usize], pub array: *mut PyArrayObject, pub ait: *mut PyArrayIterObject, pub subspace: *mut PyArrayObject, pub iteraxes: [c_int; 32usize], pub fancy_strides: [npy_intp; 32usize], pub baseoffset: *mut c_char, pub consec: c_int, pub dataptr: *mut c_char, pub nd_fancy: c_int, pub fancy_dims: [npy_intp; 32usize], pub needs_api: c_int, pub extra_op: *mut PyArrayObject, pub extra_op_dtype: *mut PyArray_Descr, pub extra_op_flags: *mut npy_uint32, pub extra_op_iter: *mut NpyIter, pub extra_op_next: NpyIter_IterNextFunc, pub extra_op_ptrs: *mut *mut c_char, pub outer_next: NpyIter_IterNextFunc, pub outer_ptrs: *mut *mut c_char, pub outer_strides: *mut npy_intp, pub subspace_iter: *mut NpyIter, pub subspace_next: NpyIter_IterNextFunc, pub subspace_ptrs: *mut *mut c_char, pub subspace_strides: *mut npy_intp, pub iter_count: npy_intp, } pub type NpyIter_IterNextFunc = Option c_int>; pub type NpyIter_GetMultiIndexFunc = Option; pub type PyDataMem_EventHookFunc = Option; pub type npy_iter_get_dataptr_t = Option *mut c_char>; #[repr(C)] #[derive(Copy, Clone)] pub struct NpyAuxData { pub free: NpyAuxData_FreeFunc, pub clone: NpyAuxData_CloneFunc, pub reserved: [*mut c_void; 2usize], } pub type NpyAuxData_FreeFunc = Option; pub type NpyAuxData_CloneFunc = Option *mut NpyAuxData>; #[repr(C)] #[derive(Clone, Copy)] pub struct PyArray_DatetimeMetaData { pub base: NPY_DATETIMEUNIT, pub num: c_int, } #[repr(C)] #[derive(Clone, Copy)] pub struct PyArray_DatetimeDTypeMetaData { pub base: NpyAuxData, pub meta: PyArray_DatetimeMetaData, } // npy_packed_static_string and npy_string_allocator are opaque pointers. // FIXME(adamreichold): Consider extern types when they are stabilized. // https://github.com/rust-lang/rust/issues/43467 pub type npy_packed_static_string = c_void; pub type npy_string_allocator = c_void; pub type PyArray_DTypeMeta = PyTypeObject; #[repr(C)] #[derive(Clone, Copy)] pub struct npy_static_string { pub size: usize, pub buf: *const c_char, } #[repr(C)] pub struct PyArray_StringDTypeObject { pub base: PyArray_Descr, pub na_object: *mut PyObject, pub coerce: c_char, pub has_nan_na: c_char, pub has_string_na: c_char, pub array_owned: c_char, pub default_string: npy_static_string, pub na_name: npy_static_string, pub allocator: *mut npy_string_allocator, } #[repr(C)] #[derive(Clone, Copy)] pub struct PyArrayMethod_Spec { pub name: *const c_char, pub nin: c_int, pub nout: c_int, pub casting: NPY_CASTING, pub flags: NPY_ARRAYMETHOD_FLAGS, pub dtypes: *mut *mut PyArray_DTypeMeta, pub slots: *mut PyType_Slot, } #[repr(C)] #[derive(Clone, Copy)] pub struct PyArrayDTypeMeta_Spec { pub typeobj: *mut PyTypeObject, pub flags: c_int, pub casts: *mut *mut PyArrayMethod_Spec, pub slots: *mut PyType_Slot, pub baseclass: *mut PyTypeObject, } numpy-0.27.1/src/npyffi/types.rs000064400000000000000000000152631046102023000146660ustar 00000000000000use pyo3::ffi::{Py_hash_t, Py_intptr_t, Py_uintptr_t}; use std::os::raw::*; pub type npy_intp = Py_intptr_t; pub type npy_uintp = Py_uintptr_t; pub type npy_longlong = c_longlong; pub type npy_ulonglong = c_ulonglong; pub type npy_bool = c_uchar; pub type npy_longdouble = f64; pub type npy_byte = c_char; pub type npy_ubyte = c_uchar; pub type npy_ushort = c_ushort; pub type npy_uint = c_uint; pub type npy_ulong = c_ulong; pub type npy_char = c_char; pub type npy_short = c_short; pub type npy_int = c_int; pub type npy_long = c_long; pub type npy_float = f32; pub type npy_double = f64; pub type npy_hash_t = Py_hash_t; pub type npy_int64 = i64; pub type npy_uint64 = u64; pub type npy_int32 = i32; pub type npy_uint32 = u32; pub type npy_ucs4 = c_uint; pub type npy_int16 = i16; pub type npy_uint16 = u16; pub type npy_int8 = i8; pub type npy_uint8 = u8; pub type npy_float64 = f64; pub type npy_complex128 = npy_cdouble; pub type npy_float32 = f32; pub type npy_complex64 = npy_cfloat; pub type npy_half = npy_uint16; pub type npy_float16 = npy_half; pub type npy_float128 = npy_longdouble; pub type npy_complex256 = npy_clongdouble; pub type npy_timedelta = npy_int64; pub type npy_datetime = npy_int64; #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct npy_cdouble { pub real: f64, pub imag: f64, } #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct npy_cfloat { pub real: f32, pub imag: f32, } #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct npy_clongdouble { pub real: npy_longdouble, pub imag: npy_longdouble, } #[repr(i32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum NPY_ORDER { NPY_ANYORDER = -1, NPY_CORDER = 0, NPY_FORTRANORDER = 1, NPY_KEEPORDER = 2, } #[repr(i32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum NPY_SCALARKIND { NPY_NOSCALAR = -1, NPY_BOOL_SCALAR = 0, NPY_INTPOS_SCALAR = 1, NPY_INTNEG_SCALAR = 2, NPY_FLOAT_SCALAR = 3, NPY_COMPLEX_SCALAR = 4, NPY_OBJECT_SCALAR = 5, } #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum NPY_SORTKIND { NPY_QUICKSORT = 0, NPY_HEAPSORT = 1, NPY_MERGESORT = 2, } #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum NPY_SEARCHSIDE { NPY_SEARCHLEFT = 0, NPY_SEARCHRIGHT = 1, } #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum NPY_DATETIMEUNIT { NPY_FR_Y = 0, NPY_FR_M = 1, NPY_FR_W = 2, NPY_FR_D = 4, NPY_FR_h = 5, NPY_FR_m = 6, NPY_FR_s = 7, NPY_FR_ms = 8, NPY_FR_us = 9, NPY_FR_ns = 10, NPY_FR_ps = 11, NPY_FR_fs = 12, NPY_FR_as = 13, NPY_FR_GENERIC = 14, } #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum NPY_TYPES { NPY_BOOL = 0, NPY_BYTE = 1, NPY_UBYTE = 2, NPY_SHORT = 3, NPY_USHORT = 4, NPY_INT = 5, NPY_UINT = 6, NPY_LONG = 7, NPY_ULONG = 8, NPY_LONGLONG = 9, NPY_ULONGLONG = 10, NPY_FLOAT = 11, NPY_DOUBLE = 12, NPY_LONGDOUBLE = 13, NPY_CFLOAT = 14, NPY_CDOUBLE = 15, NPY_CLONGDOUBLE = 16, NPY_OBJECT = 17, NPY_STRING = 18, NPY_UNICODE = 19, NPY_VOID = 20, NPY_DATETIME = 21, NPY_TIMEDELTA = 22, NPY_HALF = 23, NPY_NTYPES_LEGACY = 24, NPY_NOTYPE = 25, NPY_CHAR = 26, NPY_USERDEF = 256, NPY_VSTRING = 2056, } #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum NPY_SELECTKIND { NPY_INTROSELECT = 0, } #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum NPY_CASTING { NPY_NO_CASTING = 0, NPY_EQUIV_CASTING = 1, NPY_SAFE_CASTING = 2, NPY_SAME_KIND_CASTING = 3, NPY_UNSAFE_CASTING = 4, } #[repr(u32)] #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub enum NPY_CLIPMODE { NPY_CLIP = 0, NPY_WRAP = 1, NPY_RAISE = 2, } #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct npy_datetimestruct { pub year: npy_int64, pub month: npy_int32, pub day: npy_int32, pub hour: npy_int32, pub min: npy_int32, pub sec: npy_int32, pub us: npy_int32, pub ps: npy_int32, pub as_: npy_int32, } #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct npy_timedeltastruct { pub day: npy_int64, pub sec: npy_int32, pub us: npy_int32, pub ps: npy_int32, pub as_: npy_int32, } #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct npy_stride_sort_item { pub perm: npy_intp, pub stride: npy_intp, } #[repr(u8)] #[derive(Debug, Clone, Copy)] pub enum NPY_TYPECHAR { NPY_BOOLLTR = b'?', NPY_BYTELTR = b'b', NPY_UBYTELTR = b'B', NPY_SHORTLTR = b'h', NPY_USHORTLTR = b'H', NPY_INTLTR = b'i', NPY_UINTLTR = b'I', NPY_LONGLTR = b'l', NPY_ULONGLTR = b'L', NPY_LONGLONGLTR = b'q', NPY_ULONGLONGLTR = b'Q', NPY_HALFLTR = b'e', NPY_FLOATLTR = b'f', NPY_DOUBLELTR = b'd', NPY_LONGDOUBLELTR = b'g', NPY_CFLOATLTR = b'F', NPY_CDOUBLELTR = b'D', NPY_CLONGDOUBLELTR = b'G', NPY_OBJECTLTR = b'O', NPY_STRINGLTR = b'S', NPY_STRINGLTR2 = b'a', NPY_UNICODELTR = b'U', NPY_VOIDLTR = b'V', NPY_DATETIMELTR = b'M', NPY_TIMEDELTALTR = b'm', NPY_CHARLTR = b'c', NPY_INTPLTR = b'p', NPY_UINTPLTR = b'P', } // Note: NPY_TYPEKINDCHAR doesn't exist in the header and has been created here artificially // because the original C enum contained duplicate values - namely, those related to type kinds. // There's also a comment in the C code preceding this block of values and stating that they are // related to type kind chars and not type codes. #[repr(u8)] #[derive(Debug, Clone, Copy)] pub enum NPY_TYPEKINDCHAR { NPY_GENBOOLLTR = b'b', NPY_SIGNEDLTR = b'i', NPY_UNSIGNEDLTR = b'u', NPY_FLOATINGLTR = b'f', NPY_COMPLEXLTR = b'c', } #[repr(u8)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum NPY_BYTEORDER_CHAR { NPY_LITTLE = b'<', NPY_BIG = b'>', NPY_NATIVE = b'=', NPY_SWAP = b's', NPY_IGNORE = b'|', } impl NPY_BYTEORDER_CHAR { #[cfg(target_endian = "little")] pub const NPY_NATBYTE: Self = Self::NPY_LITTLE; #[cfg(target_endian = "little")] pub const NPY_OPPBYTE: Self = Self::NPY_BIG; #[cfg(target_endian = "big")] pub const NPY_NATBYTE: Self = Self::NPY_BIG; #[cfg(target_endian = "big")] pub const NPY_OPPBYTE: Self = Self::NPY_LITTLE; } #[repr(i32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum NPY_ARRAYMETHOD_FLAGS { NPY_METH_REQUIRES_PYAPI = 1 << 0, NPY_METH_NO_FLOATINGPOINT_ERRORS = 1 << 1, NPY_METH_SUPPORTS_UNALIGNED = 1 << 2, NPY_METH_IS_REORDERABLE = 1 << 3, _NPY_METH_FORCE_CAST_INPUTS = 1 << 17, NPY_METH_RUNTIME_FLAGS = (1 << 0) | (1 << 1), // NPY_METH_REQUIRES_PYAPI | NPY_METH_NO_FLOATINGPOINT_ERRORS } numpy-0.27.1/src/npyffi/ufunc.rs000064400000000000000000000162201046102023000146340ustar 00000000000000//! Low-Level binding for [UFunc API](https://numpy.org/doc/stable/reference/c-api/ufunc.html) use std::os::raw::*; use pyo3::{ffi::PyObject, sync::PyOnceLock}; use crate::npyffi::*; fn mod_name(py: Python<'_>) -> PyResult<&'static str> { static MOD_NAME: PyOnceLock = PyOnceLock::new(); MOD_NAME .get_or_try_init(py, || { let numpy_core = super::array::numpy_core_name(py)?; Ok(format!("{numpy_core}.umath")) }) .map(String::as_str) } const CAPSULE_NAME: &str = "_UFUNC_API"; /// A global variable which stores a ['capsule'](https://docs.python.org/3/c-api/capsule.html) /// pointer to [Numpy UFunc API](https://numpy.org/doc/stable/reference/c-api/ufunc.html). pub static PY_UFUNC_API: PyUFuncAPI = PyUFuncAPI(PyOnceLock::new()); pub struct PyUFuncAPI(PyOnceLock<*const *const c_void>); unsafe impl Send for PyUFuncAPI {} unsafe impl Sync for PyUFuncAPI {} impl PyUFuncAPI { unsafe fn get<'py>(&self, py: Python<'py>, offset: isize) -> *const *const c_void { let api = self .0 .get_or_try_init(py, || get_numpy_api(py, mod_name(py)?, CAPSULE_NAME)) .expect("Failed to access NumPy ufunc API capsule"); api.offset(offset) } } impl PyUFuncAPI { impl_api![1; PyUFunc_FromFuncAndData(func: *mut PyUFuncGenericFunction, data: *mut *mut c_void, types: *mut c_char, ntypes: c_int, nin: c_int, nout: c_int, identity: c_int, name: *const c_char, doc: *const c_char, unused: c_int) -> *mut PyObject]; impl_api![2; PyUFunc_RegisterLoopForType(ufunc: *mut PyUFuncObject, usertype: c_int, function: PyUFuncGenericFunction, arg_types: *mut c_int, data: *mut c_void) -> c_int]; impl_api![3; PyUFunc_GenericFunction(ufunc: *mut PyUFuncObject, args: *mut PyObject, kwds: *mut PyObject, op: *mut *mut PyArrayObject) -> c_int]; impl_api![4; PyUFunc_f_f_As_d_d(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![5; PyUFunc_d_d(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![6; PyUFunc_f_f(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![7; PyUFunc_g_g(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![8; PyUFunc_F_F_As_D_D(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![9; PyUFunc_F_F(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![10; PyUFunc_D_D(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![11; PyUFunc_G_G(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![12; PyUFunc_O_O(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![13; PyUFunc_ff_f_As_dd_d(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![14; PyUFunc_ff_f(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![15; PyUFunc_dd_d(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![16; PyUFunc_gg_g(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![17; PyUFunc_FF_F_As_DD_D(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![18; PyUFunc_DD_D(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![19; PyUFunc_FF_F(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![20; PyUFunc_GG_G(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![21; PyUFunc_OO_O(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![22; PyUFunc_O_O_method(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![23; PyUFunc_OO_O_method(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![24; PyUFunc_On_Om(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![25; PyUFunc_GetPyValues(name: *mut c_char, bufsize: *mut c_int, errmask: *mut c_int, errobj: *mut *mut PyObject) -> c_int]; impl_api![26; PyUFunc_checkfperr(errmask: c_int, errobj: *mut PyObject, first: *mut c_int) -> c_int]; impl_api![27; PyUFunc_clearfperr()]; impl_api![28; PyUFunc_getfperr() -> c_int]; impl_api![29; PyUFunc_handlefperr(errmask: c_int, errobj: *mut PyObject, retstatus: c_int, first: *mut c_int) -> c_int]; impl_api![30; PyUFunc_ReplaceLoopBySignature(func: *mut PyUFuncObject, newfunc: PyUFuncGenericFunction, signature: *mut c_int, oldfunc: *mut PyUFuncGenericFunction) -> c_int]; impl_api![31; PyUFunc_FromFuncAndDataAndSignature(func: *mut PyUFuncGenericFunction, data: *mut *mut c_void, types: *mut c_char, ntypes: c_int, nin: c_int, nout: c_int, identity: c_int, name: *const c_char, doc: *const c_char, unused: c_int, signature: *const c_char) -> *mut PyObject]; impl_api![32; PyUFunc_SetUsesArraysAsData(data: *mut *mut c_void, i: usize) -> c_int]; impl_api![33; PyUFunc_e_e(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![34; PyUFunc_e_e_As_f_f(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![35; PyUFunc_e_e_As_d_d(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![36; PyUFunc_ee_e(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![37; PyUFunc_ee_e_As_ff_f(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![38; PyUFunc_ee_e_As_dd_d(args: *mut *mut c_char, dimensions: *mut npy_intp, steps: *mut npy_intp, func: *mut c_void)]; impl_api![39; PyUFunc_DefaultTypeResolver(ufunc: *mut PyUFuncObject, casting: NPY_CASTING, operands: *mut *mut PyArrayObject, type_tup: *mut PyObject, out_dtypes: *mut *mut PyArray_Descr) -> c_int]; impl_api![40; PyUFunc_ValidateCasting(ufunc: *mut PyUFuncObject, casting: NPY_CASTING, operands: *mut *mut PyArrayObject, dtypes: *mut *mut PyArray_Descr) -> c_int]; impl_api![41; PyUFunc_RegisterLoopForDescr(ufunc: *mut PyUFuncObject, user_dtype: *mut PyArray_Descr, function: PyUFuncGenericFunction, arg_dtypes: *mut *mut PyArray_Descr, data: *mut c_void) -> c_int]; impl_api![42; PyUFunc_FromFuncAndDataAndSignatureAndIdentity( ufunc: *mut PyUFuncObject, data: *mut *mut c_void, types: *mut c_char, ntypes: c_int, nin: c_int, nout: c_int, identity: c_int, name: *const c_char, doc: *const c_char, unused: c_int, signature: *const c_char, identity_value: *const c_char, ) -> c_int]; } numpy-0.27.1/src/slice_container.rs000064400000000000000000000051011046102023000153560ustar 00000000000000use std::{mem, ptr}; use ndarray::{ArrayBase, Dimension, OwnedRepr}; use pyo3::pyclass; /// Utility type to safely store `Box<[_]>` or `Vec<_>` on the Python heap #[pyclass(frozen)] #[derive(Debug)] pub(crate) struct PySliceContainer { pub(crate) ptr: *mut u8, pub(crate) len: usize, cap: usize, drop: unsafe fn(*mut u8, usize, usize), } // This resembles `unsafe impl Send for PySliceContainer {}` if we // were allow to use a generic there. // SAFETY: Every construction below enforces `T: Send` fulfilling the ideal bound above unsafe impl Send for PySliceContainer {} // This resembles `unsafe impl Sync for PySliceContainer {}` if we // were allow to use a generic there. // SAFETY: Every construction below enforces `T: Sync` fulfilling the ideal bound above unsafe impl Sync for PySliceContainer {} impl From> for PySliceContainer { fn from(data: Box<[T]>) -> Self { unsafe fn drop_boxed_slice(ptr: *mut u8, len: usize, _cap: usize) { let _ = Box::from_raw(ptr::slice_from_raw_parts_mut(ptr as *mut T, len)); } // FIXME(adamreichold): Use `Box::into_raw` when // `*mut [T]::{as_mut_ptr, len}` become stable and compatible with our MSRV. let mut data = mem::ManuallyDrop::new(data); let ptr = data.as_mut_ptr() as *mut u8; let len = data.len(); let cap = 0; let drop = drop_boxed_slice::; Self { ptr, len, cap, drop, } } } impl From> for PySliceContainer { fn from(data: Vec) -> Self { unsafe fn drop_vec(ptr: *mut u8, len: usize, cap: usize) { let _ = Vec::from_raw_parts(ptr as *mut T, len, cap); } // FIXME(adamreichold): Use `Vec::into_raw_parts` // when it becomes stable and compatible with our MSRV. let mut data = mem::ManuallyDrop::new(data); let ptr = data.as_mut_ptr() as *mut u8; let len = data.len(); let cap = data.capacity(); let drop = drop_vec::; Self { ptr, len, cap, drop, } } } impl From, D>> for PySliceContainer where A: Send + Sync, D: Dimension, { fn from(data: ArrayBase, D>) -> Self { #[allow(deprecated)] Self::from(data.into_raw_vec()) } } impl Drop for PySliceContainer { fn drop(&mut self) { unsafe { (self.drop)(self.ptr, self.len, self.cap); } } } numpy-0.27.1/src/strings.rs000064400000000000000000000154531046102023000137210ustar 00000000000000//! Types to support arrays of [ASCII][ascii] and [UCS4][ucs4] strings //! //! [ascii]: https://numpy.org/doc/stable/reference/c-api/dtype.html#c.NPY_STRING //! [ucs4]: https://numpy.org/doc/stable/reference/c-api/dtype.html#c.NPY_UNICODE use std::collections::hash_map::Entry; use std::fmt; use std::mem::size_of; use std::os::raw::c_char; use std::str; use std::sync::Mutex; use pyo3::sync::MutexExt; use pyo3::{ ffi::{Py_UCS1, Py_UCS4}, Bound, Py, Python, }; use rustc_hash::FxHashMap; use crate::dtype::{clone_methods_impl, Element, PyArrayDescr, PyArrayDescrMethods}; use crate::npyffi::PyDataType_SET_ELSIZE; use crate::npyffi::NPY_TYPES; /// A newtype wrapper around [`[u8; N]`][Py_UCS1] to handle [`byte` scalars][numpy-bytes] while satisfying coherence. /// /// Note that when creating arrays of ASCII strings without an explicit `dtype`, /// NumPy will automatically determine the smallest possible array length at runtime. /// /// For example, /// /// ```python /// array = numpy.array([b"foo", b"bar", b"foobar"]) /// ``` /// /// yields `S6` for `array.dtype`. /// /// On the Rust side however, the length `N` of `PyFixedString` must always be given /// explicitly and as a compile-time constant. For this work reliably, the Python code /// should set the `dtype` explicitly, e.g. /// /// ```python /// numpy.array([b"foo", b"bar", b"foobar"], dtype='S12') /// ``` /// /// always matching `PyArray1>`. /// /// # Example /// /// ```rust /// # use pyo3::Python; /// use numpy::{PyArray1, PyUntypedArrayMethods, PyFixedString}; /// /// # Python::attach(|py| { /// let array = PyArray1::>::from_vec(py, vec![[b'f', b'o', b'o'].into()]); /// /// assert!(array.dtype().to_string().contains("S3")); /// # }); /// ``` /// /// [numpy-bytes]: https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.bytes_ #[repr(transparent)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct PyFixedString(pub [Py_UCS1; N]); impl fmt::Display for PyFixedString { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.write_str(str::from_utf8(&self.0).unwrap().trim_end_matches('\0')) } } impl From<[Py_UCS1; N]> for PyFixedString { fn from(val: [Py_UCS1; N]) -> Self { Self(val) } } unsafe impl Element for PyFixedString { const IS_COPY: bool = true; fn get_dtype(py: Python<'_>) -> Bound<'_, PyArrayDescr> { static DTYPES: TypeDescriptors = TypeDescriptors::new(); unsafe { DTYPES.from_size(py, NPY_TYPES::NPY_STRING, b'|' as _, size_of::()) } } clone_methods_impl!(Self); } /// A newtype wrapper around [`[PyUCS4; N]`][Py_UCS4] to handle [`str_` scalars][numpy-str] while satisfying coherence. /// /// Note that when creating arrays of Unicode strings without an explicit `dtype`, /// NumPy will automatically determine the smallest possible array length at runtime. /// /// For example, /// /// ```python /// numpy.array(["foo🐍", "bar🦀", "foobar"]) /// ``` /// /// yields `U6` for `array.dtype`. /// /// On the Rust side however, the length `N` of `PyFixedUnicode` must always be given /// explicitly and as a compile-time constant. For this work reliably, the Python code /// should set the `dtype` explicitly, e.g. /// /// ```python /// numpy.array(["foo🐍", "bar🦀", "foobar"], dtype='U12') /// ``` /// /// always matching `PyArray1>`. /// /// # Example /// /// ```rust /// # use pyo3::Python; /// use numpy::{PyArray1, PyUntypedArrayMethods, PyFixedUnicode}; /// /// # Python::attach(|py| { /// let array = PyArray1::>::from_vec(py, vec![[b'b' as _, b'a' as _, b'r' as _].into()]); /// /// assert!(array.dtype().to_string().contains("U3")); /// # }); /// ``` /// /// [numpy-str]: https://numpy.org/doc/stable/reference/arrays.scalars.html#numpy.str_ #[repr(transparent)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct PyFixedUnicode(pub [Py_UCS4; N]); impl fmt::Display for PyFixedUnicode { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { for character in self.0 { if character == 0 { break; } write!(fmt, "{}", char::from_u32(character).unwrap())?; } Ok(()) } } impl From<[Py_UCS4; N]> for PyFixedUnicode { fn from(val: [Py_UCS4; N]) -> Self { Self(val) } } unsafe impl Element for PyFixedUnicode { const IS_COPY: bool = true; fn get_dtype(py: Python<'_>) -> Bound<'_, PyArrayDescr> { static DTYPES: TypeDescriptors = TypeDescriptors::new(); unsafe { DTYPES.from_size(py, NPY_TYPES::NPY_UNICODE, b'=' as _, size_of::()) } } clone_methods_impl!(Self); } struct TypeDescriptors { dtypes: Mutex>>>, } impl TypeDescriptors { const fn new() -> Self { Self { dtypes: Mutex::new(None), } } /// `npy_type` must be either `NPY_STRING` or `NPY_UNICODE` with matching `byteorder` and `size` #[allow(clippy::wrong_self_convention)] unsafe fn from_size<'py>( &self, py: Python<'py>, npy_type: NPY_TYPES, byteorder: c_char, size: usize, ) -> Bound<'py, PyArrayDescr> { let mut dtypes = self .dtypes .lock_py_attached(py) .expect("dtype cache poisoned"); let dtype = match dtypes.get_or_insert_with(Default::default).entry(size) { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => { let dtype = PyArrayDescr::new_from_npy_type(py, npy_type); let descr = &mut *dtype.as_dtype_ptr(); PyDataType_SET_ELSIZE(py, descr, size.try_into().unwrap()); descr.byteorder = byteorder; entry.insert(dtype.into()) } }; dtype.bind(py).to_owned() } } #[cfg(test)] mod tests { use super::*; #[test] fn format_fixed_string() { assert_eq!( PyFixedString([b'f', b'o', b'o', 0, 0, 0]).to_string(), "foo" ); assert_eq!( PyFixedString([b'f', b'o', b'o', b'b', b'a', b'r']).to_string(), "foobar" ); } #[test] fn format_fixed_unicode() { assert_eq!( PyFixedUnicode([b'f' as _, b'o' as _, b'o' as _, 0, 0, 0]).to_string(), "foo" ); assert_eq!( PyFixedUnicode([0x1F980, 0x1F40D, 0, 0, 0, 0]).to_string(), "🦀🐍" ); assert_eq!( PyFixedUnicode([b'f' as _, b'o' as _, b'o' as _, b'b' as _, b'a' as _, b'r' as _]) .to_string(), "foobar" ); } } numpy-0.27.1/src/sum_products.rs000064400000000000000000000131231046102023000147470ustar 00000000000000use std::borrow::Cow; use std::ffi::{CStr, CString}; use std::ptr::null_mut; use ndarray::{Dimension, IxDyn}; use pyo3::types::PyAnyMethods; use pyo3::{Borrowed, Bound, FromPyObject, PyResult}; use crate::array::PyArray; use crate::dtype::Element; use crate::npyffi::{array::PY_ARRAY_API, NPY_CASTING, NPY_ORDER}; /// Return value of a function that can yield either an array or a scalar. pub trait ArrayOrScalar<'a, 'py, T>: FromPyObject<'a, 'py> {} impl<'a, 'py, T, D> ArrayOrScalar<'a, 'py, T> for Bound<'py, PyArray> where T: Element + 'a, D: Dimension + 'a, { } impl<'a, 'py, T> ArrayOrScalar<'a, 'py, T> for T where T: Element + FromPyObject<'a, 'py> {} /// Return the inner product of two arrays. /// /// [NumPy's documentation][inner] has the details. /// /// # Examples /// /// Note that this function can either return a scalar... /// /// ``` /// use pyo3::Python; /// use numpy::{inner, pyarray, PyArray0}; /// /// Python::attach(|py| { /// let vector = pyarray![py, 1.0, 2.0, 3.0]; /// let result: f64 = inner(&vector, &vector).unwrap(); /// assert_eq!(result, 14.0); /// }); /// ``` /// /// ...or an array depending on its arguments. /// /// ``` /// use pyo3::{Python, Bound}; /// use numpy::prelude::*; /// use numpy::{inner, pyarray, PyArray0}; /// /// Python::attach(|py| { /// let vector = pyarray![py, 1, 2, 3]; /// let result: Bound<'_, PyArray0<_>> = inner(&vector, &vector).unwrap(); /// assert_eq!(result.item(), 14); /// }); /// ``` /// /// [inner]: https://numpy.org/doc/stable/reference/generated/numpy.inner.html pub fn inner<'py, T, DIN1, DIN2, OUT>( array1: &Bound<'py, PyArray>, array2: &Bound<'py, PyArray>, ) -> PyResult where T: Element, DIN1: Dimension, DIN2: Dimension, OUT: for<'a> ArrayOrScalar<'a, 'py, T>, { let py = array1.py(); let obj = unsafe { let result = PY_ARRAY_API.PyArray_InnerProduct(py, array1.as_ptr(), array2.as_ptr()); Bound::from_owned_ptr_or_err(py, result)? }; obj.extract().map_err(Into::into) } /// Return the dot product of two arrays. /// /// [NumPy's documentation][dot] has the details. /// /// # Examples /// /// Note that this function can either return an array... /// /// ``` /// use pyo3::{Python, Bound}; /// use ndarray::array; /// use numpy::{dot, pyarray, PyArray2, PyArrayMethods}; /// /// Python::attach(|py| { /// let matrix = pyarray![py, [1, 0], [0, 1]]; /// let another_matrix = pyarray![py, [4, 1], [2, 2]]; /// /// let result: Bound<'_, PyArray2<_>> = dot(&matrix, &another_matrix).unwrap(); /// /// assert_eq!( /// result.readonly().as_array(), /// array![[4, 1], [2, 2]] /// ); /// }); /// ``` /// /// ...or a scalar depending on its arguments. /// /// ``` /// use pyo3::Python; /// use numpy::{dot, pyarray, PyArray0}; /// /// Python::attach(|py| { /// let vector = pyarray![py, 1.0, 2.0, 3.0]; /// let result: f64 = dot(&vector, &vector).unwrap(); /// assert_eq!(result, 14.0); /// }); /// ``` /// /// [dot]: https://numpy.org/doc/stable/reference/generated/numpy.dot.html pub fn dot<'py, T, DIN1, DIN2, OUT>( array1: &Bound<'py, PyArray>, array2: &Bound<'py, PyArray>, ) -> PyResult where T: Element, DIN1: Dimension, DIN2: Dimension, OUT: for<'a> ArrayOrScalar<'a, 'py, T>, { let py = array1.py(); let obj = unsafe { let result = PY_ARRAY_API.PyArray_MatrixProduct(py, array1.as_ptr(), array2.as_ptr()); Bound::from_owned_ptr_or_err(py, result)? }; obj.extract().map_err(Into::into) } /// Return the Einstein summation convention of given tensors. /// /// This is usually invoked via the the [`einsum!`][crate::einsum!] macro. pub fn einsum<'py, T, OUT>( subscripts: &str, arrays: &[Borrowed<'_, 'py, PyArray>], ) -> PyResult where T: Element, OUT: for<'a> ArrayOrScalar<'a, 'py, T>, { let subscripts = match CStr::from_bytes_with_nul(subscripts.as_bytes()) { Ok(subscripts) => Cow::Borrowed(subscripts), Err(_) => Cow::Owned(CString::new(subscripts).unwrap()), }; let py = arrays[0].py(); let obj = unsafe { let result = PY_ARRAY_API.PyArray_EinsteinSum( py, subscripts.as_ptr() as _, arrays.len() as _, arrays.as_ptr() as _, null_mut(), NPY_ORDER::NPY_KEEPORDER, NPY_CASTING::NPY_NO_CASTING, null_mut(), ); Bound::from_owned_ptr_or_err(py, result)? }; obj.extract().map_err(Into::into) } /// Return the Einstein summation convention of given tensors. /// /// For more about the Einstein summation convention, please refer to /// [NumPy's documentation][einsum]. /// /// # Example /// /// ``` /// use pyo3::{Python, Bound}; /// use ndarray::array; /// use numpy::{einsum, pyarray, PyArray, PyArray2, PyArrayMethods}; /// /// Python::attach(|py| { /// let tensor = PyArray::arange(py, 0, 2 * 3 * 4, 1).reshape([2, 3, 4]).unwrap(); /// let another_tensor = pyarray![py, [20, 30], [40, 50], [60, 70]]; /// /// let result: Bound<'_, PyArray2<_>> = einsum!("ijk,ji->ik", tensor, another_tensor).unwrap(); /// /// assert_eq!( /// result.readonly().as_array(), /// array![[640, 760, 880, 1000], [2560, 2710, 2860, 3010]] /// ); /// }); /// ``` /// /// [einsum]: https://numpy.org/doc/stable/reference/generated/numpy.einsum.html #[macro_export] macro_rules! einsum { ($subscripts:literal $(,$array:ident)+ $(,)*) => {{ let arrays = [$($array.to_dyn().as_borrowed(),)+]; $crate::einsum(concat!($subscripts, "\0"), &arrays) }}; } numpy-0.27.1/src/untyped_array.rs000064400000000000000000000222141046102023000151070ustar 00000000000000//! Safe, untyped interface for NumPy's [N-dimensional arrays][ndarray] //! //! [ndarray]: https://numpy.org/doc/stable/reference/arrays.ndarray.html use std::slice; use pyo3::{ffi, pyobject_native_type_named, Bound, PyAny, PyTypeInfo, Python}; use crate::array::{PyArray, PyArrayMethods}; use crate::cold; use crate::dtype::PyArrayDescr; use crate::npyffi; /// A safe, untyped wrapper for NumPy's [`ndarray`] class. /// /// Unlike [`PyArray`][crate::PyArray], this type does not constrain either element type `T` nor the dimensionality `D`. /// This can be useful to inspect function arguments, but it prevents operating on the elements without further downcasts. /// /// When both element type `T` and index type `D` are known, these values can be downcast to `PyArray`. In addition, /// `PyArray` can be dereferenced to a `PyUntypedArray` and can therefore automatically access its methods. /// /// # Example /// /// Taking `PyUntypedArray` can be helpful to implement polymorphic entry points: /// /// ``` /// # use pyo3::prelude::*; /// use pyo3::exceptions::PyTypeError; /// use numpy::{Element, PyUntypedArray, PyArray1, dtype}; /// use numpy::{PyUntypedArrayMethods, PyArrayMethods, PyArrayDescrMethods}; /// /// #[pyfunction] /// fn entry_point(py: Python<'_>, array: &Bound<'_, PyUntypedArray>) -> PyResult<()> { /// fn implementation(array: &Bound<'_, PyArray1>) -> PyResult<()> { /// /* .. */ /// /// Ok(()) /// } /// /// let element_type = array.dtype(); /// /// if element_type.is_equiv_to(&dtype::(py)) { /// let array = array.cast::>()?; /// /// implementation(array) /// } else if element_type.is_equiv_to(&dtype::(py)) { /// let array = array.cast::>()?; /// /// implementation(array) /// } else { /// Err(PyTypeError::new_err(format!("Unsupported element type: {}", element_type))) /// } /// } /// # /// # Python::attach(|py| { /// # let array = PyArray1::::zeros(py, 42, false); /// # entry_point(py, array.as_untyped()) /// # }).unwrap(); /// ``` #[repr(transparent)] pub struct PyUntypedArray(PyAny); unsafe impl PyTypeInfo for PyUntypedArray { const NAME: &'static str = "PyUntypedArray"; const MODULE: Option<&'static str> = Some("numpy"); fn type_object_raw<'py>(py: Python<'py>) -> *mut ffi::PyTypeObject { unsafe { npyffi::PY_ARRAY_API.get_type_object(py, npyffi::NpyTypes::PyArray_Type) } } fn is_type_of(ob: &Bound<'_, PyAny>) -> bool { unsafe { npyffi::PyArray_Check(ob.py(), ob.as_ptr()) != 0 } } } pyobject_native_type_named!(PyUntypedArray); /// Implementation of functionality for [`PyUntypedArray`]. #[doc(alias = "PyUntypedArray")] pub trait PyUntypedArrayMethods<'py>: Sealed { /// Returns a raw pointer to the underlying [`PyArrayObject`][npyffi::PyArrayObject]. fn as_array_ptr(&self) -> *mut npyffi::PyArrayObject; /// Returns the `dtype` of the array. /// /// See also [`ndarray.dtype`][ndarray-dtype] and [`PyArray_DTYPE`][PyArray_DTYPE]. /// /// # Example /// /// ``` /// use numpy::prelude::*; /// use numpy::{dtype, PyArray}; /// use pyo3::Python; /// /// Python::attach(|py| { /// let array = PyArray::from_vec(py, vec![1_i32, 2, 3]); /// /// assert!(array.dtype().is_equiv_to(&dtype::(py))); /// }); /// ``` /// /// [ndarray-dtype]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.dtype.html /// [PyArray_DTYPE]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_DTYPE fn dtype(&self) -> Bound<'py, PyArrayDescr>; /// Returns `true` if the internal data of the array is contiguous, /// indepedently of whether C-style/row-major or Fortran-style/column-major. /// /// # Example /// /// ``` /// use numpy::{PyArray1, PyUntypedArrayMethods}; /// use pyo3::{types::{IntoPyDict, PyAnyMethods}, Python, ffi::c_str}; /// /// # fn main() -> pyo3::PyResult<()> { /// Python::attach(|py| { /// let array = PyArray1::arange(py, 0, 10, 1); /// assert!(array.is_contiguous()); /// /// let view = py /// .eval(c_str!("array[::2]"), None, Some(&[("array", array)].into_py_dict(py)?))? /// .cast_into::>()?; /// assert!(!view.is_contiguous()); /// # Ok(()) /// }) /// # } /// ``` fn is_contiguous(&self) -> bool { unsafe { check_flags( &*self.as_array_ptr(), npyffi::NPY_ARRAY_C_CONTIGUOUS | npyffi::NPY_ARRAY_F_CONTIGUOUS, ) } } /// Returns `true` if the internal data of the array is Fortran-style/column-major contiguous. fn is_fortran_contiguous(&self) -> bool { unsafe { check_flags(&*self.as_array_ptr(), npyffi::NPY_ARRAY_F_CONTIGUOUS) } } /// Returns `true` if the internal data of the array is C-style/row-major contiguous. fn is_c_contiguous(&self) -> bool { unsafe { check_flags(&*self.as_array_ptr(), npyffi::NPY_ARRAY_C_CONTIGUOUS) } } /// Returns the number of dimensions of the array. /// /// See also [`ndarray.ndim`][ndarray-ndim] and [`PyArray_NDIM`][PyArray_NDIM]. /// /// # Example /// /// ``` /// use numpy::{PyArray3, PyUntypedArrayMethods}; /// use pyo3::Python; /// /// Python::attach(|py| { /// let arr = PyArray3::::zeros(py, [4, 5, 6], false); /// /// assert_eq!(arr.ndim(), 3); /// }); /// ``` /// /// [ndarray-ndim]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.ndim.html /// [PyArray_NDIM]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_NDIM #[inline] fn ndim(&self) -> usize { unsafe { (*self.as_array_ptr()).nd as usize } } /// Returns a slice indicating how many bytes to advance when iterating along each axis. /// /// See also [`ndarray.strides`][ndarray-strides] and [`PyArray_STRIDES`][PyArray_STRIDES]. /// /// # Example /// /// ``` /// use numpy::{PyArray3, PyUntypedArrayMethods}; /// use pyo3::Python; /// /// Python::attach(|py| { /// let arr = PyArray3::::zeros(py, [4, 5, 6], false); /// /// assert_eq!(arr.strides(), &[240, 48, 8]); /// }); /// ``` /// [ndarray-strides]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.strides.html /// [PyArray_STRIDES]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_STRIDES #[inline] fn strides(&self) -> &[isize] { let n = self.ndim(); if n == 0 { cold(); return &[]; } let ptr = self.as_array_ptr(); unsafe { let p = (*ptr).strides; slice::from_raw_parts(p, n) } } /// Returns a slice which contains dimmensions of the array. /// /// See also [`ndarray.shape`][ndaray-shape] and [`PyArray_DIMS`][PyArray_DIMS]. /// /// # Example /// /// ``` /// use numpy::{PyArray3, PyUntypedArrayMethods}; /// use pyo3::Python; /// /// Python::attach(|py| { /// let arr = PyArray3::::zeros(py, [4, 5, 6], false); /// /// assert_eq!(arr.shape(), &[4, 5, 6]); /// }); /// ``` /// /// [ndarray-shape]: https://numpy.org/doc/stable/reference/generated/numpy.ndarray.shape.html /// [PyArray_DIMS]: https://numpy.org/doc/stable/reference/c-api/array.html#c.PyArray_DIMS #[inline] fn shape(&self) -> &[usize] { let n = self.ndim(); if n == 0 { cold(); return &[]; } let ptr = self.as_array_ptr(); unsafe { let p = (*ptr).dimensions as *mut usize; slice::from_raw_parts(p, n) } } /// Calculates the total number of elements in the array. fn len(&self) -> usize { self.shape().iter().product() } /// Returns `true` if the there are no elements in the array. fn is_empty(&self) -> bool { self.shape().contains(&0) } } mod sealed { pub trait Sealed {} } use sealed::Sealed; fn check_flags(obj: &npyffi::PyArrayObject, flags: i32) -> bool { obj.flags & flags != 0 } impl<'py> PyUntypedArrayMethods<'py> for Bound<'py, PyUntypedArray> { #[inline] fn as_array_ptr(&self) -> *mut npyffi::PyArrayObject { self.as_ptr().cast() } fn dtype(&self) -> Bound<'py, PyArrayDescr> { unsafe { let descr_ptr = (*self.as_array_ptr()).descr; Bound::from_borrowed_ptr(self.py(), descr_ptr.cast()).cast_into_unchecked() } } } impl Sealed for Bound<'_, PyUntypedArray> {} // We won't be able to provide a `Deref` impl from `Bound<'_, PyArray>` to // `Bound<'_, PyUntypedArray>`, so this seems to be the next best thing to do impl<'py, T, D> PyUntypedArrayMethods<'py> for Bound<'py, PyArray> { #[inline] fn as_array_ptr(&self) -> *mut npyffi::PyArrayObject { self.as_untyped().as_array_ptr() } #[inline] fn dtype(&self) -> Bound<'py, PyArrayDescr> { self.as_untyped().dtype() } } impl Sealed for Bound<'_, PyArray> {} numpy-0.27.1/tests/array.rs000064400000000000000000000451271046102023000137220ustar 00000000000000use std::mem::size_of; #[cfg(feature = "half")] use half::{bf16, f16}; use ndarray::{array, s, Array1, Dim}; use numpy::prelude::*; use numpy::{ dtype, get_array_module, npyffi::NPY_ORDER, pyarray, PyArray, PyArray1, PyArray2, PyArrayDescr, PyFixedString, PyFixedUnicode, }; use pyo3::ffi::c_str; use pyo3::{ py_run, pyclass, pymethods, types::{IntoPyDict, PyAnyMethods, PyDict, PyList}, Bound, Py, Python, }; fn get_np_locals(py: Python<'_>) -> Bound<'_, PyDict> { [("np", get_array_module(py).unwrap())] .into_py_dict(py) .unwrap() } fn not_contiguous_array(py: Python<'_>) -> Bound<'_, PyArray1> { py.eval( c_str!("np.array([1, 2, 3, 4], dtype='int32')[::2]"), None, Some(&get_np_locals(py)), ) .unwrap() .cast_into() .unwrap() } #[test] fn new_c_order() { Python::attach(|py| { let dims = [3, 5]; let arr = PyArray::::zeros(py, dims, false); assert!(arr.ndim() == 2); assert!(arr.dims() == dims); let size = size_of::() as isize; assert!(arr.strides() == [dims[1] as isize * size, size]); assert_eq!(arr.shape(), &dims); assert_eq!(arr.len(), 3 * 5); assert!(arr.is_contiguous()); assert!(arr.is_c_contiguous()); assert!(!arr.is_fortran_contiguous()); }); } #[test] fn new_fortran_order() { Python::attach(|py| { let dims = [3, 5]; let arr = PyArray::::zeros(py, dims, true); assert!(arr.ndim() == 2); assert!(arr.dims() == dims); let size = size_of::() as isize; assert!(arr.strides() == [size, dims[0] as isize * size]); assert_eq!(arr.shape(), &dims); assert_eq!(arr.len(), 3 * 5); assert!(arr.is_contiguous()); assert!(!arr.is_c_contiguous()); assert!(arr.is_fortran_contiguous()); }); } #[test] fn tuple_as_dim() { Python::attach(|py| { let dims = (3, 5); let arr = PyArray::::zeros(py, dims, false); assert!(arr.ndim() == 2); assert!(arr.dims() == [3, 5]); }); } #[test] fn rank_zero_array_has_invalid_strides_dimensions() { Python::attach(|py| { let arr = PyArray::::zeros(py, (), false); assert_eq!(arr.ndim(), 0); assert_eq!(arr.strides(), &[] as &[isize]); assert_eq!(arr.shape(), &[] as &[usize]); assert_eq!(arr.len(), 1); assert!(!arr.is_empty()); assert_eq!(arr.item(), 0.0); }) } #[test] fn zeros() { Python::attach(|py| { let dims = [3, 4]; let arr = PyArray::::zeros(py, dims, false); assert!(arr.ndim() == 2); assert!(arr.dims() == dims); let size = size_of::() as isize; assert!(arr.strides() == [dims[1] as isize * size, size]); let arr = PyArray::::zeros(py, dims, true); assert!(arr.ndim() == 2); assert!(arr.dims() == dims); let size = size_of::() as isize; assert!(arr.strides() == [size, dims[0] as isize * size]); }); } #[test] fn arange() { Python::attach(|py| { let arr = PyArray::::arange(py, 0.0, 1.0, 0.1); assert_eq!(arr.ndim(), 1); assert_eq!(arr.dims(), Dim([10])); }); } #[test] fn as_array() { Python::attach(|py| { let pyarr = PyArray::::zeros(py, [3, 2, 4], false).readonly(); let arr = pyarr.as_array(); assert_eq!(pyarr.shape(), arr.shape()); assert_eq!( pyarr .strides() .iter() .map(|x| x / size_of::() as isize) .collect::>(), arr.strides() ); let not_contiguous = not_contiguous_array(py).readonly(); assert_eq!(not_contiguous.as_array(), array![1, 3]); }); } #[test] fn as_raw_array() { Python::attach(|py| { let not_contiguous = not_contiguous_array(py); let raw_array_view = not_contiguous.as_raw_array(); assert_eq!(unsafe { raw_array_view.deref_into_view()[0] }, 1); let raw_array_view_mut = not_contiguous.as_raw_array_mut(); assert_eq!(unsafe { raw_array_view_mut.deref_into_view_mut()[1] }, 3); }); } #[test] fn as_slice() { Python::attach(|py| { let arr = PyArray::::zeros(py, [3, 2, 4], false); assert_eq!(arr.readonly().as_slice().unwrap().len(), 3 * 2 * 4); let not_contiguous = not_contiguous_array(py); let err = not_contiguous.readonly().as_slice().unwrap_err(); assert_eq!(err.to_string(), "The given array is not contiguous"); }); } #[test] fn is_instance() { Python::attach(|py| { let arr = PyArray2::::zeros(py, [3, 5], false); assert!(arr.is_instance_of::>()); assert!(!arr.is_instance_of::()); }); } #[test] fn from_vec2() { Python::attach(|py| { let pyarray = PyArray::from_vec2(py, &[vec![1, 2, 3], vec![4, 5, 6]]).unwrap(); assert_eq!(pyarray.readonly().as_array(), array![[1, 2, 3], [4, 5, 6]]); }); } #[test] fn from_vec2_ragged() { Python::attach(|py| { let pyarray = PyArray::from_vec2(py, &[vec![1, 2, 3], vec![4, 5]]); let err = pyarray.unwrap_err(); assert_eq!(err.to_string(), "invalid length: 2, but expected 3"); }); } #[test] fn from_vec3() { Python::attach(|py| { let pyarray = PyArray::from_vec3( py, &[ vec![vec![1, 2], vec![3, 4]], vec![vec![5, 6], vec![7, 8]], vec![vec![9, 10], vec![11, 12]], ], ) .unwrap(); assert_eq!( pyarray.readonly().as_array(), array![[[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]] ); }); } #[test] fn from_vec3_ragged() { Python::attach(|py| { let pyarray = PyArray::from_vec3( py, &[ vec![vec![1, 2], vec![3, 4]], vec![vec![5, 6], vec![7, 8]], vec![vec![9, 10], vec![11]], ], ); let err = pyarray.unwrap_err(); assert_eq!(err.to_string(), "invalid length: 1, but expected 2"); let pyarray = PyArray::from_vec3( py, &[ vec![vec![1, 2], vec![3, 4]], vec![vec![5, 6], vec![7, 8]], vec![vec![9, 10]], ], ); let err = pyarray.unwrap_err(); assert_eq!(err.to_string(), "invalid length: 1, but expected 2"); }); } #[test] fn array_cast() { Python::attach(|py| { let arr_f64 = pyarray![py, [1.5, 2.5, 3.5], [1.5, 2.5, 3.5]]; let arr_i32 = arr_f64.cast_array::(false).unwrap(); assert_eq!(arr_i32.readonly().as_array(), array![[1, 2, 3], [1, 2, 3]]); }); } #[test] fn handle_negative_strides() { Python::attach(|py| { let arr = array![[2, 3], [4, 5u32]]; let pyarr = arr.to_pyarray(py); let neg_str_pyarr = py .eval( c_str!("a[::-1]"), Some(&[("a", pyarr)].into_py_dict(py).unwrap()), None, ) .unwrap() .cast_into::>() .unwrap(); assert_eq!( neg_str_pyarr.readonly().as_array(), arr.slice(s![..;-1, ..]) ); }); } #[test] fn dtype_via_python_attribute() { Python::attach(|py| { let arr = array![[2, 3], [4, 5u32]]; let pyarr = arr.to_pyarray(py); let dt = py .eval( c_str!("a.dtype"), Some(&[("a", pyarr)].into_py_dict(py).unwrap()), None, ) .unwrap() .cast_into::() .unwrap(); assert!(dt.is_equiv_to(&dtype::(py))); }); } #[pyclass] struct Owner { array: Array1, } #[pymethods] impl Owner { #[getter] fn array(this: Bound<'_, Self>) -> Bound<'_, PyArray1> { let array = &this.borrow().array; unsafe { PyArray1::borrow_from_array(array, this.into_any()) } } } #[test] fn borrow_from_array_works() { let array = Python::attach(|py| { let owner = Py::new( py, Owner { array: Array1::linspace(0., 1., 10), }, ) .unwrap(); owner.getattr(py, "array").unwrap() }); Python::attach(|py| { py_run!(py, array, "assert array.shape == (10,)"); }); } #[test] fn casting_works() { Python::attach(|py| { let ob = PyArray::from_slice(py, &[1_i32, 2, 3]).into_any(); assert!(ob.cast::>().is_ok()); }); } #[test] fn casting_respects_element_type() { Python::attach(|py| { let ob = PyArray::from_slice(py, &[1_i32, 2, 3]).into_any(); assert!(ob.cast::>().is_err()); }); } #[test] fn casting_respects_dimensionality() { Python::attach(|py| { let ob = PyArray::from_slice(py, &[1_i32, 2, 3]).into_any(); assert!(ob.cast::>().is_err()); }); } #[test] fn unbind_works() { let arr: Py> = Python::attach(|py| { let arr = PyArray::from_slice(py, &[1_i32, 2, 3]); arr.unbind() }); Python::attach(|py| { let arr = arr.bind(py); assert_eq!(arr.readonly().as_slice().unwrap(), &[1, 2, 3]); }); } #[test] fn copy_to_works() { Python::attach(|py| { let arr1 = PyArray::arange(py, 2.0, 5.0, 1.0); let arr2 = unsafe { PyArray::::new(py, [3], false) }; arr1.copy_to(&arr2).unwrap(); assert_eq!(arr2.readonly().as_slice().unwrap(), &[2, 3, 4]); }); } #[test] fn get_works() { Python::attach(|py| { let array = pyarray![py, [[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]]; unsafe { assert_eq!(array.get([0, 0, 0]), Some(&1)); assert_eq!(array.get([2, 1, 1]), Some(&12)); assert_eq!(array.get([0, 0, 2]), None); assert_eq!(array.get([0, 2, 0]), None); assert_eq!(array.get([3, 0, 0]), None); assert_eq!(*array.uget([1, 0, 0]), 5); assert_eq!(*array.uget_mut([0, 1, 0]), 3); assert_eq!(*array.uget_raw([0, 0, 1]), 2); } }); } #[test] fn permute_and_transpose() { Python::attach(|py| { let array = array![[0, 1, 2], [3, 4, 5]].into_pyarray(py); let permuted = array.permute(Some([1, 0])).unwrap(); assert_eq!( permuted.readonly().as_array(), array![[0, 3], [1, 4], [2, 5]] ); let permuted = array.permute::<()>(None).unwrap(); assert_eq!( permuted.readonly().as_array(), array![[0, 3], [1, 4], [2, 5]] ); let transposed = array.transpose().unwrap(); assert_eq!( transposed.readonly().as_array(), array![[0, 3], [1, 4], [2, 5]] ); let array = pyarray![py, [[1, 2], [3, 4]], [[5, 6], [7, 8]], [[9, 10], [11, 12]]]; let permuted = array.permute(Some([0, 2, 1])).unwrap(); assert_eq!( permuted.readonly().as_array(), array![[[1, 3], [2, 4]], [[5, 7], [6, 8]], [[9, 11], [10, 12]]] ); }); } #[test] fn reshape() { Python::attach(|py| { let array = PyArray::from_iter(py, 0..9) .reshape_with_order([3, 3], NPY_ORDER::NPY_FORTRANORDER) .unwrap(); assert_eq!( array.readonly().as_array(), array![[0, 3, 6], [1, 4, 7], [2, 5, 8]] ); assert!(array.is_fortran_contiguous()); assert!(array.reshape([5]).is_err()); }); } #[cfg(feature = "half")] #[test] fn half_f16_works() { Python::attach(|py| { let np = py.eval(c_str!("__import__('numpy')"), None, None).unwrap(); let locals = [("np", &np)].into_py_dict(py).unwrap(); let array = py .eval( c_str!("np.array([[1, 2], [3, 4]], dtype='float16')"), None, Some(&locals), ) .unwrap() .cast_into::>() .unwrap(); assert_eq!( array.readonly().as_array(), array![ [f16::from_f32(1.0), f16::from_f32(2.0)], [f16::from_f32(3.0), f16::from_f32(4.0)] ] ); array .readwrite() .as_array_mut() .map_inplace(|value| *value *= f16::from_f32(2.0)); py_run!( py, array np, "assert np.all(array == np.array([[2, 4], [6, 8]], dtype='float16'))" ); }); } #[cfg(feature = "half")] #[test] fn half_bf16_works() { Python::attach(|py| { let np = py.eval(c_str!("__import__('numpy')"), None, None).unwrap(); // NumPy itself does not provide a `bfloat16` dtype itself, // so we import ml_dtypes which does register such a dtype. let mldt = py .eval(c_str!("__import__('ml_dtypes')"), None, None) .unwrap(); let locals = [("np", &np), ("mldt", &mldt)].into_py_dict(py).unwrap(); let array = py .eval( c_str!("np.array([[1, 2], [3, 4]], dtype='bfloat16')"), None, Some(&locals), ) .unwrap() .cast_into::>() .unwrap(); assert_eq!( array.readonly().as_array(), array![ [bf16::from_f32(1.0), bf16::from_f32(2.0)], [bf16::from_f32(3.0), bf16::from_f32(4.0)] ] ); array .readwrite() .as_array_mut() .map_inplace(|value| *value *= bf16::from_f32(2.0)); py_run!( py, array np, "assert np.all(array == np.array([[2, 4], [6, 8]], dtype='bfloat16'))" ); }); } #[test] fn ascii_strings_with_explicit_dtype_works() { Python::attach(|py| { let np = py.eval(c_str!("__import__('numpy')"), None, None).unwrap(); let locals = [("np", &np)].into_py_dict(py).unwrap(); let array = py .eval( c_str!("np.array([b'foo', b'bar', b'foobar'], dtype='S6')"), None, Some(&locals), ) .unwrap() .cast_into::>>() .unwrap(); { let array = array.readonly(); let array = array.as_array(); assert_eq!(array[0].0, [b'f', b'o', b'o', 0, 0, 0]); assert_eq!(array[1].0, [b'b', b'a', b'r', 0, 0, 0]); assert_eq!(array[2].0, [b'f', b'o', b'o', b'b', b'a', b'r']); } { let mut array = array.readwrite(); let mut array = array.as_array_mut(); array[2].0[5] = b'z'; } py_run!(py, array np, "assert array[2] == b'foobaz'"); }); } #[test] fn unicode_strings_with_explicit_dtype_works() { Python::attach(|py| { let np = py.eval(c_str!("__import__('numpy')"), None, None).unwrap(); let locals = [("np", &np)].into_py_dict(py).unwrap(); let array = py .eval( c_str!("np.array(['foo', 'bar', 'foobar'], dtype='U6')"), None, Some(&locals), ) .unwrap() .cast_into::>>() .unwrap(); { let array = array.readonly(); let array = array.as_array(); assert_eq!(array[0].0, [b'f' as _, b'o' as _, b'o' as _, 0, 0, 0]); assert_eq!(array[1].0, [b'b' as _, b'a' as _, b'r' as _, 0, 0, 0]); assert_eq!( array[2].0, [ b'f' as u32, b'o' as _, b'o' as _, b'b' as _, b'a' as _, b'r' as _ ] ); } { let mut array = array.readwrite(); let mut array = array.as_array_mut(); array[2].0[5] = b'z' as _; } py_run!(py, array np, "assert array[2] == 'foobaz'"); }); } #[test] fn ascii_strings_ignore_byteorder() { Python::attach(|py| { let np = py.eval(c_str!("__import__('numpy')"), None, None).unwrap(); let locals = [("np", &np)].into_py_dict(py).unwrap(); let native_endian_works = py .eval( c_str!("np.array([b'foo', b'bar'], dtype='=S3')"), None, Some(&locals), ) .unwrap() .cast::>>() .is_ok(); let little_endian_works = py .eval( c_str!("np.array(['bfoo', b'bar'], dtype='>>() .is_ok(); let big_endian_works = py .eval( c_str!("np.array([b'foo', b'bar'], dtype='>S3')"), None, Some(&locals), ) .unwrap() .cast::>>() .is_ok(); match (native_endian_works, little_endian_works, big_endian_works) { (true, true, true) => (), _ => panic!("All byteorders should work",), } }); } #[test] fn unicode_strings_respect_byteorder() { Python::attach(|py| { let np = py.eval(c_str!("__import__('numpy')"), None, None).unwrap(); let locals = [("np", &np)].into_py_dict(py).unwrap(); let native_endian_works = py .eval( c_str!("np.array(['foo', 'bar'], dtype='=U3')"), None, Some(&locals), ) .unwrap() .cast::>>() .is_ok(); let little_endian_works = py .eval( c_str!("np.array(['foo', 'bar'], dtype='>>() .is_ok(); let big_endian_works = py .eval( c_str!("np.array(['foo', 'bar'], dtype='>U3')"), None, Some(&locals), ) .unwrap() .cast::>>() .is_ok(); match (native_endian_works, little_endian_works, big_endian_works) { (true, true, false) | (true, false, true) => (), _ => panic!("Only native byteorder should work"), } }); } numpy-0.27.1/tests/array_like.rs000064400000000000000000000102211046102023000147110ustar 00000000000000use ndarray::array; use numpy::{get_array_module, AllowTypeChange, PyArrayLike1, PyArrayLike2, PyArrayLikeDyn}; use pyo3::{ ffi::c_str, types::{IntoPyDict, PyAnyMethods, PyDict}, Bound, Python, }; fn get_np_locals(py: Python<'_>) -> Bound<'_, PyDict> { [("np", get_array_module(py).unwrap())] .into_py_dict(py) .unwrap() } #[test] fn extract_reference() { Python::attach(|py| { let locals = get_np_locals(py); let py_array = py .eval( c_str!("np.array([[1,2],[3,4]], dtype='float64')"), Some(&locals), None, ) .unwrap(); let extracted_array = py_array.extract::>().unwrap(); assert_eq!( array![[1_f64, 2_f64], [3_f64, 4_f64]], extracted_array.as_array() ); }); } #[test] fn convert_array_on_extract() { Python::attach(|py| { let locals = get_np_locals(py); let py_array = py .eval( c_str!("np.array([[1,2],[3,4]], dtype='int32')"), Some(&locals), None, ) .unwrap(); let extracted_array = py_array .extract::>() .unwrap(); assert_eq!( array![[1_f64, 2_f64], [3_f64, 4_f64]], extracted_array.as_array() ); }); } #[test] fn convert_list_on_extract() { Python::attach(|py| { let py_list = py .eval(c_str!("[[1.0,2.0],[3.0,4.0]]"), None, None) .unwrap(); let extracted_array = py_list.extract::>().unwrap(); assert_eq!(array![[1.0, 2.0], [3.0, 4.0]], extracted_array.as_array()); }); } #[test] fn convert_array_in_list_on_extract() { Python::attach(|py| { let locals = get_np_locals(py); let py_array = py .eval( c_str!("[np.array([1.0, 2.0]), [3.0, 4.0]]"), Some(&locals), None, ) .unwrap(); let extracted_array = py_array.extract::>().unwrap(); assert_eq!(array![[1.0, 2.0], [3.0, 4.0]], extracted_array.as_array()); }); } #[test] fn convert_list_on_extract_dyn() { Python::attach(|py| { let py_list = py .eval(c_str!("[[[1,2],[3,4]],[[5,6],[7,8]]]"), None, None) .unwrap(); let extracted_array = py_list .extract::>() .unwrap(); assert_eq!( array![[[1, 2], [3, 4]], [[5, 6], [7, 8]]].into_dyn(), extracted_array.as_array() ); }); } #[test] fn convert_1d_list_on_extract() { Python::attach(|py| { let py_list = py.eval(c_str!("[1,2,3,4]"), None, None).unwrap(); let extracted_array_1d = py_list.extract::>().unwrap(); let extracted_array_dyn = py_list.extract::>().unwrap(); assert_eq!(array![1, 2, 3, 4], extracted_array_1d.as_array()); assert_eq!( array![1_f64, 2_f64, 3_f64, 4_f64].into_dyn(), extracted_array_dyn.as_array() ); }); } #[test] fn unsafe_cast_shall_fail() { Python::attach(|py| { let locals = get_np_locals(py); let py_list = py .eval( c_str!("np.array([1.1,2.2,3.3,4.4], dtype='float64')"), Some(&locals), None, ) .unwrap(); let extracted_array = py_list.extract::>(); assert!(extracted_array.is_err()); }); } #[test] fn unsafe_cast_with_coerce_works() { Python::attach(|py| { let locals = get_np_locals(py); let py_list = py .eval( c_str!("np.array([1.1,2.2,3.3,4.4], dtype='float64')"), Some(&locals), None, ) .unwrap(); let extracted_array = py_list .extract::>() .unwrap(); assert_eq!(array![1, 2, 3, 4], extracted_array.as_array()); }); } numpy-0.27.1/tests/borrow.rs000064400000000000000000000376111046102023000141150ustar 00000000000000use std::thread::spawn; use numpy::{ array::PyArrayMethods, npyffi::NPY_ARRAY_WRITEABLE, PyArray, PyArray1, PyArray2, PyReadonlyArray3, PyReadwriteArray3, PyUntypedArrayMethods, }; use pyo3::{ ffi::c_str, py_run, pyclass, pymethods, types::{IntoPyDict, PyAnyMethods}, Py, Python, }; #[test] fn distinct_borrows() { Python::attach(|py| { let array1 = PyArray::::zeros(py, (1, 2, 3), false); let array2 = PyArray::::zeros(py, (1, 2, 3), false); let exclusive1 = array1.readwrite(); let exclusive2 = array2.readwrite(); assert_eq!(exclusive2.shape(), [1, 2, 3]); assert_eq!(exclusive1.shape(), [1, 2, 3]); }); } #[test] fn multiple_shared_borrows() { Python::attach(|py| { let array = PyArray::::zeros(py, (1, 2, 3), false); let shared1 = array.readonly(); let shared2 = array.readonly(); assert_eq!(shared2.shape(), [1, 2, 3]); assert_eq!(shared1.shape(), [1, 2, 3]); }); } #[test] #[should_panic(expected = "AlreadyBorrowed")] fn exclusive_and_shared_borrows() { Python::attach(|py| { let array = PyArray::::zeros(py, (1, 2, 3), false); let _exclusive = array.readwrite(); let _shared = array.readonly(); }); } #[test] #[should_panic(expected = "AlreadyBorrowed")] fn shared_and_exclusive_borrows() { Python::attach(|py| { let array = PyArray::::zeros(py, (1, 2, 3), false); let _shared = array.readonly(); let _exclusive = array.readwrite(); }); } #[test] fn multiple_exclusive_borrows() { Python::attach(|py| { let array = PyArray::::zeros(py, (1, 2, 3), false); let _exclusive = array.try_readwrite().unwrap(); let err = array.try_readwrite().unwrap_err(); assert_eq!(err.to_string(), "The given array is already borrowed"); }); } #[test] fn exclusive_borrow_requires_writeable() { Python::attach(|py| { let array = PyArray::::zeros(py, (1, 2, 3), false); unsafe { (*array.as_array_ptr()).flags &= !NPY_ARRAY_WRITEABLE; } let err = array.try_readwrite().unwrap_err(); assert_eq!(err.to_string(), "The given array is not writeable"); }); } #[pyclass] struct Borrower; #[pymethods] impl Borrower { fn shared(&self, _array: PyReadonlyArray3<'_, f64>) {} fn exclusive(&self, _array: PyReadwriteArray3<'_, f64>) {} } #[test] #[should_panic(expected = "AlreadyBorrowed")] fn borrows_span_frames() { Python::attach(|py| { let borrower = Py::new(py, Borrower).unwrap(); let array = PyArray::::zeros(py, (1, 2, 3), false); let _exclusive = array.readwrite(); py_run!(py, borrower array, "borrower.exclusive(array)"); }); } #[test] fn borrows_span_threads() { Python::attach(|py| { let array = PyArray::::zeros(py, (1, 2, 3), false); let _exclusive = array.readwrite(); let array = array.unbind(); py.detach(move || { let thread = spawn(move || { Python::attach(|py| { let array = array.bind(py); let _exclusive = array.readwrite(); }); }); assert!(thread.join().is_err()); }); }); } #[test] fn shared_borrows_can_be_cloned() { Python::attach(|py| { let array = PyArray::::zeros(py, (1, 2, 3), false); let shared1 = array.readonly(); let shared2 = shared1.clone(); assert_eq!(shared2.shape(), [1, 2, 3]); assert_eq!(shared1.shape(), [1, 2, 3]); }); } #[test] #[should_panic(expected = "AlreadyBorrowed")] fn overlapping_views_conflict() { Python::attach(|py| { let array = PyArray::::zeros(py, (1, 2, 3), false); let locals = [("array", array)].into_py_dict(py).unwrap(); let view1 = py .eval(c_str!("array[0,0,0:2]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); assert_eq!(view1.shape(), [2]); let view2 = py .eval(c_str!("array[0,0,1:3]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); assert_eq!(view2.shape(), [2]); let _exclusive1 = view1.readwrite(); let _exclusive2 = view2.readwrite(); }); } #[test] fn non_overlapping_views_do_not_conflict() { Python::attach(|py| { let array = PyArray::::zeros(py, (1, 2, 3), false); let locals = [("array", array)].into_py_dict(py).unwrap(); let view1 = py .eval(c_str!("array[0,0,0:1]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); assert_eq!(view1.shape(), [1]); let view2 = py .eval(c_str!("array[0,0,2:3]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); assert_eq!(view2.shape(), [1]); let exclusive1 = view1.readwrite(); let exclusive2 = view2.readwrite(); assert_eq!(exclusive2.len(), 1); assert_eq!(exclusive1.len(), 1); }); } #[test] #[should_panic(expected = "AlreadyBorrowed")] fn conflict_due_to_overlapping_views() { Python::attach(|py| { let array = PyArray::::zeros(py, 3, false); let locals = [("array", array)].into_py_dict(py).unwrap(); let view1 = py .eval(c_str!("array[0:2]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); assert_eq!(view1.shape(), [2]); let view2 = py .eval(c_str!("array[1:3]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); assert_eq!(view2.shape(), [2]); let _exclusive1 = view1.readwrite(); let _shared2 = view2.readonly(); }); } #[test] #[should_panic(expected = "AlreadyBorrowed")] fn conflict_due_to_reborrow_of_overlapping_views() { Python::attach(|py| { let array = PyArray::::zeros(py, 3, false); let locals = [("array", array)].into_py_dict(py).unwrap(); let view1 = py .eval(c_str!("array[0:2]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); assert_eq!(view1.shape(), [2]); let view2 = py .eval(c_str!("array[1:3]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); assert_eq!(view2.shape(), [2]); let shared1 = view1.readonly(); let _shared2 = view2.readonly(); drop(shared1); let _exclusive1 = view1.readwrite(); }); } #[test] fn interleaved_views_do_not_conflict() { Python::attach(|py| { let array = PyArray::::zeros(py, (23, 42, 3), false); let locals = [("array", array)].into_py_dict(py).unwrap(); let view1 = py .eval(c_str!("array[:,:,0]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); assert_eq!(view1.shape(), [23, 42]); let view2 = py .eval(c_str!("array[:,:,1]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); assert_eq!(view2.shape(), [23, 42]); let view3 = py .eval(c_str!("array[:,:,2]"), None, Some(&locals)) .unwrap() .cast_into::>() .unwrap(); assert_eq!(view2.shape(), [23, 42]); let exclusive1 = view1.readwrite(); let exclusive2 = view2.readwrite(); let exclusive3 = view3.readwrite(); assert_eq!(exclusive3.len(), 23 * 42); assert_eq!(exclusive2.len(), 23 * 42); assert_eq!(exclusive1.len(), 23 * 42); }); } #[test] fn extract_readonly() { Python::attach(|py| { let ob = PyArray::::zeros(py, (1, 2, 3), false).into_any(); ob.extract::>().unwrap(); }); } #[test] fn extract_readwrite() { Python::attach(|py| { let ob = PyArray::::zeros(py, (1, 2, 3), false).into_any(); ob.extract::>().unwrap(); }); } #[test] fn readonly_as_array_slice_get() { Python::attach(|py| { let array = PyArray::::zeros(py, (1, 2, 3), false); let array = array.readonly(); assert_eq!(array.as_array().shape(), [1, 2, 3]); assert_eq!(array.as_slice().unwrap().len(), 2 * 3); assert_eq!(*array.get([0, 1, 2]).unwrap(), 0.0); }); } #[test] fn readwrite_as_array_slice() { Python::attach(|py| { let array = PyArray::::zeros(py, (1, 2, 3), false); let mut array = array.readwrite(); assert_eq!(array.as_array().shape(), [1, 2, 3]); assert_eq!(array.as_array_mut().shape(), [1, 2, 3]); assert_eq!(*array.get([0, 1, 2]).unwrap(), 0.0); assert_eq!(array.as_slice().unwrap().len(), 2 * 3); assert_eq!(array.as_slice_mut().unwrap().len(), 2 * 3); assert_eq!(*array.get_mut([0, 1, 2]).unwrap(), 0.0); }); } #[test] fn resize_using_exclusive_borrow() { Python::attach(|py| { let array = PyArray::::zeros(py, 3, false); assert_eq!(array.shape(), [3]); let mut array = array.readwrite(); assert_eq!(array.as_slice_mut().unwrap(), &[0.0; 3]); let mut array = array.resize(5).unwrap(); assert_eq!(array.as_slice_mut().unwrap(), &[0.0; 5]); }); } #[test] fn can_make_python_array_nonwriteable() { Python::attach(|py| { let array = PyArray1::::zeros(py, 10, false); let locals = [("array", &array)].into_py_dict(py).unwrap(); array.readwrite().make_nonwriteable(); assert!(!py .eval( pyo3::ffi::c_str!("array.flags.writeable"), None, Some(&locals) ) .unwrap() .extract::() .unwrap()) }) } #[cfg(feature = "nalgebra")] #[test] fn matrix_from_numpy() { Python::attach(|py| { let array = numpy::pyarray![py, [0, 1, 2], [3, 4, 5], [6, 7, 8]]; { let array = array.readonly(); let matrix = array.as_matrix(); assert_eq!(matrix, nalgebra::Matrix3::new(0, 1, 2, 3, 4, 5, 6, 7, 8)); let matrix: nalgebra::MatrixView< '_, i32, nalgebra::Const<3>, nalgebra::Const<3>, nalgebra::Const<3>, nalgebra::Const<1>, > = array.try_as_matrix().unwrap(); assert_eq!(matrix, nalgebra::Matrix3::new(0, 1, 2, 3, 4, 5, 6, 7, 8)); } { let array = array.readwrite(); let matrix = array.as_matrix_mut(); assert_eq!(matrix, nalgebra::Matrix3::new(0, 1, 2, 3, 4, 5, 6, 7, 8)); let matrix: nalgebra::MatrixViewMut< '_, i32, nalgebra::Const<3>, nalgebra::Const<3>, nalgebra::Const<3>, nalgebra::Const<1>, > = array.try_as_matrix_mut().unwrap(); assert_eq!(matrix, nalgebra::Matrix3::new(0, 1, 2, 3, 4, 5, 6, 7, 8)); } }); Python::attach(|py| { let array = numpy::pyarray![py, 0, 1, 2]; { let array = array.readonly(); let matrix = array.as_matrix(); assert_eq!(matrix, nalgebra::Matrix3x1::new(0, 1, 2)); let matrix: nalgebra::MatrixView<'_, i32, nalgebra::Const<3>, nalgebra::Const<1>> = array.try_as_matrix().unwrap(); assert_eq!(matrix, nalgebra::Matrix3x1::new(0, 1, 2)); } { let array = array.readwrite(); let matrix = array.as_matrix_mut(); assert_eq!(matrix, nalgebra::Matrix3x1::new(0, 1, 2)); let matrix: nalgebra::MatrixViewMut<'_, i32, nalgebra::Const<3>, nalgebra::Const<1>> = array.try_as_matrix_mut().unwrap(); assert_eq!(matrix, nalgebra::Matrix3x1::new(0, 1, 2)); } }); Python::attach(|py| { let array = PyArray::::zeros(py, (2, 2, 2), false); let array = array.readonly(); let matrix: Option> = array.try_as_matrix(); assert!(matrix.is_none()); }); Python::attach(|py| { let array = numpy::pyarray![py, [0, 1, 2], [3, 4, 5], [6, 7, 8]]; let array = py .eval( c_str!("a[::-1]"), Some(&[("a", array)].into_py_dict(py).unwrap()), None, ) .unwrap() .cast_into::>() .unwrap(); let array = array.readonly(); let matrix: Option> = array.try_as_matrix(); assert!(matrix.is_none()); }); Python::attach(|py| { let array = numpy::pyarray![py, [[0, 1], [2, 3]], [[4, 5], [6, 7]]]; let array = py .eval( c_str!("a[:,:,0]"), Some(&[("a", &array)].into_py_dict(py).unwrap()), None, ) .unwrap() .cast_into::>() .unwrap(); let array = array.readonly(); let matrix: nalgebra::MatrixView< '_, i32, nalgebra::Const<2>, nalgebra::Const<2>, nalgebra::Dyn, nalgebra::Dyn, > = array.try_as_matrix().unwrap(); assert_eq!(matrix, nalgebra::Matrix2::new(0, 2, 4, 6)); }); Python::attach(|py| { let array = numpy::pyarray![py, [[0, 1], [2, 3]], [[4, 5], [6, 7]]]; let array = py .eval( c_str!("a[:,:,0]"), Some(&[("a", &array)].into_py_dict(py).unwrap()), None, ) .unwrap() .cast_into::>() .unwrap(); let array = array.readonly(); let matrix: nalgebra::MatrixView< '_, i32, nalgebra::Const<2>, nalgebra::Const<2>, nalgebra::Dyn, nalgebra::Dyn, > = array.try_as_matrix().unwrap(); assert_eq!(matrix, nalgebra::Matrix2::new(0, 2, 4, 6)); }); Python::attach(|py| { let array = numpy::pyarray![py, [0, 1, 2], [3, 4, 5], [6, 7, 8]]; let array = array.readonly(); let matrix: Option< nalgebra::MatrixView< '_, i32, nalgebra::Const<2>, nalgebra::Const<3>, nalgebra::Const<3>, nalgebra::Const<1>, >, > = array.try_as_matrix(); assert!(matrix.is_none()); let matrix: Option< nalgebra::MatrixView< '_, i32, nalgebra::Const<3>, nalgebra::Const<2>, nalgebra::Const<3>, nalgebra::Const<1>, >, > = array.try_as_matrix(); assert!(matrix.is_none()); let matrix: Option< nalgebra::MatrixView< '_, i32, nalgebra::Const<3>, nalgebra::Const<3>, nalgebra::Const<2>, nalgebra::Const<1>, >, > = array.try_as_matrix(); assert!(matrix.is_none()); let matrix: Option< nalgebra::MatrixView< '_, i32, nalgebra::Const<3>, nalgebra::Const<3>, nalgebra::Const<3>, nalgebra::Const<2>, >, > = array.try_as_matrix(); assert!(matrix.is_none()); }); } numpy-0.27.1/tests/sum_products.rs000064400000000000000000000055511046102023000153300ustar 00000000000000use numpy::prelude::*; use numpy::{array, dot, einsum, inner, pyarray, PyArray0, PyArray1, PyArray2}; use pyo3::{Bound, Python}; #[test] fn test_dot() { Python::attach(|py| { let a = pyarray![py, [1, 0], [0, 1]]; let b = pyarray![py, [4, 1], [2, 2]]; let c: Bound<'_, PyArray2<_>> = dot(&a, &b).unwrap(); assert_eq!(c.readonly().as_array(), array![[4, 1], [2, 2]]); let a = pyarray![py, 1, 2, 3]; let err = dot::<_, _, _, Bound<'_, PyArray2<_>>>(&a, &b).unwrap_err(); assert!(err.to_string().contains("not aligned"), "{}", err); let a = pyarray![py, 1, 2, 3]; let b = pyarray![py, 0, 1, 0]; let c: Bound<'_, PyArray0<_>> = dot(&a, &b).unwrap(); assert_eq!(c.item(), 2); let c: i32 = dot(&a, &b).unwrap(); assert_eq!(c, 2); let a = pyarray![py, 1.0, 2.0, 3.0]; let b = pyarray![py, 0.0, 0.0, 0.0]; let c: f64 = dot(&a, &b).unwrap(); assert_eq!(c, 0.0); }); } #[test] fn test_inner() { Python::attach(|py| { let a = pyarray![py, 1, 2, 3]; let b = pyarray![py, 0, 1, 0]; let c: Bound<'_, PyArray0<_>> = inner(&a, &b).unwrap(); assert_eq!(c.item(), 2); let c: i32 = inner(&a, &b).unwrap(); assert_eq!(c, 2); let a = pyarray![py, 1.0, 2.0, 3.0]; let b = pyarray![py, 0.0, 0.0, 0.0]; let c: f64 = inner(&a, &b).unwrap(); assert_eq!(c, 0.0); let a = pyarray![py, [1, 0], [0, 1]]; let b = pyarray![py, [4, 1], [2, 2]]; let c: Bound<'_, PyArray2<_>> = inner(&a, &b).unwrap(); assert_eq!(c.readonly().as_array(), array![[4, 2], [1, 2]]); let a = pyarray![py, 1, 2, 3]; let err = inner::<_, _, _, Bound<'_, PyArray2<_>>>(&a, &b).unwrap_err(); assert!(err.to_string().contains("not aligned"), "{}", err); }); } #[test] fn test_einsum() { Python::attach(|py| { let a = PyArray1::::arange(py, 0, 25, 1) .reshape([5, 5]) .unwrap(); let b = pyarray![py, 0, 1, 2, 3, 4]; let c = pyarray![py, [0, 1, 2], [3, 4, 5]]; let d: Bound<'_, PyArray0<_>> = einsum!("ii", a).unwrap(); assert_eq!(d.item(), 60); let d: i32 = einsum!("ii", a).unwrap(); assert_eq!(d, 60); let d: Bound<'_, PyArray1<_>> = einsum!("ii->i", a).unwrap(); assert_eq!(d.readonly().as_array(), array![0, 6, 12, 18, 24]); let d: Bound<'_, PyArray1<_>> = einsum!("ij->i", a).unwrap(); assert_eq!(d.readonly().as_array(), array![10, 35, 60, 85, 110]); let d: Bound<'_, PyArray2<_>> = einsum!("ji", c).unwrap(); assert_eq!(d.readonly().as_array(), array![[0, 3], [1, 4], [2, 5]]); let d: Bound<'_, PyArray1<_>> = einsum!("ij,j", a, b).unwrap(); assert_eq!(d.readonly().as_array(), array![30, 80, 130, 180, 230]); }); } numpy-0.27.1/tests/to_py.rs000064400000000000000000000212021046102023000137220ustar 00000000000000use std::cmp::Ordering; use std::mem::size_of; use ndarray::{array, s, Array2, Array3}; use numpy::{prelude::*, PyArray}; use pyo3::{ py_run, types::{PyAnyMethods, PyDict, PyString}, Python, }; #[test] fn to_pyarray_vec() { Python::attach(|py| { #[allow(clippy::useless_vec)] let arr = vec![1, 2, 3].to_pyarray(py); assert_eq!(arr.shape(), [3]); assert_eq!(arr.readonly().as_slice().unwrap(), &[1, 2, 3]) }); } #[test] fn to_pyarray_boxed_slice() { Python::attach(|py| { let arr = vec![1, 2, 3].into_boxed_slice().to_pyarray(py); assert_eq!(arr.shape(), [3]); assert_eq!(arr.readonly().as_slice().unwrap(), &[1, 2, 3]) }); } #[test] fn to_pyarray_array() { Python::attach(|py| { let arr = Array3::::zeros((3, 4, 2)); let shape = arr.shape().to_vec(); let strides = arr .strides() .iter() .map(|dim| dim * size_of::() as isize) .collect::>(); let py_arr = PyArray::from_array(py, &arr); assert_eq!(py_arr.shape(), shape.as_slice()); assert_eq!(py_arr.strides(), strides.as_slice()); }); } #[test] fn iter_to_pyarray() { Python::attach(|py| { let arr = PyArray::from_iter(py, (0..10).map(|x| x * x)); assert_eq!( arr.readonly().as_slice().unwrap(), &[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] ); }); } #[test] fn long_iter_to_pyarray() { Python::attach(|py| { let arr = PyArray::from_iter(py, 0_u32..512); assert_eq!( arr.readonly().as_slice().unwrap(), (0_u32..512).collect::>(), ); }); } #[test] fn from_small_array() { macro_rules! small_array_test { ($($t:ty)+) => { $({ Python::attach(|py| { let array: [$t; 2] = [<$t>::MIN, <$t>::MAX]; let pyarray = array.to_pyarray(py); assert_eq!( pyarray.readonly().as_slice().unwrap(), &[<$t>::MIN, <$t>::MAX] ); }); })+ }; } small_array_test!(i8 u8 i16 u16 i32 u32 i64 u64 isize usize); } #[test] fn usize_dtype() { Python::attach(|py| { let x = vec![1_usize, 2, 3].into_pyarray(py); if cfg!(target_pointer_width = "64") { py_run!(py, x, "assert str(x.dtype) == 'uint64'") } else { py_run!(py, x, "assert str(x.dtype) == 'uint32'") }; }) } #[test] fn into_pyarray_vec() { Python::attach(|py| { let arr = vec![1, 2, 3].into_pyarray(py); assert_eq!(arr.readonly().as_slice().unwrap(), &[1, 2, 3]) }); } #[test] fn into_pyarray_boxed_slice() { Python::attach(|py| { let arr = vec![1, 2, 3].into_boxed_slice().into_pyarray(py); assert_eq!(arr.readonly().as_slice().unwrap(), &[1, 2, 3]) }); } #[test] fn into_pyarray_array() { Python::attach(|py| { let arr = Array3::::zeros((3, 4, 2)); let shape = arr.shape().to_vec(); let strides = arr .strides() .iter() .map(|dim| dim * size_of::() as isize) .collect::>(); let py_arr = arr.into_pyarray(py); assert_eq!(py_arr.shape(), shape.as_slice()); assert_eq!(py_arr.strides(), strides.as_slice()); }); } #[test] fn into_pyarray_cannot_resize() { Python::attach(|py| { let arr = vec![1, 2, 3].into_pyarray(py); unsafe { assert!(arr.resize(100).is_err()); } }); } #[test] fn into_pyarray_can_write() { Python::attach(|py| { let arr = vec![1, 2, 3].into_pyarray(py); py_run!(py, arr, "assert arr.flags['WRITEABLE']"); py_run!(py, arr, "arr[1] = 4"); }); } #[test] fn collapsed_into_pyarray() { // Check that `into_pyarray` works for array with the pointer of the first element is // not at the start of the allocation. // See https://github.com/PyO3/rust-numpy/issues/182 for more. Python::attach(|py| { let mut arr = Array2::::from_shape_fn([3, 4], |(i, j)| (i * 10 + j) as f64); arr.slice_collapse(s![1.., ..]); let cloned_arr = arr.clone(); let py_arr = arr.into_pyarray(py); assert_eq!(py_arr.readonly().as_array(), cloned_arr); }); } #[test] fn sliced_to_pyarray() { Python::attach(|py| { let matrix = Array2::from_shape_vec([4, 2], vec![0, 1, 2, 3, 4, 5, 6, 7]).unwrap(); let sliced_matrix = matrix.slice(s![1..4; -1, ..]); let py_arr = sliced_matrix.to_pyarray(py); assert_eq!(py_arr.readonly().as_array(), array![[6, 7], [4, 5], [2, 3]],); py_run!(py, py_arr, "assert py_arr.flags['C_CONTIGUOUS']") }); } #[test] fn forder_to_pyarray() { Python::attach(|py| { let matrix = Array2::from_shape_vec([4, 2], vec![0, 1, 2, 3, 4, 5, 6, 7]).unwrap(); let forder_matrix = matrix.reversed_axes(); let py_arr = forder_matrix.to_pyarray(py); assert_eq!( py_arr.readonly().as_array(), array![[0, 2, 4, 6], [1, 3, 5, 7]], ); py_run!(py, py_arr, "assert py_arr.flags['F_CONTIGUOUS']") }); } #[test] fn forder_into_pyarray() { Python::attach(|py| { let matrix = Array2::from_shape_vec([4, 2], vec![0, 1, 2, 3, 4, 5, 6, 7]).unwrap(); let forder_matrix = matrix.reversed_axes(); let py_arr = forder_matrix.into_pyarray(py); assert_eq!( py_arr.readonly().as_array(), array![[0, 2, 4, 6], [1, 3, 5, 7]], ); py_run!(py, py_arr, "assert py_arr.flags['F_CONTIGUOUS']") }); } #[test] fn to_pyarray_object_vec() { Python::attach(|py| { let dict = PyDict::new(py); let string = PyString::new(py, "Hello:)"); #[allow(clippy::useless_vec)] // otherwise we do not test the right trait impl let vec = vec![dict.into_any().unbind(), string.into_any().unbind()]; let arr = vec.to_pyarray(py); for (a, b) in vec.iter().zip(arr.readonly().as_slice().unwrap().iter()) { assert_eq!( a.bind(py).compare(b).map_err(|e| e.print(py)).unwrap(), Ordering::Equal ); } }); } #[test] fn to_pyarray_object_array() { Python::attach(|py| { let mut nd_arr = Array2::from_shape_fn((2, 3), |(_, _)| py.None()); nd_arr[(0, 2)] = PyDict::new(py).into_any().unbind(); nd_arr[(1, 0)] = PyString::new(py, "Hello:)").into_any().unbind(); let py_arr = nd_arr.to_pyarray(py); for (a, b) in nd_arr .as_slice() .unwrap() .iter() .zip(py_arr.readonly().as_slice().unwrap().iter()) { assert_eq!( a.bind(py).compare(b).map_err(|e| e.print(py)).unwrap(), Ordering::Equal ); } }); } #[test] fn slice_container_type_confusion() { Python::attach(|py| { let mut nd_arr = Array2::from_shape_fn((2, 3), |(_, _)| py.None()); nd_arr[(0, 2)] = PyDict::new(py).into_any().unbind(); nd_arr[(1, 0)] = PyString::new(py, "Hello:)").into_any().unbind(); let _py_arr = nd_arr.into_pyarray(py); // Dropping `_py_arr` used to trigger a segmentation fault due to calling `Py_DECREF` // on 1, 2 and 3 interpreted as pointers into the Python heap // after having created a `SliceBox` backing `_py_arr`, // c.f. https://github.com/PyO3/rust-numpy/issues/232. let _py_arr = vec![1, 2, 3].into_pyarray(py); }); } #[cfg(feature = "nalgebra")] #[test] fn matrix_to_numpy() { let matrix = nalgebra::Matrix3::::new(0, 1, 2, 3, 4, 5, 6, 7, 8); assert!(nalgebra::RawStorage::is_contiguous(&matrix.data)); Python::attach(|py| { let array = matrix.to_pyarray(py); assert_eq!( array.readonly().as_array(), array![[0, 1, 2], [3, 4, 5], [6, 7, 8]], ); }); let matrix = matrix.row(0); assert!(!nalgebra::RawStorage::is_contiguous(&matrix.data)); Python::attach(|py| { let array = matrix.to_pyarray(py); assert_eq!(array.readonly().as_array(), array![[0, 1, 2]]); }); let vector = nalgebra::Vector4::::new(-4, 1, 2, 3); Python::attach(|py| { let array = vector.to_pyarray(py); assert_eq!(array.readonly().as_array(), array![[-4], [1], [2], [3]]); }); let vector = nalgebra::RowVector2::::new(23, 42); Python::attach(|py| { let array = vector.to_pyarray(py); assert_eq!(array.readonly().as_array(), array![[23, 42]]); }); }