diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 000000000..d0a0c0118 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,103 @@ +on: + pull_request: + merge_group: + +name: Continuous integration + +env: + CARGO_TERM_COLOR: always + HOST: x86_64-unknown-linux-gnu + FEATURES: "test docs" + RUSTFLAGS: "-D warnings" + +jobs: + clippy: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - beta + name: clippy/${{ matrix.rust }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + components: clippy + - uses: Swatinem/rust-cache@v2 + - run: cargo clippy --features docs + tests: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - stable + - beta + - nightly + - 1.57.0 # MSRV + + name: tests/${{ matrix.rust }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - uses: Swatinem/rust-cache@v2 + - name: Install openblas + run: sudo apt-get install libopenblas-dev gfortran + - run: ./scripts/all-tests.sh "$FEATURES" ${{ matrix.rust }} + + cross_test: + if: ${{ github.event_name == 'merge_group' }} + runs-on: ubuntu-latest + strategy: + matrix: + include: + - rust: stable + target: s390x-unknown-linux-gnu + - rust: stable + target: i686-unknown-linux-gnu + + name: cross_test/${{ matrix.target }}/${{ matrix.rust }} + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - uses: Swatinem/rust-cache@v2 + - name: Install cross + run: cargo install cross + - run: ./scripts/cross-tests.sh "docs" ${{ matrix.rust }} ${{ matrix.target }} + + cargo-careful: + if: ${{ github.event_name == 'merge_group' }} + runs-on: ubuntu-latest + name: cargo-careful + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly + - uses: Swatinem/rust-cache@v2 + - name: Install cargo-careful + run: cargo install cargo-careful + - run: cargo careful test -Zcareful-sanitizer --features="$FEATURES" + - run: cargo careful test -Zcareful-sanitizer -p ndarray-rand + + conclusion: + needs: + - clippy + - tests + - cross_test + - cargo-careful + if: always() + runs-on: ubuntu-latest + steps: + - name: Result + run: | + jq -C <<< "${needs}" + # Check if all needs were successful or skipped. + "$(jq -r 'all(.result as $result | (["success", "skipped"] | contains([$result])))' <<< "${needs}")" + env: + needs: ${{ toJson(needs) }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 42e5e6584..000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,79 +0,0 @@ -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -name: Continuous integration - -env: - CARGO_TERM_COLOR: always - HOST: x86_64-unknown-linux-gnu - FEATURES: "test docs" - RUSTFLAGS: "-D warnings" - -jobs: - tests: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - stable - - beta - - nightly - - 1.49.0 # MSRV - - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - name: Install openblas - run: sudo apt-get install libopenblas-dev gfortran - - run: ./scripts/all-tests.sh "$FEATURES" ${{ matrix.rust }} - - cross_test: - runs-on: ubuntu-latest - strategy: - matrix: - include: - - rust: stable - target: mips-unknown-linux-gnu - - rust: stable - target: i686-unknown-linux-gnu - - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - - name: Cache cargo plugins - uses: actions/cache@v1 - with: - path: ~/.cargo/bin/ - key: ${{ runner.os }}-cargo-plugins - - name: Install cross - run: cargo install cross || true - - run: ./scripts/cross-tests.sh "docs" ${{ matrix.rust }} ${{ matrix.target }} - - clippy: - runs-on: ubuntu-latest - strategy: - matrix: - rust: - - beta - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - components: clippy - - run: cargo clippy - diff --git a/Cargo.toml b/Cargo.toml index b2c53ca29..ae9e33f06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,11 @@ [package] name = "ndarray" -version = "0.15.1" +version = "0.15.6" edition = "2018" +rust-version = "1.57" authors = [ - "bluss", + "Ulrik Sverdrup \"bluss\"", "Jim Turner" ] license = "MIT OR Apache-2.0" @@ -34,19 +35,20 @@ num-complex = { version = "0.4", default-features = false } rayon_ = { version = "1.0.3", optional = true, package = "rayon" } approx = { version = "0.4", optional = true , default-features = false } +approx-0_5 = { package = "approx", version = "0.5", optional = true , default-features = false } # Use via the `blas` crate feature! cblas-sys = { version = "0.1.4", optional = true, default-features = false } libc = { version = "0.2.82", optional = true } -matrixmultiply = { version = "0.3.0", default-features = false} +matrixmultiply = { version = "0.3.2", default-features = false, features=["cgemm"] } serde = { version = "1.0", optional = true, default-features = false, features = ["alloc"] } rawpointer = { version = "0.2" } [dev-dependencies] defmac = "0.2" -quickcheck = { version = "0.9", default-features = false } +quickcheck = { version = "1.0", default-features = false } approx = "0.4" itertools = { version = "0.10.0", default-features = false, features = ["use_std"] } @@ -64,20 +66,22 @@ serde-1 = ["serde"] test = [] # This feature is used for docs -docs = ["approx", "serde", "rayon"] +docs = ["approx", "approx-0_5", "serde", "rayon"] std = ["num-traits/std", "matrixmultiply/std"] rayon = ["rayon_", "std"] matrixmultiply-threading = ["matrixmultiply/threading"] -[profile.release] [profile.bench] debug = true +[profile.dev.package.numeric-tests] +opt-level = 2 +[profile.test.package.numeric-tests] +opt-level = 2 [workspace] -members = ["ndarray-rand", "xtest-serialization", "xtest-blas"] -exclude = ["xtest-numeric"] +members = ["ndarray-rand", "xtest-serialization", "xtest-blas", "xtest-numeric"] [package.metadata.release] no-dev-version = true diff --git a/LICENSE-MIT b/LICENSE-MIT index c87e92dc4..d0af99b04 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright (c) 2015 - 2018 Ulrik Sverdrup "bluss", +Copyright (c) 2015 - 2021 Ulrik Sverdrup "bluss", Jim Turner, and ndarray developers diff --git a/README-quick-start.md b/README-quick-start.md index 981ef3e9c..ad13acc72 100644 --- a/README-quick-start.md +++ b/README-quick-start.md @@ -7,7 +7,7 @@ You can use [play.integer32.com](https://play.integer32.com/) to immediately try ## The Basics -Just create your first 2x3 floating-point ndarray +You can create your first 2x3 floating-point ndarray as such: ```rust use ndarray::prelude::*; @@ -24,7 +24,7 @@ fn main() { println!("{:?}", a); } ``` -This code will create a simple array and output to stdout: +This code will create a simple array, then print it to stdout as such: ``` [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], shape=[2, 3], strides=[3, 1], layout=C (0x1), const ndim=2 @@ -34,8 +34,7 @@ This code will create a simple array and output to stdout: ### Element type and dimensionality -Now let's create more arrays. How about try make a zero array with dimension of (3, 2, 4)? - +Now let's create more arrays. A common operation on matrices is to create a matrix full of 0's of certain dimensions. Let's try to do that with dimensions (3, 2, 4) using the `Array::zeros` function: ```rust use ndarray::prelude::*; use ndarray::Array; @@ -44,13 +43,13 @@ fn main() { println!("{:?}", a); } ``` -gives +Unfortunately, this code does not compile. ``` | let a = Array::zeros((3, 2, 4).f()); | - ^^^^^^^^^^^^ cannot infer type for type parameter `A` ``` -Note that the compiler needs to infer the element type and dimensionality from context. In this -case the compiler failed to do that. Now we give it the type and let it infer dimensionality +Indeed, note that the compiler needs to infer the element type and dimensionality from context only. In this +case the compiler does not have enough information. To fix the code, we can explicitly give the element type through turbofish syntax, and let it infer the dimensionality type: ```rust use ndarray::prelude::*; @@ -60,7 +59,7 @@ fn main() { println!("{:?}", a); } ``` -and now it works: +This code now compiles to what we wanted: ``` [[[0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]], @@ -72,24 +71,11 @@ and now it works: [0.0, 0.0, 0.0, 0.0]]], shape=[3, 2, 4], strides=[1, 3, 6], layout=F (0x2), const ndim=3 ``` -We can also specify its dimensionality +We could also specify its dimensionality explicitly `Array::::zeros(...)`, with`Ix3` standing for 3D array type. Phew! We achieved type safety. If you tried changing the code above to `Array::::zeros((3, 2, 4, 5).f());`, which is not of dimension 3 anymore, Rust's type system would gracefully prevent you from compiling the code. -```rust -use ndarray::prelude::*; -use ndarray::{Array, Ix3}; -fn main() { - let a = Array::::zeros((3, 2, 4).f()); - println!("{:?}", a); -} -``` -`Ix3` stands for 3D array. +### Creating arrays with different initial values and/or different types -And now we are type checked. Try change the code above to `Array::::zeros((3, 2, 4, 5).f());` -and compile, see what happens. - -### How about create array of different type and having different initial values? - -The [`from_elem`](http://docs.rs/ndarray/latest/ndarray/struct.ArrayBase.html#method.from_elem) method can be handy here: +The [`from_elem`](http://docs.rs/ndarray/latest/ndarray/struct.ArrayBase.html#method.from_elem) method allows initializing an array of given dimension to a specific value of any type: ```rust use ndarray::{Array, Ix3}; @@ -99,7 +85,7 @@ fn main() { } ``` -### Some common create helper functions +### Some common array initializing helper functions `linspace` - Create a 1-D array with 11 elements with values 0., …, 5. ```rust use ndarray::prelude::*; @@ -114,10 +100,11 @@ The output is: [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0], shape=[11], strides=[1], layout=C | F (0x3), const ndim=1 ``` -And there are also `range`, `logspace`, `ones`, `eye` and so on you can choose to use. +Common array initializing methods include [`range`](https://docs.rs/ndarray/0.13.0/ndarray/struct.ArrayBase.html#method.range), [`logspace`](https://docs.rs/ndarray/0.13.0/ndarray/struct.ArrayBase.html#method.logspace), [`eye`](https://docs.rs/ndarray/0.13.0/ndarray/struct.ArrayBase.html#method.eye), [`ones`](https://docs.rs/ndarray/0.13.0/ndarray/struct.ArrayBase.html#method.ones)... ## Basic operations +Basic operations on arrays are all element-wise; you need to use specific methods for operations such as matrix multiplication (see later section). ```rust use ndarray::prelude::*; use ndarray::Array; @@ -136,16 +123,19 @@ fn main() { } ``` -Try remove all the `&` sign in front of `a` and `b`, does it still compile? Why? -Note that +Note that (for any binary operator `@`): * `&A @ &A` produces a new `Array` * `B @ A` consumes `B`, updates it with the result, and returns it * `B @ &A` consumes `B`, updates it with the result, and returns it * `C @= &A` performs an arithmetic operation in place +Try removing all the `&` sign in front of `a` and `b` in the last example: it will not compile anymore because of those rules. + For more info checkout https://docs.rs/ndarray/latest/ndarray/struct.ArrayBase.html#arithmetic-operations + + Some operations have `_axis` appended to the function name: they generally take in a parameter of type `Axis` as one of their inputs, such as `sum_axis`: @@ -179,8 +169,8 @@ fn main() { println!("a shape {:?}", &a.shape()); println!("b shape {:?}", &b.shape()); - let b = b.into_shape((4,1)).unwrap(); // reshape b to shape [4, 1] - println!("b shape {:?}", &b.shape()); + let b = b.into_shape_with_order((4,1)).unwrap(); // reshape b to shape [4, 1] + println!("b shape after reshape {:?}", &b.shape()); println!("{}", a.dot(&b)); // [1, 4] x [4, 1] -> [1, 1] println!("{}", a.t().dot(&b.t())); // [4, 1] x [1, 4] -> [4, 4] @@ -237,7 +227,7 @@ The output is: For more info about iteration see [Loops, Producers, and Iterators](https://docs.rs/ndarray/0.13.0/ndarray/struct.ArrayBase.html#loops-producers-and-iterators) -Let's try a 3D array with elements of type `isize`. This is how you index it: +Let's try a iterating over a 3D array with elements of type `isize`. This is how you index it: ```rust use ndarray::prelude::*; @@ -250,8 +240,8 @@ fn main() { [110,112,113]] ]; - let a = a.mapv(|a: isize| a.pow(1)); // numpy equivlant of `a ** 1`; - // This line does nothing but illustrate mapv with isize type + let a = a.mapv(|a: isize| a.pow(1)); // numpy equivalent of `a ** 1`; + // This line does nothing except illustrating mapv with isize type println!("a -> \n{}\n", a); println!("`a.slice(s![1, .., ..])` -> \n{}\n", a.slice(s![1, .., ..])); @@ -305,7 +295,7 @@ row: [[100, 101, 102], ## Shape Manipulation ### Changing the shape of an array -The shape of an array can be changed with `into_shape` method. +The shape of an array can be changed with the `into_shape_with_order` or `to_shape` method. ````rust use ndarray::prelude::*; @@ -329,7 +319,7 @@ fn main() { let b = Array::from_iter(a.iter()); println!("b = \n{:?}\n", b); - let c = b.into_shape([6, 2]).unwrap(); // consume b and generate c with new shape + let c = b.into_shape_with_order([6, 2]).unwrap(); // consume b and generate c with new shape println!("c = \n{:?}", c); } ```` @@ -352,39 +342,68 @@ c = [4.0, 9.0]], shape=[6, 2], strides=[2, 1], layout=C (0x1), const ndim=2 ``` -### Stacking together different arrays +### Stacking/concatenating together different arrays + +The `stack!` and `concatenate!` macros are helpful for stacking/concatenating +arrays. The `stack!` macro stacks arrays along a new axis, while the +`concatenate!` macro concatenates arrays along an existing axis: -Macro `stack!` is helpful for stacking arrays: ```rust use ndarray::prelude::*; -use ndarray::{Array, Axis, stack}; +use ndarray::{concatenate, stack, Axis}; fn main() { - let a = array![ - [9., 7.], - [5., 2.]]; - + [3., 7., 8.], + [5., 2., 4.], + ]; + let b = array![ - [1., 9.], - [5., 1.]]; - - println!("a vstack b = \n{:?}\n", stack![Axis(0), a, b]); - - println!("a hstack b = \n{:?}\n", stack![Axis(1), a, b]); + [1., 9., 0.], + [5., 4., 1.], + ]; + + println!("stack, axis 0:\n{:?}\n", stack![Axis(0), a, b]); + println!("stack, axis 1:\n{:?}\n", stack![Axis(1), a, b]); + println!("stack, axis 2:\n{:?}\n", stack![Axis(2), a, b]); + println!("concatenate, axis 0:\n{:?}\n", concatenate![Axis(0), a, b]); + println!("concatenate, axis 1:\n{:?}\n", concatenate![Axis(1), a, b]); } ``` The output is: ``` -a vstack b = -[[9.0, 7.0], - [5.0, 2.0], - [1.0, 9.0], - [5.0, 1.0]], shape=[4, 2], strides=[2, 1], layout=C (0x1), const ndim=2 +stack, axis 0: +[[[3.0, 7.0, 8.0], + [5.0, 2.0, 4.0]], + + [[1.0, 9.0, 0.0], + [5.0, 4.0, 1.0]]], shape=[2, 2, 3], strides=[6, 3, 1], layout=Cc (0x5), const ndim=3 + +stack, axis 1: +[[[3.0, 7.0, 8.0], + [1.0, 9.0, 0.0]], + + [[5.0, 2.0, 4.0], + [5.0, 4.0, 1.0]]], shape=[2, 2, 3], strides=[3, 6, 1], layout=c (0x4), const ndim=3 + +stack, axis 2: +[[[3.0, 1.0], + [7.0, 9.0], + [8.0, 0.0]], + + [[5.0, 5.0], + [2.0, 4.0], + [4.0, 1.0]]], shape=[2, 3, 2], strides=[1, 2, 6], layout=Ff (0xa), const ndim=3 + +concatenate, axis 0: +[[3.0, 7.0, 8.0], + [5.0, 2.0, 4.0], + [1.0, 9.0, 0.0], + [5.0, 4.0, 1.0]], shape=[4, 3], strides=[3, 1], layout=Cc (0x5), const ndim=2 -a hstack b = -[[9.0, 7.0, 1.0, 9.0], - [5.0, 2.0, 5.0, 1.0]], shape=[2, 4], strides=[4, 1], layout=C (0x1), const ndim=2 +concatenate, axis 1: +[[3.0, 7.0, 8.0, 1.0, 9.0, 0.0], + [5.0, 2.0, 4.0, 5.0, 4.0, 1.0]], shape=[2, 6], strides=[1, 2], layout=Ff (0xa), const ndim=2 ``` ### Splitting one array into several smaller ones @@ -432,18 +451,15 @@ s2 = ## Copies and Views ### View, Ref or Shallow Copy -As in Rust we have owner ship, so we cannot simply -update an element of an array while we have a -shared view of it. This will help us write more -robust code. +Rust has ownership, so we cannot simply update an element of an array while we have a shared view of it. This brings guarantees & helps having more robust code. ```rust use ndarray::prelude::*; use ndarray::{Array, Axis}; fn main() { - let mut a = Array::range(0., 12., 1.).into_shape([3 ,4]).unwrap(); + let mut a = Array::range(0., 12., 1.).into_shape_with_order([3 ,4]).unwrap(); println!("a = \n{}\n", a); { @@ -503,7 +519,7 @@ use ndarray::Array; fn main() { - let mut a = Array::range(0., 4., 1.).into_shape([2 ,2]).unwrap(); + let mut a = Array::range(0., 4., 1.).into_shape_with_order([2 ,2]).unwrap(); let b = a.clone(); println!("a = \n{}\n", a); @@ -537,9 +553,9 @@ b clone of a = [2, 3]] ``` -Noticing that using `clone()` (or cloning) an `Array` type also copies the array's elements. It creates an independently owned array of the same type. +Notice that using `clone()` (or cloning) an `Array` type also copies the array's elements. It creates an independently owned array of the same type. -Cloning an `ArrayView` does not clone or copy the underlying elements - it just clones the view reference (as it happens in Rust when cloning a `&` reference). +Cloning an `ArrayView` does not clone or copy the underlying elements - it only clones the view reference (as it happens in Rust when cloning a `&` reference). ## Broadcasting @@ -571,7 +587,7 @@ fn main() { See [.broadcast()](https://docs.rs/ndarray/latest/ndarray/struct.ArrayBase.html#method.broadcast) for a more detailed description. -And there is a short example of it: +And here is a short example of it: ```rust use ndarray::prelude::*; diff --git a/README.rst b/README.rst index 503024886..9e3d37247 100644 --- a/README.rst +++ b/README.rst @@ -15,7 +15,7 @@ __ https://docs.rs/ndarray/ :alt: CI build status .. _build_status: https://github.com/rust-ndarray/ndarray/actions -.. |crates| image:: http://meritbadge.herokuapp.com/ndarray +.. |crates| image:: https://img.shields.io/crates/v/ndarray.svg :alt: ndarray at crates.io .. _crates: https://crates.io/crates/ndarray @@ -65,8 +65,11 @@ your `Cargo.toml`. - This crate can be used without the standard library by disabling the default `std` feature. To do so, use this in your `Cargo.toml`: - [dependencies] - ndarray = { version = "0.x.y", default-features = false } + :: + + [dependencies] + ndarray = { version = "0.x.y", default-features = false } + - The `geomspace` `linspace` `logspace` `range` `std` `var` `var_axis` and `std_axis` methods are only available when `std` is enabled. @@ -80,6 +83,14 @@ your `Cargo.toml`. - Enables parallel iterators, parallelized methods and ``par_azip!``. - Implies std +- ``approx`` + + - Implementations of traits from version 0.4 of the [`approx`] crate. + +- ``approx-0_5`` + + - Implementations of traits from version 0.5 of the [`approx`] crate. + - ``blas`` - Enable transparent BLAS support for matrix multiplication. diff --git a/RELEASES.md b/RELEASES.md index be479c874..364166718 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,312 @@ +Version 0.15.6 (2022-07-30) +=========================== + +New features +------------ + +- Add `get_ptr` and `get_mut_ptr` methods for getting an element's pointer from + an index, by [@adamreichold]. + + https://github.com/rust-ndarray/ndarray/pull/1151 + +Other changes +------------- + +- Various fixes to resolve compiler and Clippy warnings/errors, by [@aganders3] + and [@jturner314]. + + https://github.com/rust-ndarray/ndarray/pull/1171 + +- Fix description of `stack!` in quick start docs, by [@jturner314]. Thanks to + [@HyeokSuLee] for pointing out the issue. + + https://github.com/rust-ndarray/ndarray/pull/1156 + +- Add MSRV to `Cargo.toml`. + + https://github.com/rust-ndarray/ndarray/pull/1191 + + +Version 0.15.5 (2022-07-30) +=========================== + +Enhancements +------------ + +- The `s!` macro now works in `no_std` environments, by [@makotokato]. + + https://github.com/rust-ndarray/ndarray/pull/1154 + +Other changes +------------- + +- Improve docs and fix typos, by [@steffahn] and [@Rikorose]. + + https://github.com/rust-ndarray/ndarray/pull/1134
+ https://github.com/rust-ndarray/ndarray/pull/1164 + + +Version 0.15.4 (2021-11-23) +=========================== + +The Dr. Turner release 🚀 + +New features +------------ + +- Complex matrix multiplication now uses BLAS ``cgemm``/``zgemm`` when + enabled (and matrix layout allows), by [@ethanhs]. + + https://github.com/rust-ndarray/ndarray/pull/1106 + +- Use `matrixmultiply` as fallback for complex matrix multiplication + when BLAS is not available or the matrix layout requires it by [@bluss] + + https://github.com/rust-ndarray/ndarray/pull/1118 + +- Add ``into/to_slice_memory_order`` methods for views, lifetime-preserving + versions of existing similar methods by [@jturner314] + + https://github.com/rust-ndarray/ndarray/pull/1015 + +- ``kron`` function for Kronecker product by [@ethanhs]. + + https://github.com/rust-ndarray/ndarray/pull/1105 + +- ``split_complex`` method for splitting complex arrays into separate + real and imag view parts by [@jturner314] and [@ethanhs]. + + https://github.com/rust-ndarray/ndarray/pull/1107 + +- New method ``try_into_owned_nocopy`` by [@jturner314] + + https://github.com/rust-ndarray/ndarray/pull/1022 + +- New producer and iterable ``axis_windows`` by [@VasanthakumarV] + and [@jturner314]. + + https://github.com/rust-ndarray/ndarray/pull/1022 + +- New method ``Zip::par_fold`` by [@adamreichold] + + https://github.com/rust-ndarray/ndarray/pull/1095 + +- New constructor ``from_diag_elem`` by [@jturner314] + + https://github.com/rust-ndarray/ndarray/pull/1076 + +- ``Parallel::with_min_len`` method for parallel iterators by [@adamreichold] + + https://github.com/rust-ndarray/ndarray/pull/1081 + +- Allocation-preserving map function ``.mapv_into_any()`` added by [@benkay86] + +Enhancements +------------ + +- Improve performance of ``.sum_axis()`` for some cases by [@jturner314] + + https://github.com/rust-ndarray/ndarray/pull/1061 + +Bug fixes +--------- + +- Fix error in calling dgemv (matrix-vector multiplication) with BLAS and + broadcasted arrays, by [@jturner314]. + + https://github.com/rust-ndarray/ndarray/pull/1088 + +API changes +----------- + +- Support approx 0.5 partially alongside the already existing approx 0.4 support. + New feature flag is `approx-0_5`, by [@jturner314] + + https://github.com/rust-ndarray/ndarray/pull/1025 + +- Slice and reference-to-array conversions to CowArray added for by [@jturner314]. + + https://github.com/rust-ndarray/ndarray/pull/1038 + +- Allow trailing comma in stack and concatenate macros by [@jturner314] + + https://github.com/rust-ndarray/ndarray/pull/1044 + +- ``Zip`` now has a ``must_use`` marker to help users by [@adamreichold] + + https://github.com/rust-ndarray/ndarray/pull/1082 + +Other changes +------------- + +- Fixing the crates.io badge on github by [@atouchet] + + https://github.com/rust-ndarray/ndarray/pull/1104 + +- Use intra-doc links in docs by [@LeSeulArtichaut] + + https://github.com/rust-ndarray/ndarray/pull/1033 + +- Clippy fixes by [@adamreichold] + + https://github.com/rust-ndarray/ndarray/pull/1092
+ https://github.com/rust-ndarray/ndarray/pull/1091 + +- Minor fixes in links and punctuation in docs by [@jimblandy] + + https://github.com/rust-ndarray/ndarray/pull/1056 + +- Minor fixes in docs by [@chohner] + + https://github.com/rust-ndarray/ndarray/pull/1119 + +- Update tests to quickcheck 1.0 by [@bluss] + + https://github.com/rust-ndarray/ndarray/pull/1114 + + +Version 0.15.3 (2021-06-05) +=========================== + +New features +------------ + +- New methods `.last/_mut()` for arrays and array views by [@jturner314] + + https://github.com/rust-ndarray/ndarray/pull/1013 + +Bug fixes +--------- + +- Fix `as_slice_memory_order_mut()` so that it never changes strides (the + memory layout) of the array when called. + + This was a bug that impacted `ArcArray` (and for example not `Array` or `ArrayView/Mut`), + and multiple methods on `ArcArray` that use `as_slice_memory_order_mut` (for example `map_mut`). + Fix by [@jturner314]. + + https://github.com/rust-ndarray/ndarray/pull/1019 + +API changes +----------- + +- Array1 now implements `From>` by [@jturner314] + + https://github.com/rust-ndarray/ndarray/pull/1016 + +- ArcArray now implements `From>` by [@jturner314] + + https://github.com/rust-ndarray/ndarray/pull/1021 + +- CowArray now implements RawDataSubst by [@jturner314] + + https://github.com/rust-ndarray/ndarray/pull/1020 + +Other changes +------------- + +- Mention unsharing in `.as_mut_ptr` docs by [@jturner314] + + https://github.com/rust-ndarray/ndarray/pull/1017 + +- Clarify and fix minor errors in push/append method docs by [@bluss] f21c668a + +- Fix several warnings in doc example code by [@bluss] + + https://github.com/rust-ndarray/ndarray/pull/1009 + + +Version 0.15.2 (2021-05-17 🇳🇴) +================================ + +New features +------------ + +- New methods for growing/appending to owned `Array`s. These methods allow + building an array efficiently chunk by chunk. By [@bluss]. + + - `.push_row()`, `.push_column()` + - `.push(axis, array)`, `.append(axis, array)` + + `stack`, `concatenate` and `.select()` now support all `Clone`-able elements + as a result. + + https://github.com/rust-ndarray/ndarray/pull/932
+ https://github.com/rust-ndarray/ndarray/pull/990 + +- New reshaping method `.to_shape(...)`, called with new shape and optional + ordering parameter, this is the first improvement for reshaping in terms of + added features and increased consistency, with more to come. By [@bluss]. + + https://github.com/rust-ndarray/ndarray/pull/982 + +- `Array` now implements a by-value iterator, by [@bluss]. + + https://github.com/rust-ndarray/ndarray/pull/986 + +- New methods `.move_into()` and `.move_into_uninit()` which allow assigning + into an array by moving values from an array into another, by [@bluss]. + + https://github.com/rust-ndarray/ndarray/pull/932
+ https://github.com/rust-ndarray/ndarray/pull/997 + +- New method `.remove_index()` for owned arrays by [@bluss] + + https://github.com/rust-ndarray/ndarray/pull/967 + +- New constructor `build_uninit` which makes it easier to initialize + uninitialized arrays in a way that's generic over all owned array kinds. + By [@bluss]. + + https://github.com/rust-ndarray/ndarray/pull/1001 + +Enhancements +------------ + +- Preserve the allocation of the input array in some more cases for arithmetic ops by [@SparrowLii] + + https://github.com/rust-ndarray/ndarray/pull/963 + +- Improve broadcasting performance for &array + &array arithmetic ops by [@SparrowLii] + + https://github.com/rust-ndarray/ndarray/pull/965 + +Bug fixes +--------- + +- Fix an error in construction of empty array with negative strides, by [@jturner314]. + + https://github.com/rust-ndarray/ndarray/pull/998 + +- Fix minor performance bug with loop order selection in Zip by [@bluss] + + https://github.com/rust-ndarray/ndarray/pull/977 + +API changes +----------- + +- Add dimension getters to `Shape` and `StrideShape` by [@stokhos] + + https://github.com/rust-ndarray/ndarray/pull/978 + +Other changes +------------- + +- Rustdoc now uses the ndarray logo that [@jturner314] created previously + + https://github.com/rust-ndarray/ndarray/pull/981 + +- Minor doc changes by [@stokhos], [@cassiersg] and [@jturner314] + + https://github.com/rust-ndarray/ndarray/pull/968
+ https://github.com/rust-ndarray/ndarray/pull/971
+ https://github.com/rust-ndarray/ndarray/pull/974 + +- A little refactoring to reduce generics bloat in a few places by [@bluss]. + + https://github.com/rust-ndarray/ndarray/pull/1004 + + Version 0.15.1 (2021-03-29) =========================== @@ -105,7 +414,7 @@ API changes ----------- - New constructors `Array::from_iter` and `Array::from_vec` by [@bluss]. - No new functionality, just that these constructors are avaiable without trait + No new functionality, just that these constructors are available without trait imports. https://github.com/rust-ndarray/ndarray/pull/921 @@ -284,7 +593,7 @@ New features Enhancements ------------ -- Handle inhomogenous shape inputs better in Zip, in practice: guess better whether +- Handle inhomogeneous shape inputs better in Zip, in practice: guess better whether to prefer c- or f-order for the inner loop by [@bluss] https://github.com/rust-ndarray/ndarray/pull/809 @@ -717,7 +1026,7 @@ Earlier releases - Add `Zip::indexed` - New methods `genrows/_mut, gencolumns/_mut, lanes/_mut` that - return iterable producers (producer means `Zip` compatibile). + return iterable producers (producer means `Zip` compatible). - New method `.windows()` by @Robbepop, returns an iterable producer - New function `general_mat_vec_mul` (with fast default and blas acceleration) - `Zip::apply` and `fold_while` now take `self` as the first argument @@ -1281,28 +1590,44 @@ Earlier releases - Starting point for evolution to come +[@adamreichold]: https://github.com/adamreichold +[@aganders3]: https://github.com/aganders3 [@bluss]: https://github.com/bluss [@jturner314]: https://github.com/jturner314 [@LukeMathWalker]: https://github.com/LukeMathWalker [@acj]: https://github.com/acj +[@adamreichold]: https://github.com/adamreichold +[@atouchet]: https://github.com/atouchet [@andrei-papou]: https://github.com/andrei-papou +[@benkay]: https://github.com/benkay +[@cassiersg]: https://github.com/cassiersg +[@chohner]: https://github.com/chohner [@dam5h]: https://github.com/dam5h +[@ethanhs]: https://github.com/ethanhs [@d-dorazio]: https://github.com/d-dorazio [@Eijebong]: https://github.com/Eijebong +[@HyeokSuLee]: https://github.com/HyeokSuLee [@insideoutclub]: https://github.com/insideoutclub [@JP-Ellis]: https://github.com/JP-Ellis +[@jimblandy]: https://github.com/jimblandy +[@LeSeulArtichaut]: https://github.com/LeSeulArtichaut [@lifuyang]: https://github.com/liufuyang [@kdubovikov]: https://github.com/kdubovikov +[@makotokato]: https://github.com/makotokato [@max-sixty]: https://github.com/max-sixty [@mneumann]: https://github.com/mneumann [@mockersf]: https://github.com/mockersf [@nilgoyette]: https://github.com/nilgoyette [@nitsky]: https://github.com/nitsky +[@Rikorose]: https://github.com/Rikorose [@rth]: https://github.com/rth [@sebasv]: https://github.com/sebasv [@SparrowLii]: https://github.com/SparrowLii +[@steffahn]: https://github.com/steffahn +[@stokhos]: https://github.com/stokhos [@termoshtt]: https://github.com/termoshtt [@TheLortex]: https://github.com/TheLortex [@viniciusd]: https://github.com/viniciusd +[@VasanthakumarV]: https://github.com/VasanthakumarV [@xd009642]: https://github.com/xd009642 [@Zuse64]: https://github.com/Zuse64 diff --git a/benches/append.rs b/benches/append.rs new file mode 100644 index 000000000..1a911a278 --- /dev/null +++ b/benches/append.rs @@ -0,0 +1,35 @@ +#![feature(test)] + +extern crate test; +use test::Bencher; + +use ndarray::prelude::*; + +#[bench] +fn select_axis0(bench: &mut Bencher) { + let a = Array::::zeros((256, 256)); + let selectable = vec![0, 1, 2, 0, 1, 3, 0, 4, 16, 32, 128, 147, 149, 220, 221, 255, 221, 0, 1]; + bench.iter(|| { + a.select(Axis(0), &selectable) + }); +} + +#[bench] +fn select_axis1(bench: &mut Bencher) { + let a = Array::::zeros((256, 256)); + let selectable = vec![0, 1, 2, 0, 1, 3, 0, 4, 16, 32, 128, 147, 149, 220, 221, 255, 221, 0, 1]; + bench.iter(|| { + a.select(Axis(1), &selectable) + }); +} + +#[bench] +fn select_1d(bench: &mut Bencher) { + let a = Array::::zeros(1024); + let mut selectable = (0..a.len()).step_by(17).collect::>(); + selectable.extend(selectable.clone().iter().rev()); + + bench.iter(|| { + a.select(Axis(0), &selectable) + }); +} diff --git a/benches/bench1.rs b/benches/bench1.rs index c7f18e3c4..6b6864194 100644 --- a/benches/bench1.rs +++ b/benches/bench1.rs @@ -915,7 +915,7 @@ const MEAN_SUM_N: usize = 127; fn range_mat(m: Ix, n: Ix) -> Array2 { assert!(m * n != 0); Array::linspace(0., (m * n - 1) as f32, m * n) - .into_shape((m, n)) + .into_shape_with_order((m, n)) .unwrap() } diff --git a/benches/construct.rs b/benches/construct.rs index 3d77a89e0..c3603ce7c 100644 --- a/benches/construct.rs +++ b/benches/construct.rs @@ -22,13 +22,13 @@ fn zeros_f64(bench: &mut Bencher) { #[bench] fn map_regular(bench: &mut test::Bencher) { - let a = Array::linspace(0., 127., 128).into_shape((8, 16)).unwrap(); + let a = Array::linspace(0., 127., 128).into_shape_with_order((8, 16)).unwrap(); bench.iter(|| a.map(|&x| 2. * x)); } #[bench] fn map_stride(bench: &mut test::Bencher) { - let a = Array::linspace(0., 127., 256).into_shape((8, 32)).unwrap(); + let a = Array::linspace(0., 127., 256).into_shape_with_order((8, 32)).unwrap(); let av = a.slice(s![.., ..;2]); bench.iter(|| av.map(|&x| 2. * x)); } diff --git a/benches/gemv.rs b/benches/gemv_gemm.rs similarity index 61% rename from benches/gemv.rs rename to benches/gemv_gemm.rs index 4bca08319..cfa14beac 100644 --- a/benches/gemv.rs +++ b/benches/gemv_gemm.rs @@ -9,8 +9,13 @@ extern crate test; use test::Bencher; +use num_complex::Complex; +use num_traits::{Float, One, Zero}; + use ndarray::prelude::*; +use ndarray::LinalgScalar; +use ndarray::linalg::general_mat_mul; use ndarray::linalg::general_mat_vec_mul; #[bench] @@ -45,3 +50,27 @@ fn gemv_64_32(bench: &mut Bencher) { general_mat_vec_mul(1.0, &a, &x, 1.0, &mut y); }); } + +#[bench] +fn cgemm_100(bench: &mut Bencher) { + cgemm_bench::(100, bench); +} + +#[bench] +fn zgemm_100(bench: &mut Bencher) { + cgemm_bench::(100, bench); +} + +fn cgemm_bench(size: usize, bench: &mut Bencher) +where + A: LinalgScalar + Float, +{ + let (m, k, n) = (size, size, size); + let a = Array::, _>::zeros((m, k)); + + let x = Array::zeros((k, n)); + let mut y = Array::zeros((m, n)); + bench.iter(|| { + general_mat_mul(Complex::one(), &a, &x, Complex::zero(), &mut y); + }); +} diff --git a/benches/higher-order.rs b/benches/higher-order.rs index 6bbd57177..f593cd026 100644 --- a/benches/higher-order.rs +++ b/benches/higher-order.rs @@ -17,7 +17,7 @@ const Y: usize = 16; #[bench] fn map_regular(bench: &mut Bencher) { - let a = Array::linspace(0., 127., N).into_shape((X, Y)).unwrap(); + let a = Array::linspace(0., 127., N).into_shape_with_order((X, Y)).unwrap(); bench.iter(|| a.map(|&x| 2. * x)); } @@ -28,7 +28,7 @@ pub fn double_array(mut a: ArrayViewMut2<'_, f64>) { #[bench] fn map_stride_double_f64(bench: &mut Bencher) { let mut a = Array::linspace(0., 127., N * 2) - .into_shape([X, Y * 2]) + .into_shape_with_order([X, Y * 2]) .unwrap(); let mut av = a.slice_mut(s![.., ..;2]); bench.iter(|| { @@ -39,7 +39,7 @@ fn map_stride_double_f64(bench: &mut Bencher) { #[bench] fn map_stride_f64(bench: &mut Bencher) { let a = Array::linspace(0., 127., N * 2) - .into_shape([X, Y * 2]) + .into_shape_with_order([X, Y * 2]) .unwrap(); let av = a.slice(s![.., ..;2]); bench.iter(|| av.map(|&x| 2. * x)); @@ -48,7 +48,7 @@ fn map_stride_f64(bench: &mut Bencher) { #[bench] fn map_stride_u32(bench: &mut Bencher) { let a = Array::linspace(0., 127., N * 2) - .into_shape([X, Y * 2]) + .into_shape_with_order([X, Y * 2]) .unwrap(); let b = a.mapv(|x| x as u32); let av = b.slice(s![.., ..;2]); @@ -58,7 +58,7 @@ fn map_stride_u32(bench: &mut Bencher) { #[bench] fn fold_axis(bench: &mut Bencher) { let a = Array::linspace(0., 127., N * 2) - .into_shape([X, Y * 2]) + .into_shape_with_order([X, Y * 2]) .unwrap(); bench.iter(|| a.fold_axis(Axis(0), 0., |&acc, &elt| acc + elt)); } @@ -69,7 +69,7 @@ const MASZ: usize = MA * MA; #[bench] fn map_axis_0(bench: &mut Bencher) { let a = Array::from_iter(0..MASZ as i32) - .into_shape([MA, MA]) + .into_shape_with_order([MA, MA]) .unwrap(); bench.iter(|| a.map_axis(Axis(0), black_box)); } @@ -77,7 +77,7 @@ fn map_axis_0(bench: &mut Bencher) { #[bench] fn map_axis_1(bench: &mut Bencher) { let a = Array::from_iter(0..MASZ as i32) - .into_shape([MA, MA]) + .into_shape_with_order([MA, MA]) .unwrap(); bench.iter(|| a.map_axis(Axis(1), black_box)); } diff --git a/benches/iter.rs b/benches/iter.rs index d8c716dc8..22c8b8d17 100644 --- a/benches/iter.rs +++ b/benches/iter.rs @@ -46,21 +46,21 @@ fn iter_sum_2d_transpose(bench: &mut Bencher) { #[bench] fn iter_filter_sum_2d_u32(bench: &mut Bencher) { - let a = Array::linspace(0., 1., 256).into_shape((16, 16)).unwrap(); + let a = Array::linspace(0., 1., 256).into_shape_with_order((16, 16)).unwrap(); let b = a.mapv(|x| (x * 100.) as u32); bench.iter(|| b.iter().filter(|&&x| x < 75).sum::()); } #[bench] fn iter_filter_sum_2d_f32(bench: &mut Bencher) { - let a = Array::linspace(0., 1., 256).into_shape((16, 16)).unwrap(); + let a = Array::linspace(0., 1., 256).into_shape_with_order((16, 16)).unwrap(); let b = a * 100.; bench.iter(|| b.iter().filter(|&&x| x < 75.).sum::()); } #[bench] fn iter_filter_sum_2d_stride_u32(bench: &mut Bencher) { - let a = Array::linspace(0., 1., 256).into_shape((16, 16)).unwrap(); + let a = Array::linspace(0., 1., 256).into_shape_with_order((16, 16)).unwrap(); let b = a.mapv(|x| (x * 100.) as u32); let b = b.slice(s![.., ..;2]); bench.iter(|| b.iter().filter(|&&x| x < 75).sum::()); @@ -68,7 +68,7 @@ fn iter_filter_sum_2d_stride_u32(bench: &mut Bencher) { #[bench] fn iter_filter_sum_2d_stride_f32(bench: &mut Bencher) { - let a = Array::linspace(0., 1., 256).into_shape((16, 16)).unwrap(); + let a = Array::linspace(0., 1., 256).into_shape_with_order((16, 16)).unwrap(); let b = a * 100.; let b = b.slice(s![.., ..;2]); bench.iter(|| b.iter().filter(|&&x| x < 75.).sum::()); @@ -321,7 +321,7 @@ fn indexed_iter_3d_dyn(bench: &mut Bencher) { for ((i, j, k), elt) in a.indexed_iter_mut() { *elt = (i + 100 * j + 10000 * k) as _; } - let a = a.into_shape(&[ISZ; 3][..]).unwrap(); + let a = a.into_shape_with_order(&[ISZ; 3][..]).unwrap(); bench.iter(|| { for (i, &_elt) in a.indexed_iter() { diff --git a/benches/numeric.rs b/benches/numeric.rs index 4c579eb71..d9b9187ff 100644 --- a/benches/numeric.rs +++ b/benches/numeric.rs @@ -12,7 +12,7 @@ const Y: usize = 16; #[bench] fn clip(bench: &mut Bencher) { let mut a = Array::linspace(0., 127., N * 2) - .into_shape([X, Y * 2]) + .into_shape_with_order([X, Y * 2]) .unwrap(); let min = 2.; let max = 5.; diff --git a/benches/to_shape.rs b/benches/to_shape.rs new file mode 100644 index 000000000..a048eb774 --- /dev/null +++ b/benches/to_shape.rs @@ -0,0 +1,106 @@ +#![feature(test)] + +extern crate test; +use test::Bencher; + +use ndarray::prelude::*; +use ndarray::Order; + +#[bench] +fn to_shape2_1(bench: &mut Bencher) { + let a = Array::::zeros((4, 5)); + let view = a.view(); + bench.iter(|| { + view.to_shape(4 * 5).unwrap() + }); +} + +#[bench] +fn to_shape2_2_same(bench: &mut Bencher) { + let a = Array::::zeros((4, 5)); + let view = a.view(); + bench.iter(|| { + view.to_shape((4, 5)).unwrap() + }); +} + +#[bench] +fn to_shape2_2_flip(bench: &mut Bencher) { + let a = Array::::zeros((4, 5)); + let view = a.view(); + bench.iter(|| { + view.to_shape((5, 4)).unwrap() + }); +} + +#[bench] +fn to_shape2_3(bench: &mut Bencher) { + let a = Array::::zeros((4, 5)); + let view = a.view(); + bench.iter(|| { + view.to_shape((2, 5, 2)).unwrap() + }); +} + +#[bench] +fn to_shape3_1(bench: &mut Bencher) { + let a = Array::::zeros((3, 4, 5)); + let view = a.view(); + bench.iter(|| { + view.to_shape(3 * 4 * 5).unwrap() + }); +} + +#[bench] +fn to_shape3_2_order(bench: &mut Bencher) { + let a = Array::::zeros((3, 4, 5)); + let view = a.view(); + bench.iter(|| { + view.to_shape((12, 5)).unwrap() + }); +} + +#[bench] +fn to_shape3_2_outoforder(bench: &mut Bencher) { + let a = Array::::zeros((3, 4, 5)); + let view = a.view(); + bench.iter(|| { + view.to_shape((4, 15)).unwrap() + }); +} + +#[bench] +fn to_shape3_3c(bench: &mut Bencher) { + let a = Array::::zeros((3, 4, 5)); + let view = a.view(); + bench.iter(|| { + view.to_shape((3, 4, 5)).unwrap() + }); +} + +#[bench] +fn to_shape3_3f(bench: &mut Bencher) { + let a = Array::::zeros((3, 4, 5).f()); + let view = a.view(); + bench.iter(|| { + view.to_shape(((3, 4, 5), Order::F)).unwrap() + }); +} + +#[bench] +fn to_shape3_4c(bench: &mut Bencher) { + let a = Array::::zeros((3, 4, 5)); + let view = a.view(); + bench.iter(|| { + view.to_shape(((2, 3, 2, 5), Order::C)).unwrap() + }); +} + +#[bench] +fn to_shape3_4f(bench: &mut Bencher) { + let a = Array::::zeros((3, 4, 5).f()); + let view = a.view(); + bench.iter(|| { + view.to_shape(((2, 3, 2, 5), Order::F)).unwrap() + }); +} diff --git a/examples/axis_ops.rs b/examples/axis_ops.rs index 624af32c3..e84d112c2 100644 --- a/examples/axis_ops.rs +++ b/examples/axis_ops.rs @@ -7,7 +7,16 @@ use ndarray::prelude::*; -fn regularize(a: &mut Array) -> Result<(), ()> +/// Reorder a's axes so that they are in "standard" axis order; +/// make sure axes are in positive stride direction, and merge adjacent +/// axes if possible. +/// +/// This changes the logical order of the elements in the +/// array, so that if we read them in row-major order after regularization, +/// it corresponds to their order in memory. +/// +/// Errors if array has a 0-stride axis +fn regularize(a: &mut Array) -> Result<(), &'static str> where D: Dimension, A: ::std::fmt::Debug, @@ -16,7 +25,9 @@ where // reverse all neg axes while let Some(ax) = a.axes().find(|ax| ax.stride <= 0) { if ax.stride == 0 { - return Err(()); + // no real reason to error on this case; other than + // stride == 0 is incompatible with an owned array. + return Err("Cannot regularize array with stride == 0 axis"); } // reverse ax println!("Reverse {:?}", ax.axis); @@ -27,8 +38,11 @@ where let mut i = 0; let n = a.ndim(); while let Some(ax) = a.axes().rev().skip(i).min_by_key(|ax| ax.stride.abs()) { - a.swap_axes(n - 1 - i, ax.axis.index()); - println!("Swap {:?} <=> {}", ax.axis, n - 1 - i); + let cur_axis = Axis(n - 1 - i); + if ax.axis != cur_axis { + a.swap_axes(cur_axis.index(), ax.axis.index()); + println!("Swap {:?} <=> {:?}", cur_axis, ax.axis); + } i += 1; } @@ -40,7 +54,7 @@ where break; } } - println!("{:?}", a); + println!("Result:\n{:?}\n", a); Ok(()) } @@ -52,22 +66,24 @@ fn main() { a.swap_axes(0, 1); a.swap_axes(0, 2); a.slice_collapse(s![.., ..;-1, ..]); - regularize(&mut a).ok(); + regularize(&mut a).unwrap(); let mut b = Array::::zeros((2, 3, 4)); for (i, elt) in (0..).zip(&mut b) { *elt = i; } - regularize(&mut b).ok(); - let mut b = b.into_shape(a.len()).unwrap(); - regularize(&mut b).ok(); + regularize(&mut b).unwrap(); + + let mut b = b.into_shape_with_order(a.len()).unwrap(); + regularize(&mut b).unwrap(); + b.invert_axis(Axis(0)); - regularize(&mut b).ok(); + regularize(&mut b).unwrap(); let mut a = Array::::zeros((2, 3, 4)); for (i, elt) in (0..).zip(&mut a) { *elt = i; } a.slice_collapse(s![..;-1, ..;2, ..]); - regularize(&mut a).ok(); + regularize(&mut a).unwrap(); } diff --git a/examples/column_standardize.rs b/examples/column_standardize.rs index c360170bd..6a1840f03 100644 --- a/examples/column_standardize.rs +++ b/examples/column_standardize.rs @@ -1,31 +1,26 @@ +#[cfg(feature = "std")] use ndarray::prelude::*; -fn std1d(a: ArrayView1<'_, f64>) -> f64 { - let n = a.len() as f64; - if n == 0. { - return 0.; - } - let mean = a.sum() / n; - (a.fold(0., |acc, &x| acc + (x - mean).powi(2)) / n).sqrt() -} - -fn std(a: &Array2, axis: Axis) -> Array1 { - a.map_axis(axis, std1d) -} - +#[cfg(feature = "std")] fn main() { - // "recreating the following" + // This example recreates the following from python/numpy // counts -= np.mean(counts, axis=0) // counts /= np.std(counts, axis=0) let mut data = array![[-1., -2., -3.], [1., -3., 5.], [2., 2., 2.]]; println!("{:8.4}", data); - println!("{:8.4} (Mean axis=0)", data.mean_axis(Axis(0)).unwrap()); + println!("Mean along axis=0 (along columns):\n{:8.4}", data.mean_axis(Axis(0)).unwrap()); data -= &data.mean_axis(Axis(0)).unwrap(); - println!("{:8.4}", data); + println!("Centered around mean:\n{:8.4}", data); - data /= &std(&data, Axis(0)); - println!("{:8.4}", data); + data /= &data.std_axis(Axis(0), 0.); + println!("Scaled to normalize std:\n{:8.4}", data); + + println!("New mean:\n{:8.4}", data.mean_axis(Axis(0)).unwrap()); + println!("New std: \n{:8.4}", data.std_axis(Axis(0), 0.)); } + +#[cfg(not(feature = "std"))] +fn main() {} diff --git a/examples/life.rs b/examples/life.rs index 48f1d609f..e0675ae17 100644 --- a/examples/life.rs +++ b/examples/life.rs @@ -22,7 +22,7 @@ fn parse(x: &[u8]) -> Board { _ => None, })); - let a = a.into_shape((N, N)).unwrap(); + let a = a.into_shape_with_order((N, N)).unwrap(); map.slice_mut(s![1..-1, 1..-1]).assign(&a); map } diff --git a/examples/sort-axis.rs b/examples/sort-axis.rs index d721571b2..2ff6ceb32 100644 --- a/examples/sort-axis.rs +++ b/examples/sort-axis.rs @@ -1,3 +1,7 @@ +//! This is an example of sorting arrays along an axis. +//! This file may not be so instructive except for advanced users, instead it +//! could be a "feature preview" before sorting is added to the main crate. +//! use ndarray::prelude::*; use ndarray::{Data, RemoveAxis, Zip}; @@ -162,7 +166,7 @@ where #[cfg(feature = "std")] fn main() { - let a = Array::linspace(0., 63., 64).into_shape((8, 8)).unwrap(); + let a = Array::linspace(0., 63., 64).into_shape_with_order((8, 8)).unwrap(); let strings = a.map(|x| x.to_string()); let perm = a.sort_axis_by(Axis(1), |i, j| a[[i, 0]] > a[[j, 0]]); diff --git a/examples/type_conversion.rs b/examples/type_conversion.rs new file mode 100644 index 000000000..7bec2542f --- /dev/null +++ b/examples/type_conversion.rs @@ -0,0 +1,119 @@ +#[cfg(feature = "approx")] +use std::convert::TryFrom; + +#[cfg(feature = "approx")] +use approx::assert_abs_diff_eq; +#[cfg(feature = "approx")] +use ndarray::prelude::*; + +#[cfg(feature = "approx")] +fn main() { + // Converting an array from one datatype to another is implemented with the + // `ArrayBase::mapv()` function. We pass a closure that is applied to each + // element independently. This allows for more control and flexiblity in + // converting types. + // + // Below, we illustrate four different approaches for the actual conversion + // in the closure. + // - `From` ensures lossless conversions known at compile time and is the + // best default choice. + // - `TryFrom` either converts data losslessly or panics, ensuring that the + // rest of the program does not continue with unexpected data. + // - `as` never panics and may silently convert in a lossy way, depending + // on the source and target datatypes. More details can be found in the + // reference: https://doc.rust-lang.org/reference/expressions/operator-expr.html#numeric-cast + // - Using custom logic in the closure, e.g. to clip values or for NaN + // handling in floats. + // + // For a brush-up on casting between numeric types in Rust, refer to: + // https://doc.rust-lang.org/rust-by-example/types/cast.html + + // Infallible, lossless conversion with `From` + // The trait `std::convert::From` is only implemented for conversions that + // can be guaranteed to be lossless at compile time. This is the safest + // approach. + let a_u8: Array = array![[1, 2, 3], [4, 5, 6]]; + let a_f32 = a_u8.mapv(|element| f32::from(element)); + assert_abs_diff_eq!(a_f32, array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]); + + // Fallible, lossless conversion with `TryFrom` + // `i8` numbers can be negative, in such a case, there is no perfect + // conversion to `u8` defined. In this example, all numbers are positive and + // in bounds and can be converted at runtime. But for unknown runtime input, + // this would panic with the message provided in `.expect()`. Note that you + // can also use `.unwrap()` to be more concise. + let a_i8: Array = array![120, 8, 0]; + let a_u8 = a_i8.mapv(|element| u8::try_from(element).expect("Could not convert i8 to u8")); + assert_eq!(a_u8, array![120u8, 8u8, 0u8]); + + // Unsigned to signed integer conversion with `as` + // A real-life example of this would be coordinates on a grid. + // A `usize` value can be larger than what fits into a `isize`, therefore, + // it would be safer to use `TryFrom`. Nevertheless, `as` can be used for + // either simplicity or performance. + // The example includes `usize::MAX` to illustrate potentially undesired + // behavior. It will be interpreted as -1 (noop-casting + 2-complement), see + // https://doc.rust-lang.org/reference/expressions/operator-expr.html#numeric-cast + let a_usize: Array = array![1, 2, 3, usize::MAX]; + let a_isize = a_usize.mapv(|element| element as isize); + assert_eq!(a_isize, array![1_isize, 2_isize, 3_isize, -1_isize]); + + // Simple upcasting with `as` + // Every `u8` fits perfectly into a `u32`, therefore this is a lossless + // conversion. + // Note that it is up to the programmer to ensure the validity of the + // conversion over the lifetime of a program. With type inference, subtle + // bugs can creep in since conversions with `as` will always compile, so a + // programmer might not notice that a prior lossless conversion became a + // lossy conversion. With `From`, this would be noticed at compile-time and + // with `TryFrom`, it would also be either handled or make the program + // panic. + let a_u8: Array = array![[1, 2, 3], [4, 5, 6]]; + let a_u32 = a_u8.mapv(|element| element as u32); + assert_eq!(a_u32, array![[1u32, 2u32, 3u32], [4u32, 5u32, 6u32]]); + + // Saturating cast with `as` + // The `as` keyword performs a *saturating cast* When casting floats to + // ints. This means that numbers which do not fit into the target datatype + // will silently be clipped to the maximum/minimum numbers. Since this is + // not obvious, we discourage the intentional use of casting with `as` with + // silent saturation and recommend a custom logic instead which makes the + // intent clear. + let a_f32: Array = array![ + 256.0, // saturated to 255 + 255.7, // saturated to 255 + 255.1, // saturated to 255 + 254.7, // rounded down to 254 by cutting the decimal part + 254.1, // rounded down to 254 by cutting the decimal part + -1.0, // saturated to 0 on the lower end + f32::INFINITY, // saturated to 255 + f32::NAN, // converted to zero + ]; + let a_u8 = a_f32.mapv(|element| element as u8); + assert_eq!(a_u8, array![255, 255, 255, 254, 254, 0, 255, 0]); + + // Custom mapping logic + // Given that we pass a closure for the conversion, we can also define + // custom logic to e.g. replace NaN values and clip others. This also + // makes the intent clear. + let a_f32: Array = array![ + 270.0, // clipped to 200 + -1.2, // clipped to 0 + 4.7, // rounded up to 5 instead of just stripping decimals + f32::INFINITY, // clipped to 200 + f32::NAN, // replaced with upper bound 200 + ]; + let a_u8_custom = a_f32.mapv(|element| { + if element == f32::INFINITY || element.is_nan() { + return 200; + } + if let Some(std::cmp::Ordering::Less) = element.partial_cmp(&0.0) { + return 0; + } + 200.min(element.round() as u8) + }); + assert_eq!(a_u8_custom, array![200, 0, 5, 200, 200]); +} + +#[cfg(not(feature = "approx"))] +fn main() {} diff --git a/examples/zip_many.rs b/examples/zip_many.rs index 40b855df2..e2c5169f2 100644 --- a/examples/zip_many.rs +++ b/examples/zip_many.rs @@ -9,45 +9,40 @@ use ndarray::prelude::*; use ndarray::Zip; fn main() { - let n = 16; + let n = 6; + let mut a = Array::::zeros((n, n)); - let mut b = Array::::from_elem((n, n), 1.); + let mut b = Array::::zeros((n, n)); for ((i, j), elt) in b.indexed_iter_mut() { - *elt /= 1. + (i + 2 * j) as f32; + *elt = 1. / (1. + (i + 2 * j) as f32); } let c = Array::::from_elem((n, n + 1), 1.7); let c = c.slice(s![.., ..-1]); - { - let a = a.view_mut().reversed_axes(); - azip!((a in a, &b in b.t()) *a = b); - } - assert_eq!(a, b); + // Using Zip for arithmetic ops across a, b, c + Zip::from(&mut a).and(&b).and(&c) + .for_each(|a, &b, &c| *a = b + c); + assert_eq!(a, &b + &c); + // and this is how to do the *same thing* with azip!() azip!((a in &mut a, &b in &b, &c in c) *a = b + c); - assert_eq!(a, &b + &c); + + println!("{:8.4}", a); // sum of each row - let ax = Axis(0); - let mut sums = Array::zeros(a.len_of(ax)); - azip!((s in &mut sums, a in a.axis_iter(ax)) *s = a.sum()); + let mut sums = Array::zeros(a.nrows()); + Zip::from(a.rows()).and(&mut sums) + .for_each(|row, sum| *sum = row.sum()); + // show sums as a column matrix + println!("{:8.4}", sums.insert_axis(Axis(1))); - // sum of each chunk + // sum of each 2x2 chunk let chunk_sz = (2, 2); let nchunks = (n / chunk_sz.0, n / chunk_sz.1); let mut sums = Array::zeros(nchunks); - azip!((s in &mut sums, a in a.exact_chunks(chunk_sz)) *s = a.sum()); - - // Let's imagine we split to parallelize - { - let (x, y) = Zip::indexed(&mut a).split(); - x.for_each(|(_, j), elt| { - *elt = elt.powi(j as i32); - }); - - y.for_each(|(_, j), elt| { - *elt = elt.powi(j as i32); - }); - } - println!("{:8.3?}", a); + + Zip::from(a.exact_chunks(chunk_sz)) + .and(&mut sums) + .for_each(|chunk, sum| *sum = chunk.sum()); + println!("{:8.4}", sums); } diff --git a/ndarray-rand/Cargo.toml b/ndarray-rand/Cargo.toml index 4127d5631..4e9bf265f 100644 --- a/ndarray-rand/Cargo.toml +++ b/ndarray-rand/Cargo.toml @@ -16,7 +16,7 @@ keywords = ["multidimensional", "matrix", "rand", "ndarray"] [dependencies] ndarray = { version = "0.15", path = ".." } rand_distr = "0.4.0" -quickcheck = { version = "0.9", default-features = false, optional = true } +quickcheck = { version = "1.0", default-features = false, optional = true } [dependencies.rand] version = "0.8.0" @@ -24,7 +24,7 @@ features = ["small_rng"] [dev-dependencies] rand_isaac = "0.3.0" -quickcheck = { version = "0.9", default-features = false } +quickcheck = { version = "1.0", default-features = false } [package.metadata.release] no-dev-version = true diff --git a/ndarray-rand/src/lib.rs b/ndarray-rand/src/lib.rs index e293eb561..448eb10d9 100644 --- a/ndarray-rand/src/lib.rs +++ b/ndarray-rand/src/lib.rs @@ -8,15 +8,15 @@ //! Constructors for randomized arrays: `rand` integration for `ndarray`. //! -//! See [**`RandomExt`**](trait.RandomExt.html) for usage examples. +//! See **[`RandomExt`]** for usage examples. //! //! ## Note //! //! `ndarray-rand` depends on [`rand` 0.8][rand]. //! //! [`rand`][rand] and [`rand_distr`][rand_distr] -//! are re-exported as sub-modules, [`ndarray_rand::rand`](rand/index.html) -//! and [`ndarray_rand::rand_distr`](rand_distr/index.html) respectively. +//! are re-exported as sub-modules, [`ndarray_rand::rand`](rand) +//! and [`ndarray_rand::rand_distr`](rand_distr) respectively. //! You can use these submodules for guaranteed version compatibility or //! convenience. //! @@ -60,7 +60,7 @@ pub mod rand_distr { /// Note that `SmallRng` is cheap to initialize and fast, but it may generate /// low-quality random numbers, and reproducibility is not guaranteed. See its /// documentation for information. You can select a different RNG with -/// [`.random_using()`](#tymethod.random_using). +/// [`.random_using()`](Self::random_using). pub trait RandomExt where S: RawData, @@ -293,8 +293,8 @@ where /// if lanes from the original array should only be sampled once (*without replacement*) or /// multiple times (*with replacement*). /// -/// [`sample_axis`]: trait.RandomExt.html#tymethod.sample_axis -/// [`sample_axis_using`]: trait.RandomExt.html#tymethod.sample_axis_using +/// [`sample_axis`]: RandomExt::sample_axis +/// [`sample_axis_using`]: RandomExt::sample_axis_using #[derive(Debug, Clone)] pub enum SamplingStrategy { WithReplacement, @@ -304,7 +304,7 @@ pub enum SamplingStrategy { // `Arbitrary` enables `quickcheck` to generate random `SamplingStrategy` values for testing. #[cfg(feature = "quickcheck")] impl Arbitrary for SamplingStrategy { - fn arbitrary(g: &mut G) -> Self { + fn arbitrary(g: &mut Gen) -> Self { if bool::arbitrary(g) { SamplingStrategy::WithReplacement } else { diff --git a/ndarray-rand/tests/tests.rs b/ndarray-rand/tests/tests.rs index d8b18fe56..5a0cd6c1b 100644 --- a/ndarray-rand/tests/tests.rs +++ b/ndarray-rand/tests/tests.rs @@ -5,7 +5,7 @@ use ndarray_rand::rand::{distributions::Distribution, thread_rng}; use ndarray::ShapeBuilder; use ndarray_rand::rand_distr::Uniform; use ndarray_rand::{RandomExt, SamplingStrategy}; -use quickcheck::quickcheck; +use quickcheck::{quickcheck, TestResult}; #[test] fn test_dim() { @@ -51,7 +51,8 @@ fn oversampling_without_replacement_should_panic() { } quickcheck! { - fn oversampling_with_replacement_is_fine(m: usize, n: usize) -> bool { + fn oversampling_with_replacement_is_fine(m: u8, n: u8) -> TestResult { + let (m, n) = (m as usize, n as usize); let a = Array::random((m, n), Uniform::new(0., 2.)); // Higher than the length of both axes let n_samples = m + n + 1; @@ -59,24 +60,28 @@ quickcheck! { // We don't want to deal with sampling from 0-length axes in this test if m != 0 { if !sampling_works(&a, SamplingStrategy::WithReplacement, Axis(0), n_samples) { - return false; + return TestResult::failed(); } + } else { + return TestResult::discard(); } // We don't want to deal with sampling from 0-length axes in this test if n != 0 { if !sampling_works(&a, SamplingStrategy::WithReplacement, Axis(1), n_samples) { - return false; + return TestResult::failed(); } + } else { + return TestResult::discard(); } - - true + TestResult::passed() } } #[cfg(feature = "quickcheck")] quickcheck! { - fn sampling_behaves_as_expected(m: usize, n: usize, strategy: SamplingStrategy) -> bool { + fn sampling_behaves_as_expected(m: u8, n: u8, strategy: SamplingStrategy) -> TestResult { + let (m, n) = (m as usize, n as usize); let a = Array::random((m, n), Uniform::new(0., 2.)); let mut rng = &mut thread_rng(); @@ -84,19 +89,23 @@ quickcheck! { if m != 0 { let n_row_samples = Uniform::from(1..m+1).sample(&mut rng); if !sampling_works(&a, strategy.clone(), Axis(0), n_row_samples) { - return false; + return TestResult::failed(); } + } else { + return TestResult::discard(); } // We don't want to deal with sampling from 0-length axes in this test if n != 0 { let n_col_samples = Uniform::from(1..n+1).sample(&mut rng); if !sampling_works(&a, strategy, Axis(1), n_col_samples) { - return false; + return TestResult::failed(); } + } else { + return TestResult::discard(); } - true + TestResult::passed() } } diff --git a/scripts/all-tests.sh b/scripts/all-tests.sh index 98f4e3390..cbea6dba7 100755 --- a/scripts/all-tests.sh +++ b/scripts/all-tests.sh @@ -6,6 +6,25 @@ set -e FEATURES=$1 CHANNEL=$2 +if [ "$CHANNEL" = "1.57.0" ]; then + cargo update --package openblas-src --precise 0.10.5 + cargo update --package openblas-build --precise 0.10.5 + cargo update --package once_cell --precise 1.14.0 + cargo update --package byteorder --precise 1.4.3 + cargo update --package rayon --precise 1.5.3 + cargo update --package rayon-core --precise 1.9.3 + cargo update --package crossbeam-channel --precise 0.5.8 + cargo update --package crossbeam-deque --precise 0.8.3 + cargo update --package crossbeam-epoch --precise 0.9.15 + cargo update --package crossbeam-utils --precise 0.8.16 + cargo update --package rmp --precise 0.8.11 + cargo update --package serde_json --precise 1.0.99 + cargo update --package serde --precise 1.0.156 + cargo update --package thiserror --precise 1.0.39 + cargo update --package quote --precise 1.0.30 + cargo update --package proc-macro2 --precise 1.0.65 +fi + cargo build --verbose --no-default-features # Testing both dev and release profiles helps find bugs, especially in low level code cargo test --verbose --no-default-features @@ -17,6 +36,6 @@ cargo test --manifest-path=ndarray-rand/Cargo.toml --features quickcheck --verbo cargo test --manifest-path=xtest-serialization/Cargo.toml --verbose cargo test --manifest-path=xtest-blas/Cargo.toml --verbose --features openblas-system cargo test --examples -CARGO_TARGET_DIR=target/ cargo test --manifest-path=xtest-numeric/Cargo.toml --verbose -CARGO_TARGET_DIR=target/ cargo test --manifest-path=xtest-numeric/Cargo.toml --verbose --features test_blas +cargo test --manifest-path=xtest-numeric/Cargo.toml --verbose +cargo test --manifest-path=xtest-numeric/Cargo.toml --verbose --features test_blas ([ "$CHANNEL" != "nightly" ] || cargo bench --no-run --verbose --features "$FEATURES") diff --git a/scripts/cross-tests.sh b/scripts/cross-tests.sh index 7c4f13111..dc27058f8 100755 --- a/scripts/cross-tests.sh +++ b/scripts/cross-tests.sh @@ -11,4 +11,4 @@ cross build -v --features="$FEATURES" --target=$TARGET cross test -v --no-fail-fast --features="$FEATURES" --target=$TARGET cross test -v --no-fail-fast --target=$TARGET --manifest-path=ndarray-rand/Cargo.toml --features quickcheck cross test -v --no-fail-fast --target=$TARGET --manifest-path=xtest-serialization/Cargo.toml --verbose -CARGO_TARGET_DIR=target/ cross test -v --no-fail-fast --target=$TARGET --manifest-path=xtest-numeric/Cargo.toml +cross test -v --no-fail-fast --target=$TARGET --manifest-path=xtest-numeric/Cargo.toml --release diff --git a/src/aliases.rs b/src/aliases.rs index d41c888a6..9a8ea8f2c 100644 --- a/src/aliases.rs +++ b/src/aliases.rs @@ -7,43 +7,43 @@ use crate::{ArcArray, Array, ArrayView, ArrayViewMut, Ix, IxDynImpl}; /// Create a zero-dimensional index #[allow(non_snake_case)] #[inline(always)] -pub fn Ix0() -> Ix0 { +pub const fn Ix0() -> Ix0 { Dim::new([]) } /// Create a one-dimensional index #[allow(non_snake_case)] #[inline(always)] -pub fn Ix1(i0: Ix) -> Ix1 { +pub const fn Ix1(i0: Ix) -> Ix1 { Dim::new([i0]) } /// Create a two-dimensional index #[allow(non_snake_case)] #[inline(always)] -pub fn Ix2(i0: Ix, i1: Ix) -> Ix2 { +pub const fn Ix2(i0: Ix, i1: Ix) -> Ix2 { Dim::new([i0, i1]) } /// Create a three-dimensional index #[allow(non_snake_case)] #[inline(always)] -pub fn Ix3(i0: Ix, i1: Ix, i2: Ix) -> Ix3 { +pub const fn Ix3(i0: Ix, i1: Ix, i2: Ix) -> Ix3 { Dim::new([i0, i1, i2]) } /// Create a four-dimensional index #[allow(non_snake_case)] #[inline(always)] -pub fn Ix4(i0: Ix, i1: Ix, i2: Ix, i3: Ix) -> Ix4 { +pub const fn Ix4(i0: Ix, i1: Ix, i2: Ix, i3: Ix) -> Ix4 { Dim::new([i0, i1, i2, i3]) } /// Create a five-dimensional index #[allow(non_snake_case)] #[inline(always)] -pub fn Ix5(i0: Ix, i1: Ix, i2: Ix, i3: Ix, i4: Ix) -> Ix5 { +pub const fn Ix5(i0: Ix, i1: Ix, i2: Ix, i3: Ix, i4: Ix) -> Ix5 { Dim::new([i0, i1, i2, i3, i4]) } /// Create a six-dimensional index #[allow(non_snake_case)] #[inline(always)] -pub fn Ix6(i0: Ix, i1: Ix, i2: Ix, i3: Ix, i4: Ix, i5: Ix) -> Ix6 { +pub const fn Ix6(i0: Ix, i1: Ix, i2: Ix, i3: Ix, i4: Ix, i5: Ix) -> Ix6 { Dim::new([i0, i1, i2, i3, i4, i5]) } diff --git a/src/array_approx.rs b/src/array_approx.rs index bb6804a27..a40982a56 100644 --- a/src/array_approx.rs +++ b/src/array_approx.rs @@ -1,181 +1,202 @@ -use crate::imp_prelude::*; -use crate::Zip; -use approx::{AbsDiffEq, RelativeEq, UlpsEq}; - -/// **Requires crate feature `"approx"`** -impl AbsDiffEq> for ArrayBase -where - A: AbsDiffEq, - A::Epsilon: Clone, - S: Data, - S2: Data, - D: Dimension, -{ - type Epsilon = A::Epsilon; - - fn default_epsilon() -> A::Epsilon { - A::default_epsilon() - } +#[cfg(feature = "approx")] +mod approx_methods { + use crate::imp_prelude::*; - fn abs_diff_eq(&self, other: &ArrayBase, epsilon: A::Epsilon) -> bool { - if self.shape() != other.shape() { - return false; + impl ArrayBase + where + S: Data, + D: Dimension, + { + /// A test for equality that uses the elementwise absolute difference to compute the + /// approximate equality of two arrays. + /// + /// **Requires crate feature `"approx"`** + pub fn abs_diff_eq(&self, other: &ArrayBase, epsilon: A::Epsilon) -> bool + where + A: ::approx::AbsDiffEq, + A::Epsilon: Clone, + S2: Data, + { + >::abs_diff_eq(self, other, epsilon) } - Zip::from(self) - .and(other) - .all(move |a, b| A::abs_diff_eq(a, b, epsilon.clone())) - } -} - -/// **Requires crate feature `"approx"`** -impl RelativeEq> for ArrayBase -where - A: RelativeEq, - A::Epsilon: Clone, - S: Data, - S2: Data, - D: Dimension, -{ - fn default_max_relative() -> A::Epsilon { - A::default_max_relative() - } - - fn relative_eq( - &self, - other: &ArrayBase, - epsilon: A::Epsilon, - max_relative: A::Epsilon, - ) -> bool { - if self.shape() != other.shape() { - return false; + /// A test for equality that uses an elementwise relative comparison if the values are far + /// apart; and the absolute difference otherwise. + /// + /// **Requires crate feature `"approx"`** + pub fn relative_eq( + &self, + other: &ArrayBase, + epsilon: A::Epsilon, + max_relative: A::Epsilon, + ) -> bool + where + A: ::approx::RelativeEq, + A::Epsilon: Clone, + S2: Data, + { + >::relative_eq(self, other, epsilon, max_relative) } - - Zip::from(self) - .and(other) - .all(move |a, b| A::relative_eq(a, b, epsilon.clone(), max_relative.clone())) } } -/// **Requires crate feature `"approx"`** -impl UlpsEq> for ArrayBase -where - A: UlpsEq, - A::Epsilon: Clone, - S: Data, - S2: Data, - D: Dimension, -{ - fn default_max_ulps() -> u32 { - A::default_max_ulps() - } - - fn ulps_eq(&self, other: &ArrayBase, epsilon: A::Epsilon, max_ulps: u32) -> bool { - if self.shape() != other.shape() { - return false; +macro_rules! impl_approx_traits { + ($approx:ident, $doc:expr) => { + mod $approx { + use crate::imp_prelude::*; + use crate::Zip; + use $approx::{AbsDiffEq, RelativeEq, UlpsEq}; + + #[doc = $doc] + impl AbsDiffEq> for ArrayBase + where + A: AbsDiffEq, + A::Epsilon: Clone, + S: Data, + S2: Data, + D: Dimension, + { + type Epsilon = A::Epsilon; + + fn default_epsilon() -> A::Epsilon { + A::default_epsilon() + } + + fn abs_diff_eq(&self, other: &ArrayBase, epsilon: A::Epsilon) -> bool { + if self.shape() != other.shape() { + return false; + } + + Zip::from(self) + .and(other) + .all(move |a, b| A::abs_diff_eq(a, b, epsilon.clone())) + } + } + + #[doc = $doc] + impl RelativeEq> for ArrayBase + where + A: RelativeEq, + A::Epsilon: Clone, + S: Data, + S2: Data, + D: Dimension, + { + fn default_max_relative() -> A::Epsilon { + A::default_max_relative() + } + + fn relative_eq( + &self, + other: &ArrayBase, + epsilon: A::Epsilon, + max_relative: A::Epsilon, + ) -> bool { + if self.shape() != other.shape() { + return false; + } + + Zip::from(self).and(other).all(move |a, b| { + A::relative_eq(a, b, epsilon.clone(), max_relative.clone()) + }) + } + } + + #[doc = $doc] + impl UlpsEq> for ArrayBase + where + A: UlpsEq, + A::Epsilon: Clone, + S: Data, + S2: Data, + D: Dimension, + { + fn default_max_ulps() -> u32 { + A::default_max_ulps() + } + + fn ulps_eq( + &self, + other: &ArrayBase, + epsilon: A::Epsilon, + max_ulps: u32, + ) -> bool { + if self.shape() != other.shape() { + return false; + } + + Zip::from(self) + .and(other) + .all(move |a, b| A::ulps_eq(a, b, epsilon.clone(), max_ulps)) + } + } + + #[cfg(test)] + mod tests { + use crate::prelude::*; + use alloc::vec; + use $approx::{ + assert_abs_diff_eq, assert_abs_diff_ne, assert_relative_eq, assert_relative_ne, + assert_ulps_eq, assert_ulps_ne, + }; + + #[test] + fn abs_diff_eq() { + let a: Array2 = array![[0., 2.], [-0.000010001, 100000000.]]; + let mut b: Array2 = array![[0., 1.], [-0.000010002, 100000001.]]; + assert_abs_diff_ne!(a, b); + b[(0, 1)] = 2.; + assert_abs_diff_eq!(a, b); + + // Check epsilon. + assert_abs_diff_eq!(array![0.0f32], array![1e-40f32], epsilon = 1e-40f32); + assert_abs_diff_ne!(array![0.0f32], array![1e-40f32], epsilon = 1e-41f32); + + // Make sure we can compare different shapes without failure. + let c = array![[1., 2.]]; + assert_abs_diff_ne!(a, c); + } + + #[test] + fn relative_eq() { + let a: Array2 = array![[1., 2.], [-0.000010001, 100000000.]]; + let mut b: Array2 = array![[1., 1.], [-0.000010002, 100000001.]]; + assert_relative_ne!(a, b); + b[(0, 1)] = 2.; + assert_relative_eq!(a, b); + + // Check epsilon. + assert_relative_eq!(array![0.0f32], array![1e-40f32], epsilon = 1e-40f32); + assert_relative_ne!(array![0.0f32], array![1e-40f32], epsilon = 1e-41f32); + + // Make sure we can compare different shapes without failure. + let c = array![[1., 2.]]; + assert_relative_ne!(a, c); + } + + #[test] + fn ulps_eq() { + let a: Array2 = array![[1., 2.], [-0.000010001, 100000000.]]; + let mut b: Array2 = array![[1., 1.], [-0.000010002, 100000001.]]; + assert_ulps_ne!(a, b); + b[(0, 1)] = 2.; + assert_ulps_eq!(a, b); + + // Check epsilon. + assert_ulps_eq!(array![0.0f32], array![1e-40f32], epsilon = 1e-40f32); + assert_ulps_ne!(array![0.0f32], array![1e-40f32], epsilon = 1e-41f32); + + // Make sure we can compare different shapes without failure. + let c = array![[1., 2.]]; + assert_ulps_ne!(a, c); + } + } } - - Zip::from(self) - .and(other) - .all(move |a, b| A::ulps_eq(a, b, epsilon.clone(), max_ulps)) - } -} - -impl ArrayBase -where - S: Data, - D: Dimension, -{ - /// A test for equality that uses the elementwise absolute difference to compute the - /// approximate equality of two arrays. - /// - /// **Requires crate feature `"approx"`** - pub fn abs_diff_eq(&self, other: &ArrayBase, epsilon: A::Epsilon) -> bool - where - A: AbsDiffEq, - A::Epsilon: Clone, - S2: Data, - { - >::abs_diff_eq(self, other, epsilon) - } - - /// A test for equality that uses an elementwise relative comparison if the values are far - /// apart; and the absolute difference otherwise. - /// - /// **Requires crate feature `"approx"`** - pub fn relative_eq( - &self, - other: &ArrayBase, - epsilon: A::Epsilon, - max_relative: A::Epsilon, - ) -> bool - where - A: RelativeEq, - A::Epsilon: Clone, - S2: Data - { - >::relative_eq(self, other, epsilon, max_relative) - } -} - - -#[cfg(test)] -mod tests { - use crate::prelude::*; - use alloc::vec; - use approx::{ - assert_abs_diff_eq, assert_abs_diff_ne, assert_relative_eq, assert_relative_ne, - assert_ulps_eq, assert_ulps_ne, }; +} - #[test] - fn abs_diff_eq() { - let a: Array2 = array![[0., 2.], [-0.000010001, 100000000.]]; - let mut b: Array2 = array![[0., 1.], [-0.000010002, 100000001.]]; - assert_abs_diff_ne!(a, b); - b[(0, 1)] = 2.; - assert_abs_diff_eq!(a, b); - - // Check epsilon. - assert_abs_diff_eq!(array![0.0f32], array![1e-40f32], epsilon = 1e-40f32); - assert_abs_diff_ne!(array![0.0f32], array![1e-40f32], epsilon = 1e-41f32); - - // Make sure we can compare different shapes without failure. - let c = array![[1., 2.]]; - assert_abs_diff_ne!(a, c); - } +#[cfg(feature = "approx")] +impl_approx_traits!(approx, "**Requires crate feature `\"approx\"`.**"); - #[test] - fn relative_eq() { - let a: Array2 = array![[1., 2.], [-0.000010001, 100000000.]]; - let mut b: Array2 = array![[1., 1.], [-0.000010002, 100000001.]]; - assert_relative_ne!(a, b); - b[(0, 1)] = 2.; - assert_relative_eq!(a, b); - - // Check epsilon. - assert_relative_eq!(array![0.0f32], array![1e-40f32], epsilon = 1e-40f32); - assert_relative_ne!(array![0.0f32], array![1e-40f32], epsilon = 1e-41f32); - - // Make sure we can compare different shapes without failure. - let c = array![[1., 2.]]; - assert_relative_ne!(a, c); - } - - #[test] - fn ulps_eq() { - let a: Array2 = array![[1., 2.], [-0.000010001, 100000000.]]; - let mut b: Array2 = array![[1., 1.], [-0.000010002, 100000001.]]; - assert_ulps_ne!(a, b); - b[(0, 1)] = 2.; - assert_ulps_eq!(a, b); - - // Check epsilon. - assert_ulps_eq!(array![0.0f32], array![1e-40f32], epsilon = 1e-40f32); - assert_ulps_ne!(array![0.0f32], array![1e-40f32], epsilon = 1e-41f32); - - // Make sure we can compare different shapes without failure. - let c = array![[1., 2.]]; - assert_ulps_ne!(a, c); - } -} +#[cfg(feature = "approx-0_5")] +impl_approx_traits!(approx_0_5, "**Requires crate feature `\"approx-0_5\"`.**"); diff --git a/src/array_serde.rs b/src/array_serde.rs index 41f7d60c1..a6f3c617c 100644 --- a/src/array_serde.rs +++ b/src/array_serde.rs @@ -12,6 +12,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::fmt; use std::marker::PhantomData; use alloc::format; +#[cfg(not(feature = "std"))] use alloc::vec::Vec; use crate::imp_prelude::*; @@ -276,17 +277,17 @@ where let _v = match v { Some(v) => v, - None => Err(de::Error::missing_field("v"))?, + None => return Err(de::Error::missing_field("v")), }; let data = match data { Some(data) => data, - None => Err(de::Error::missing_field("data"))?, + None => return Err(de::Error::missing_field("data")), }; let dim = match dim { Some(dim) => dim, - None => Err(de::Error::missing_field("dim"))?, + None => return Err(de::Error::missing_field("dim")), }; if let Ok(array) = ArrayBase::from_shape_vec(dim, data) { diff --git a/src/arrayformat.rs b/src/arrayformat.rs index c1ca352e3..ec5b041d9 100644 --- a/src/arrayformat.rs +++ b/src/arrayformat.rs @@ -50,10 +50,8 @@ impl FormatOptions { self.axis_collapse_limit = std::usize::MAX; self.axis_collapse_limit_next_last = std::usize::MAX; self.axis_collapse_limit_last = std::usize::MAX; - self - } else { - self } + self } /// Axis length collapse limit before ellipsizing, where `axis_rindex` is @@ -191,7 +189,7 @@ where /// to each element. /// /// The array is shown in multiline style. -impl<'a, A: fmt::Display, S, D: Dimension> fmt::Display for ArrayBase +impl fmt::Display for ArrayBase where S: Data, { @@ -205,7 +203,7 @@ where /// to each element. /// /// The array is shown in multiline style. -impl<'a, A: fmt::Debug, S, D: Dimension> fmt::Debug for ArrayBase +impl fmt::Debug for ArrayBase where S: Data, { @@ -219,7 +217,7 @@ where ", shape={:?}, strides={:?}, layout={:?}", self.shape(), self.strides(), - layout = self.view().layout() + self.view().layout(), )?; match D::NDIM { Some(ndim) => write!(f, ", const ndim={}", ndim)?, @@ -233,7 +231,7 @@ where /// to each element. /// /// The array is shown in multiline style. -impl<'a, A: fmt::LowerExp, S, D: Dimension> fmt::LowerExp for ArrayBase +impl fmt::LowerExp for ArrayBase where S: Data, { @@ -247,7 +245,7 @@ where /// to each element. /// /// The array is shown in multiline style. -impl<'a, A: fmt::UpperExp, S, D: Dimension> fmt::UpperExp for ArrayBase +impl fmt::UpperExp for ArrayBase where S: Data, { @@ -260,7 +258,7 @@ where /// to each element. /// /// The array is shown in multiline style. -impl<'a, A: fmt::LowerHex, S, D: Dimension> fmt::LowerHex for ArrayBase +impl fmt::LowerHex for ArrayBase where S: Data, { @@ -274,7 +272,7 @@ where /// to each element. /// /// The array is shown in multiline style. -impl<'a, A: fmt::Binary, S, D: Dimension> fmt::Binary for ArrayBase +impl fmt::Binary for ArrayBase where S: Data, { @@ -287,8 +285,9 @@ where #[cfg(test)] mod formatting_with_omit { use itertools::Itertools; - use std::fmt; + #[cfg(not(feature = "std"))] use alloc::string::String; + #[cfg(not(feature = "std"))] use alloc::vec::Vec; use super::*; diff --git a/src/arraytraits.rs b/src/arraytraits.rs index 23f99b8fb..8d44c1e72 100644 --- a/src/arraytraits.rs +++ b/src/arraytraits.rs @@ -6,19 +6,21 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::hash; -use std::iter::FromIterator; -use std::iter::IntoIterator; +#[cfg(not(feature = "std"))] +use alloc::boxed::Box; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; use std::mem; use std::ops::{Index, IndexMut}; -use alloc::vec::Vec; +use std::{hash, mem::size_of}; +use std::{iter::FromIterator, slice}; use crate::imp_prelude::*; -use crate::iter::{Iter, IterMut}; -use crate::NdIndex; - -use crate::numeric_util; -use crate::{FoldWhile, Zip}; +use crate::{ + dimension, + iter::{Iter, IterMut}, + numeric_util, FoldWhile, NdIndex, Zip, +}; #[cold] #[inline(never)] @@ -114,6 +116,7 @@ where /// Return `true` if the array shapes and all elements of `self` and /// `rhs` are equal. Return `false` otherwise. +#[allow(clippy::unconditional_recursion)] // false positive impl<'a, A, B, S, S2, D> PartialEq<&'a ArrayBase> for ArrayBase where A: PartialEq, @@ -128,6 +131,7 @@ where /// Return `true` if the array shapes and all elements of `self` and /// `rhs` are equal. Return `false` otherwise. +#[allow(clippy::unconditional_recursion)] // false positive impl<'a, A, B, S, S2, D> PartialEq> for &'a ArrayBase where A: PartialEq, @@ -148,6 +152,18 @@ where { } +impl From> for ArrayBase +where + S: DataOwned, +{ + /// Create a one-dimensional array from a boxed slice (no copying needed). + /// + /// **Panics** if the length is greater than `isize::MAX`. + fn from(b: Box<[A]>) -> Self { + Self::from_vec(b.into_vec()) + } +} + impl From> for ArrayBase where S: DataOwned, @@ -176,7 +192,6 @@ where /// /// ```rust /// use ndarray::{Array, arr1}; - /// use std::iter::FromIterator; /// /// // Either use `from_iter` directly or use `Iterator::collect`. /// let array = Array::from_iter((0..5).map(|x| x * x)); @@ -240,7 +255,7 @@ where } } -impl<'a, S, D> hash::Hash for ArrayBase +impl hash::Hash for ArrayBase where D: Dimension, S: Data, @@ -252,7 +267,7 @@ where if let Some(self_s) = self.as_slice() { hash::Hash::hash_slice(self_s, state); } else { - for row in self.inner_rows() { + for row in self.rows() { if let Some(row_s) = row.as_slice() { hash::Hash::hash_slice(row_s, state); } else { @@ -286,12 +301,16 @@ where { } -#[cfg(any(feature = "serde"))] +#[cfg(feature = "serde")] // Use version number so we can add a packed format later. pub const ARRAY_FORMAT_VERSION: u8 = 1u8; // use "raw" form instead of type aliases here so that they show up in docs -/// Implementation of `ArrayView::from(&S)` where `S` is a slice or slicable. +/// Implementation of `ArrayView::from(&S)` where `S` is a slice or sliceable. +/// +/// **Panics** if the length of the slice overflows `isize`. (This can only +/// occur if `A` is zero-sized, because slices cannot contain more than +/// `isize::MAX` number of bytes.) impl<'a, A, Slice: ?Sized> From<&'a Slice> for ArrayView<'a, A, Ix1> where Slice: AsRef<[A]>, @@ -300,14 +319,19 @@ where /// /// **Panics** if the slice length is greater than `isize::MAX`. fn from(slice: &'a Slice) -> Self { - let xs = slice.as_ref(); - if mem::size_of::() == 0 { - assert!( - xs.len() <= ::std::isize::MAX as usize, - "Slice length must fit in `isize`.", - ); - } - unsafe { Self::from_shape_ptr(xs.len(), xs.as_ptr()) } + aview1(slice.as_ref()) + } +} + +/// Implementation of ArrayView2::from(&S) where S is a slice to a 2D array +/// +/// **Panics** if the product of non-zero axis lengths overflows `isize`. (This +/// can only occur if A is zero-sized or if `N` is zero, because slices cannot +/// contain more than `isize::MAX` number of bytes.) +impl<'a, A, const N: usize> From<&'a [[A; N]]> for ArrayView<'a, A, Ix2> { + /// Create a two-dimensional read-only array view of the data in `slice` + fn from(xs: &'a [[A; N]]) -> Self { + aview2(xs) } } @@ -323,7 +347,7 @@ where } } -/// Implementation of `ArrayViewMut::from(&mut S)` where `S` is a slice or slicable. +/// Implementation of `ArrayViewMut::from(&mut S)` where `S` is a slice or sliceable. impl<'a, A, Slice: ?Sized> From<&'a mut Slice> for ArrayViewMut<'a, A, Ix1> where Slice: AsMut<[A]>, @@ -343,6 +367,36 @@ where } } +/// Implementation of ArrayViewMut2::from(&S) where S is a slice to a 2D array +/// +/// **Panics** if the product of non-zero axis lengths overflows `isize`. (This +/// can only occur if `A` is zero-sized or if `N` is zero, because slices +/// cannot contain more than `isize::MAX` number of bytes.) +impl<'a, A, const N: usize> From<&'a mut [[A; N]]> for ArrayViewMut<'a, A, Ix2> { + /// Create a two-dimensional read-write array view of the data in `slice` + fn from(xs: &'a mut [[A; N]]) -> Self { + let cols = N; + let rows = xs.len(); + let dim = Ix2(rows, cols); + if size_of::() == 0 { + dimension::size_of_shape_checked(&dim) + .expect("Product of non-zero axis lengths must not overflow isize."); + } else if N == 0 { + assert!( + xs.len() <= isize::MAX as usize, + "Product of non-zero axis lengths must not overflow isize.", + ); + } + + // `cols * rows` is guaranteed to fit in `isize` because we checked that it fits in + // `isize::MAX` + unsafe { + let data = slice::from_raw_parts_mut(xs.as_mut_ptr() as *mut A, cols * rows); + ArrayViewMut::from_shape_ptr(dim, data.as_mut_ptr()) + } + } +} + /// Implementation of `ArrayViewMut::from(&mut A)` where `A` is an array. impl<'a, A, S, D> From<&'a mut ArrayBase> for ArrayViewMut<'a, A, D> where @@ -355,6 +409,15 @@ where } } +impl From> for ArcArray +where + D: Dimension, +{ + fn from(arr: Array) -> ArcArray { + arr.into_shared() + } +} + /// Argument conversion into an array view /// /// The trait is parameterized over `A`, the element type, and `D`, the diff --git a/src/data_repr.rs b/src/data_repr.rs index b34f0a4ca..f740988f4 100644 --- a/src/data_repr.rs +++ b/src/data_repr.rs @@ -2,14 +2,18 @@ use std::mem; use std::mem::ManuallyDrop; use std::ptr::NonNull; use alloc::slice; +#[cfg(not(feature = "std"))] use alloc::borrow::ToOwned; +#[cfg(not(feature = "std"))] use alloc::vec::Vec; use crate::extension::nonnull; +use rawpointer::PointerExt; + /// Array's representation. /// /// *Don’t use this type directly—use the type alias -/// [`Array`](type.Array.html) for the array type!* +/// [`Array`](crate::Array) for the array type!* // Like a Vec, but with non-unique ownership semantics // // repr(C) to make it transmutable OwnedRepr -> OwnedRepr if @@ -51,10 +55,52 @@ impl OwnedRepr { self.ptr.as_ptr() } + pub(crate) fn as_ptr_mut(&self) -> *mut A { + self.ptr.as_ptr() + } + pub(crate) fn as_nonnull_mut(&mut self) -> NonNull { self.ptr } + /// Return end pointer + pub(crate) fn as_end_nonnull(&self) -> NonNull { + unsafe { + self.ptr.add(self.len) + } + } + + /// Reserve `additional` elements; return the new pointer + /// + /// ## Safety + /// + /// Note that existing pointers into the data are invalidated + #[must_use = "must use new pointer to update existing pointers"] + pub(crate) fn reserve(&mut self, additional: usize) -> NonNull { + self.modify_as_vec(|mut v| { + v.reserve(additional); + v + }); + self.as_nonnull_mut() + } + + /// Set the valid length of the data + /// + /// ## Safety + /// + /// The first `new_len` elements of the data should be valid. + pub(crate) unsafe fn set_len(&mut self, new_len: usize) { + debug_assert!(new_len <= self.capacity); + self.len = new_len; + } + + /// Return the length (number of elements in total) + pub(crate) fn release_all_elements(&mut self) -> usize { + let ret = self.len; + self.len = 0; + ret + } + /// Cast self into equivalent repr of other element type /// /// ## Safety @@ -72,6 +118,11 @@ impl OwnedRepr { } } + fn modify_as_vec(&mut self, f: impl FnOnce(Vec) -> Vec) { + let v = self.take_as_vec(); + *self = Self::from(f(v)); + } + fn take_as_vec(&mut self) -> Vec { let capacity = self.capacity; let len = self.len; diff --git a/src/data_traits.rs b/src/data_traits.rs index 7ac63d54e..50e5a3e6e 100644 --- a/src/data_traits.rs +++ b/src/data_traits.rs @@ -14,9 +14,12 @@ use std::mem::{self, size_of}; use std::mem::MaybeUninit; use std::ptr::NonNull; use alloc::sync::Arc; +#[cfg(not(feature = "std"))] use alloc::vec::Vec; -use crate::{ArrayBase, CowRepr, Dimension, OwnedArcRepr, OwnedRepr, RawViewRepr, ViewRepr}; +use crate::{ + ArcArray, Array, ArrayBase, CowRepr, Dimension, OwnedArcRepr, OwnedRepr, RawViewRepr, ViewRepr, +}; /// Array representation trait. /// @@ -27,14 +30,19 @@ use crate::{ArrayBase, CowRepr, Dimension, OwnedArcRepr, OwnedRepr, RawViewRepr, /// ***Note:*** `RawData` is not an extension interface at this point. /// Traits in Rust can serve many different roles. This trait is public because /// it is used as a bound on public methods. +#[allow(clippy::missing_safety_doc)] // not implementable downstream pub unsafe trait RawData: Sized { /// The array element type. type Elem; #[doc(hidden)] // This method is only used for debugging + #[deprecated(note="Unused", since="0.15.2")] fn _data_slice(&self) -> Option<&[Self::Elem]>; + #[doc(hidden)] + fn _is_pointer_inbounds(&self, ptr: *const Self::Elem) -> bool; + private_decl! {} } @@ -43,11 +51,15 @@ pub unsafe trait RawData: Sized { /// For an array with writable elements. /// /// ***Internal trait, see `RawData`.*** +#[allow(clippy::missing_safety_doc)] // not implementable downstream pub unsafe trait RawDataMut: RawData { /// If possible, ensures that the array has unique access to its data. /// - /// If `Self` provides safe mutable access to array elements, then it - /// **must** panic or ensure that the data is unique. + /// The implementer must ensure that if the input is contiguous, then the + /// output has the same strides as input. + /// + /// Additionally, if `Self` provides safe mutable access to array elements, + /// then this method **must** panic or ensure that the data is unique. #[doc(hidden)] fn try_ensure_unique(_: &mut ArrayBase) where @@ -67,6 +79,7 @@ pub unsafe trait RawDataMut: RawData { /// An array representation that can be cloned. /// /// ***Internal trait, see `RawData`.*** +#[allow(clippy::missing_safety_doc)] // not implementable downstream pub unsafe trait RawDataClone: RawData { #[doc(hidden)] /// Unsafe because, `ptr` must point inside the current storage. @@ -89,20 +102,30 @@ pub unsafe trait RawDataClone: RawData { /// For an array with elements that can be accessed with safe code. /// /// ***Internal trait, see `RawData`.*** +#[allow(clippy::missing_safety_doc)] // not implementable downstream pub unsafe trait Data: RawData { /// Converts the array to a uniquely owned array, cloning elements if necessary. #[doc(hidden)] #[allow(clippy::wrong_self_convention)] - fn into_owned(self_: ArrayBase) -> ArrayBase, D> + fn into_owned(self_: ArrayBase) -> Array where Self::Elem: Clone, D: Dimension; + /// Converts the array into `Array` if this is possible without + /// cloning the array elements. Otherwise, returns `self_` unchanged. + #[doc(hidden)] + fn try_into_owned_nocopy( + self_: ArrayBase, + ) -> Result, ArrayBase> + where + D: Dimension; + /// Return a shared ownership (copy on write) array based on the existing one, /// cloning elements if necessary. #[doc(hidden)] #[allow(clippy::wrong_self_convention)] - fn to_shared(self_: &ArrayBase) -> ArrayBase, D> + fn to_shared(self_: &ArrayBase) -> ArcArray where Self::Elem: Clone, D: Dimension, @@ -124,6 +147,7 @@ pub unsafe trait Data: RawData { // `RawDataMut::try_ensure_unique` implementation always panics or ensures that // the data is unique. You are also guaranteeing that `try_is_unique` always // returns `Some(_)`. +#[allow(clippy::missing_safety_doc)] // not implementable downstream pub unsafe trait DataMut: Data + RawDataMut { /// Ensures that the array has unique access to its data. #[doc(hidden)] @@ -139,6 +163,7 @@ pub unsafe trait DataMut: Data + RawDataMut { /// Returns whether the array has unique access to its data. #[doc(hidden)] #[inline] + #[allow(clippy::wrong_self_convention)] // mut needed for Arc types fn is_unique(&mut self) -> bool { self.try_is_unique().unwrap() } @@ -146,9 +171,15 @@ pub unsafe trait DataMut: Data + RawDataMut { unsafe impl RawData for RawViewRepr<*const A> { type Elem = A; + + #[inline] fn _data_slice(&self) -> Option<&[A]> { None } + + #[inline(always)] + fn _is_pointer_inbounds(&self, _ptr: *const Self::Elem) -> bool { true } + private_impl! {} } @@ -160,9 +191,15 @@ unsafe impl RawDataClone for RawViewRepr<*const A> { unsafe impl RawData for RawViewRepr<*mut A> { type Elem = A; + + #[inline] fn _data_slice(&self) -> Option<&[A]> { None } + + #[inline(always)] + fn _is_pointer_inbounds(&self, _ptr: *const Self::Elem) -> bool { true } + private_impl! {} } @@ -192,6 +229,11 @@ unsafe impl RawData for OwnedArcRepr { fn _data_slice(&self) -> Option<&[A]> { Some(self.0.as_slice()) } + + fn _is_pointer_inbounds(&self, self_ptr: *const Self::Elem) -> bool { + self.0._is_pointer_inbounds(self_ptr) + } + private_impl! {} } @@ -209,14 +251,9 @@ where return; } if self_.dim.size() <= self_.data.0.len() / 2 { - // Create a new vec if the current view is less than half of - // backing data. - unsafe { - *self_ = ArrayBase::from_shape_vec_unchecked( - self_.dim.clone(), - self_.iter().cloned().collect(), - ); - } + // Clone only the visible elements if the current view is less than + // half of backing data. + *self_ = self_.to_owned().into_shared(); return; } let rcvec = &mut self_.data.0; @@ -238,7 +275,7 @@ where } unsafe impl Data for OwnedArcRepr { - fn into_owned(mut self_: ArrayBase) -> ArrayBase, D> + fn into_owned(mut self_: ArrayBase) -> Array where A: Clone, D: Dimension, @@ -252,7 +289,29 @@ unsafe impl Data for OwnedArcRepr { } } - fn to_shared(self_: &ArrayBase) -> ArrayBase, D> + fn try_into_owned_nocopy( + self_: ArrayBase, + ) -> Result, ArrayBase> + where + D: Dimension, + { + match Arc::try_unwrap(self_.data.0) { + Ok(owned_data) => unsafe { + // Safe because the data is equivalent. + Ok(ArrayBase::from_data_ptr(owned_data, self_.ptr) + .with_strides_dim(self_.strides, self_.dim)) + }, + Err(arc_data) => unsafe { + // Safe because the data is equivalent; we're just + // reconstructing `self_`. + Err(ArrayBase::from_data_ptr(OwnedArcRepr(arc_data), self_.ptr) + .with_strides_dim(self_.strides, self_.dim)) + }, + } + } + + #[allow(clippy::wrong_self_convention)] + fn to_shared(self_: &ArrayBase) -> ArcArray where Self::Elem: Clone, D: Dimension, @@ -273,9 +332,18 @@ unsafe impl RawDataClone for OwnedArcRepr { unsafe impl RawData for OwnedRepr { type Elem = A; + fn _data_slice(&self) -> Option<&[A]> { Some(self.as_slice()) } + + fn _is_pointer_inbounds(&self, self_ptr: *const Self::Elem) -> bool { + let slc = self.as_slice(); + let ptr = slc.as_ptr() as *mut A; + let end = unsafe { ptr.add(slc.len()) }; + self_ptr >= ptr && self_ptr <= end + } + private_impl! {} } @@ -296,13 +364,23 @@ unsafe impl RawDataMut for OwnedRepr { unsafe impl Data for OwnedRepr { #[inline] - fn into_owned(self_: ArrayBase) -> ArrayBase, D> + fn into_owned(self_: ArrayBase) -> Array where A: Clone, D: Dimension, { self_ } + + #[inline] + fn try_into_owned_nocopy( + self_: ArrayBase, + ) -> Result, ArrayBase> + where + D: Dimension, + { + Ok(self_) + } } unsafe impl DataMut for OwnedRepr {} @@ -332,27 +410,42 @@ where } else { 0 }; - self.clone_from(&other); + self.clone_from(other); self.as_nonnull_mut().offset(our_off) } } unsafe impl<'a, A> RawData for ViewRepr<&'a A> { type Elem = A; + + #[inline] fn _data_slice(&self) -> Option<&[A]> { None } + + #[inline(always)] + fn _is_pointer_inbounds(&self, _ptr: *const Self::Elem) -> bool { true } + private_impl! {} } unsafe impl<'a, A> Data for ViewRepr<&'a A> { - fn into_owned(self_: ArrayBase) -> ArrayBase, D> + fn into_owned(self_: ArrayBase) -> Array where Self::Elem: Clone, D: Dimension, { self_.to_owned() } + + fn try_into_owned_nocopy( + self_: ArrayBase, + ) -> Result, ArrayBase> + where + D: Dimension, + { + Err(self_) + } } unsafe impl<'a, A> RawDataClone for ViewRepr<&'a A> { @@ -363,9 +456,15 @@ unsafe impl<'a, A> RawDataClone for ViewRepr<&'a A> { unsafe impl<'a, A> RawData for ViewRepr<&'a mut A> { type Elem = A; + + #[inline] fn _data_slice(&self) -> Option<&[A]> { None } + + #[inline(always)] + fn _is_pointer_inbounds(&self, _ptr: *const Self::Elem) -> bool { true } + private_impl! {} } @@ -385,13 +484,22 @@ unsafe impl<'a, A> RawDataMut for ViewRepr<&'a mut A> { } unsafe impl<'a, A> Data for ViewRepr<&'a mut A> { - fn into_owned(self_: ArrayBase) -> ArrayBase, D> + fn into_owned(self_: ArrayBase) -> Array where Self::Elem: Clone, D: Dimension, { self_.to_owned() } + + fn try_into_owned_nocopy( + self_: ArrayBase, + ) -> Result, ArrayBase> + where + D: Dimension, + { + Err(self_) + } } unsafe impl<'a, A> DataMut for ViewRepr<&'a mut A> {} @@ -408,6 +516,7 @@ unsafe impl<'a, A> DataMut for ViewRepr<&'a mut A> {} // The array storage must be initially mutable - copy on write arrays may require copying for // unsharing storage before mutating it. The initially allocated storage must be mutable so // that it can be mutated directly - through .raw_view_mut_unchecked() - for initialization. +#[allow(clippy::missing_safety_doc)] // not implementable downstream pub unsafe trait DataOwned: Data { /// Corresponding owned data with MaybeUninit elements type MaybeUninit: DataOwned> @@ -426,6 +535,7 @@ pub unsafe trait DataOwned: Data { /// A representation that is a lightweight view. /// /// ***Internal trait, see `Data`.*** +#[allow(clippy::missing_safety_doc)] // not implementable downstream pub unsafe trait DataShared: Clone + Data + RawDataClone {} unsafe impl DataShared for OwnedArcRepr {} @@ -457,12 +567,23 @@ unsafe impl DataOwned for OwnedArcRepr { unsafe impl<'a, A> RawData for CowRepr<'a, A> { type Elem = A; + fn _data_slice(&self) -> Option<&[A]> { + #[allow(deprecated)] match self { CowRepr::View(view) => view._data_slice(), CowRepr::Owned(data) => data._data_slice(), } } + + #[inline] + fn _is_pointer_inbounds(&self, ptr: *const Self::Elem) -> bool { + match self { + CowRepr::View(view) => view._is_pointer_inbounds(ptr), + CowRepr::Owned(data) => data._is_pointer_inbounds(ptr), + } + } + private_impl! {} } @@ -511,7 +632,6 @@ where } } - #[doc(hidden)] unsafe fn clone_from_with_ptr( &mut self, other: &Self, @@ -536,7 +656,7 @@ where unsafe impl<'a, A> Data for CowRepr<'a, A> { #[inline] - fn into_owned(self_: ArrayBase, D>) -> ArrayBase, D> + fn into_owned(self_: ArrayBase, D>) -> Array where A: Clone, D: Dimension, @@ -550,6 +670,22 @@ unsafe impl<'a, A> Data for CowRepr<'a, A> { }, } } + + fn try_into_owned_nocopy( + self_: ArrayBase, + ) -> Result, ArrayBase> + where + D: Dimension, + { + match self_.data { + CowRepr::View(_) => Err(self_), + CowRepr::Owned(data) => unsafe { + // safe because the data is equivalent so ptr, dims remain valid + Ok(ArrayBase::from_data_ptr(data, self_.ptr) + .with_strides_dim(self_.strides, self_.dim)) + }, + } + } } unsafe impl<'a, A> DataMut for CowRepr<'a, A> where A: Clone {} @@ -621,3 +757,13 @@ impl<'a, A: 'a, B: 'a> RawDataSubst for ViewRepr<&'a mut A> { } } +impl<'a, A: 'a, B: 'a> RawDataSubst for CowRepr<'a, A> { + type Output = CowRepr<'a, B>; + + unsafe fn data_subst(self) -> Self::Output { + match self { + CowRepr::View(view) => CowRepr::View(view.data_subst()), + CowRepr::Owned(owned) => CowRepr::Owned(owned.data_subst()), + } + } +} diff --git a/src/dimension/axes.rs b/src/dimension/axes.rs index d6f1c7280..5660675b5 100644 --- a/src/dimension/axes.rs +++ b/src/dimension/axes.rs @@ -48,8 +48,11 @@ pub struct Axes<'a, D> { /// Description of the axis, its length and its stride. #[derive(Debug)] pub struct AxisDescription { + /// Axis identifier (index) pub axis: Axis, + /// Length in count of elements of the current axis pub len: usize, + /// Stride in count of elements of the current axis pub stride: isize, } @@ -140,7 +143,6 @@ where trait IncOps: Copy { fn post_inc(&mut self) -> Self; - fn post_dec(&mut self) -> Self; fn pre_dec(&mut self) -> Self; } @@ -152,12 +154,6 @@ impl IncOps for usize { x } #[inline(always)] - fn post_dec(&mut self) -> Self { - let x = *self; - *self -= 1; - x - } - #[inline(always)] fn pre_dec(&mut self) -> Self { *self -= 1; *self diff --git a/src/dimension/conversion.rs b/src/dimension/conversion.rs index 6b53a4eef..f6c408a75 100644 --- a/src/dimension/conversion.rs +++ b/src/dimension/conversion.rs @@ -10,6 +10,7 @@ use num_traits::Zero; use std::ops::{Index, IndexMut}; +#[cfg(not(feature = "std"))] use alloc::vec::Vec; use crate::{Dim, Dimension, Ix, Ix1, IxDyn, IxDynImpl}; diff --git a/src/dimension/dim.rs b/src/dimension/dim.rs index 97de6d842..3f8ff23e3 100644 --- a/src/dimension/dim.rs +++ b/src/dimension/dim.rs @@ -18,7 +18,7 @@ use crate::Ix; /// `Dim` describes the number of axes and the length of each axis /// in an array. It is also used as an index type. /// -/// See also the [`Dimension` trait](trait.Dimension.html) for its methods and +/// See also the [`Dimension`] trait for its methods and /// operations. /// /// # Examples @@ -42,7 +42,7 @@ pub struct Dim { impl Dim { /// Private constructor and accessors for Dim - pub(crate) fn new(index: I) -> Dim { + pub(crate) const fn new(index: I) -> Dim { Dim { index } } #[inline(always)] diff --git a/src/dimension/dimension_trait.rs b/src/dimension/dimension_trait.rs index 1eace34a7..c594859e9 100644 --- a/src/dimension/dimension_trait.rs +++ b/src/dimension/dimension_trait.rs @@ -9,6 +9,7 @@ use std::fmt::Debug; use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}; use std::ops::{Index, IndexMut}; +#[cfg(not(feature = "std"))] use alloc::vec::Vec; use super::axes_of; @@ -83,14 +84,14 @@ pub trait Dimension: /// Compute the size of the dimension (number of elements) fn size(&self) -> usize { - self.slice().iter().fold(1, |s, &a| s * a as usize) + self.slice().iter().product() } /// Compute the size while checking for overflow. fn size_checked(&self) -> Option { self.slice() .iter() - .fold(Some(1), |s, &a| s.and_then(|s_| s_.checked_mul(a))) + .try_fold(1_usize, |s, &a| s.checked_mul(a)) } #[doc(hidden)] @@ -284,21 +285,25 @@ pub trait Dimension: return true; } if dim.ndim() == 1 { - return strides[0] as isize == -1; - } - let order = strides._fastest_varying_stride_order(); - let strides = strides.slice(); - - let dim_slice = dim.slice(); - let mut cstride = 1; - for &i in order.slice() { - // a dimension of length 1 can have unequal strides - if dim_slice[i] != 1 && (strides[i] as isize).abs() as usize != cstride { - return false; + // fast case for ndim == 1: + // Either we have length <= 1, then stride is arbitrary, + // or we have stride == 1 or stride == -1, but +1 case is already handled above. + dim[0] <= 1 || strides[0] as isize == -1 + } else { + let order = strides._fastest_varying_stride_order(); + let strides = strides.slice(); + + let dim_slice = dim.slice(); + let mut cstride = 1; + for &i in order.slice() { + // a dimension of length 1 can have unequal strides + if dim_slice[i] != 1 && (strides[i] as isize).unsigned_abs() != cstride { + return false; + } + cstride *= dim_slice[i]; } - cstride *= dim_slice[i]; + true } - true } /// Return the axis ordering corresponding to the fastest variation @@ -379,6 +384,7 @@ pub trait Dimension: macro_rules! impl_insert_axis_array( ($n:expr) => ( + #[inline] fn insert_axis(&self, axis: Axis) -> Self::Larger { debug_assert!(axis.index() <= $n); let mut out = [1; $n + 1]; @@ -422,7 +428,6 @@ impl Dimension for Dim<[Ix; 0]> { fn next_for(&self, _index: Self) -> Option { None } - #[inline] impl_insert_axis_array!(0); #[inline] fn try_remove_axis(&self, _ignore: Axis) -> Self::Smaller { @@ -530,7 +535,6 @@ impl Dimension for Dim<[Ix; 1]> { None } } - #[inline] impl_insert_axis_array!(1); #[inline] fn try_remove_axis(&self, axis: Axis) -> Self::Smaller { @@ -604,7 +608,7 @@ impl Dimension for Dim<[Ix; 2]> { fn size_checked(&self) -> Option { let m = get!(self, 0); let n = get!(self, 1); - (m as usize).checked_mul(n as usize) + m.checked_mul(n) } #[inline] @@ -694,7 +698,6 @@ impl Dimension for Dim<[Ix; 2]> { None } } - #[inline] impl_insert_axis_array!(2); #[inline] fn try_remove_axis(&self, axis: Axis) -> Self::Smaller { @@ -730,7 +733,7 @@ impl Dimension for Dim<[Ix; 3]> { let m = get!(self, 0); let n = get!(self, 1); let o = get!(self, 2); - m as usize * n as usize * o as usize + m * n * o } #[inline] @@ -814,7 +817,6 @@ impl Dimension for Dim<[Ix; 3]> { } order } - #[inline] impl_insert_axis_array!(3); #[inline] fn try_remove_axis(&self, axis: Axis) -> Self::Smaller { @@ -845,7 +847,6 @@ macro_rules! large_dim { assert_eq!(ndim, $n); Self::default() } - #[inline] $($insert_axis)* #[inline] fn try_remove_axis(&self, axis: Axis) -> Self::Smaller { diff --git a/src/dimension/dynindeximpl.rs b/src/dimension/dynindeximpl.rs index b75db91c5..c2aea032e 100644 --- a/src/dimension/dynindeximpl.rs +++ b/src/dimension/dynindeximpl.rs @@ -2,7 +2,9 @@ use crate::imp_prelude::*; use std::hash::{Hash, Hasher}; use std::ops::{Deref, DerefMut, Index, IndexMut}; use alloc::vec; +#[cfg(not(feature = "std"))] use alloc::boxed::Box; +#[cfg(not(feature = "std"))] use alloc::vec::Vec; const CAP: usize = 4; @@ -21,7 +23,7 @@ impl Deref for IxDynRepr { debug_assert!(len as usize <= ar.len()); unsafe { ar.get_unchecked(..len as usize) } } - IxDynRepr::Alloc(ref ar) => &*ar, + IxDynRepr::Alloc(ref ar) => ar, } } } @@ -33,7 +35,7 @@ impl DerefMut for IxDynRepr { debug_assert!(len as usize <= ar.len()); unsafe { ar.get_unchecked_mut(..len as usize) } } - IxDynRepr::Alloc(ref mut ar) => &mut *ar, + IxDynRepr::Alloc(ref mut ar) => ar, } } } @@ -96,7 +98,7 @@ impl PartialEq for IxDynRepr { match (self, rhs) { (&IxDynRepr::Inline(slen, ref sarr), &IxDynRepr::Inline(rlen, ref rarr)) => { slen == rlen - && (0..CAP as usize) + && (0..CAP) .filter(|&i| i < slen as usize) .all(|i| sarr[i] == rarr[i]) } diff --git a/src/dimension/mod.rs b/src/dimension/mod.rs index 8e6245312..76a3984fe 100644 --- a/src/dimension/mod.rs +++ b/src/dimension/mod.rs @@ -9,10 +9,10 @@ use crate::error::{from_kind, ErrorKind, ShapeError}; use crate::slice::SliceArg; use crate::{Ix, Ixs, Slice, SliceInfoElem}; +use crate::shape_builder::Strides; use num_integer::div_floor; pub use self::axes::{Axes, AxisDescription}; -pub(crate) use self::axes::axes_of; pub use self::axis::Axis; pub use self::broadcast::DimMax; pub use self::conversion::IntoDimension; @@ -23,7 +23,8 @@ pub use self::ndindex::NdIndex; pub use self::ops::DimAdd; pub use self::remove_axis::RemoveAxis; -use crate::shape_builder::Strides; +pub(crate) use self::axes::axes_of; +pub(crate) use self::reshape::reshape_dim; use std::isize; use std::mem; @@ -40,11 +41,13 @@ mod dynindeximpl; mod ndindex; mod ops; mod remove_axis; +pub(crate) mod reshape; +mod sequence; /// Calculate offset from `Ix` stride converting sign properly #[inline(always)] pub fn stride_offset(n: Ix, stride: Ix) -> isize { - (n as isize) * ((stride as Ixs) as isize) + (n as isize) * (stride as Ixs) } /// Check whether the given `dim` and `stride` lead to overlapping indices @@ -182,7 +185,7 @@ where .try_fold(0usize, |acc, (&d, &s)| { let s = s as isize; // Calculate maximum possible absolute movement along this axis. - let off = d.saturating_sub(1).checked_mul(s.abs() as usize)?; + let off = d.saturating_sub(1).checked_mul(s.unsigned_abs())?; acc.checked_add(off) }) .ok_or_else(|| from_kind(ErrorKind::Overflow))?; @@ -232,11 +235,12 @@ where /// units of `A` and in units of bytes between the least address and greatest /// address accessible by moving along all axes does not exceed `isize::MAX`. /// -/// Warning: This function is sufficient to check the invariants of ArrayBase only -/// if the pointer to the first element of the array is chosen such that the element -/// with the smallest memory address is at the start of data. (In other words, the -/// pointer to the first element of the array must be computed using offset_from_ptr_to_memory -/// so that negative strides are correctly handled.) +/// Warning: This function is sufficient to check the invariants of ArrayBase +/// only if the pointer to the first element of the array is chosen such that +/// the element with the smallest memory address is at the start of the +/// allocation. (In other words, the pointer to the first element of the array +/// must be computed using `offset_from_low_addr_ptr_to_logical_ptr` so that +/// negative strides are correctly handled.) pub(crate) fn can_index_slice( data: &[A], dim: &D, @@ -331,7 +335,7 @@ where } } -impl<'a> DimensionExt for [Ix] { +impl DimensionExt for [Ix] { #[inline] fn axis(&self, axis: Axis) -> Ix { self[axis.index()] @@ -408,17 +412,19 @@ fn to_abs_slice(axis_len: usize, slice: Slice) -> (usize, usize, isize) { (start, end, step) } -/// This function computes the offset from the logically first element to the first element in -/// memory of the array. The result is always <= 0. -pub fn offset_from_ptr_to_memory(dim: &D, strides: &D) -> isize { - let offset = izip!(dim.slice(), strides.slice()).fold(0, |_offset, (d, s)| { - if (*s as isize) < 0 { - _offset + *s as isize * (*d as isize - 1) +/// Returns the offset from the lowest-address element to the logically first +/// element. +pub fn offset_from_low_addr_ptr_to_logical_ptr(dim: &D, strides: &D) -> usize { + let offset = izip!(dim.slice(), strides.slice()).fold(0, |_offset, (&d, &s)| { + let s = s as isize; + if s < 0 && d > 1 { + _offset - s * (d as isize - 1) } else { _offset } }); - offset + debug_assert!(offset >= 0); + offset as usize } /// Modify dimension, stride and return data pointer offset @@ -456,7 +462,7 @@ pub fn do_slice(dim: &mut usize, stride: &mut usize, slice: Slice) -> isize { }; // Update dimension. - let abs_step = step.abs() as usize; + let abs_step = step.unsigned_abs(); *dim = if abs_step == 1 { m } else { @@ -650,7 +656,7 @@ pub fn slices_intersect( Some(m) => m, None => return false, }; - if ind < min || ind > max || (ind - min) % step.abs() as usize != 0 { + if ind < min || ind > max || (ind - min) % step.unsigned_abs() != 0 { return false; } } @@ -667,6 +673,56 @@ pub fn slices_intersect( true } +pub(crate) fn is_layout_c(dim: &D, strides: &D) -> bool { + if let Some(1) = D::NDIM { + return strides[0] == 1 || dim[0] <= 1; + } + + for &d in dim.slice() { + if d == 0 { + return true; + } + } + + let mut contig_stride = 1_isize; + // check all dimensions -- a dimension of length 1 can have unequal strides + for (&dim, &s) in izip!(dim.slice().iter().rev(), strides.slice().iter().rev()) { + if dim != 1 { + let s = s as isize; + if s != contig_stride { + return false; + } + contig_stride *= dim as isize; + } + } + true +} + +pub(crate) fn is_layout_f(dim: &D, strides: &D) -> bool { + if let Some(1) = D::NDIM { + return strides[0] == 1 || dim[0] <= 1; + } + + for &d in dim.slice() { + if d == 0 { + return true; + } + } + + let mut contig_stride = 1_isize; + // check all dimensions -- a dimension of length 1 can have unequal strides + for (&dim, &s) in izip!(dim.slice(), strides.slice()) { + if dim != 1 { + let s = s as isize; + if s != contig_stride { + return false; + } + contig_stride *= dim as isize; + } + } + true +} + pub fn merge_axes(dim: &mut D, strides: &mut D, take: Axis, into: Axis) -> bool where D: Dimension, @@ -869,12 +925,16 @@ mod test { } quickcheck! { - fn extended_gcd_solves_eq(a: isize, b: isize) -> bool { + // FIXME: This test can't handle larger values at the moment + fn extended_gcd_solves_eq(a: i16, b: i16) -> bool { + let (a, b) = (a as isize, b as isize); let (g, (x, y)) = extended_gcd(a, b); a * x + b * y == g } - fn extended_gcd_correct_gcd(a: isize, b: isize) -> bool { + // FIXME: This test can't handle larger values at the moment + fn extended_gcd_correct_gcd(a: i16, b: i16) -> bool { + let (a, b) = (a as isize, b as isize); let (g, _) = extended_gcd(a, b); g == gcd(a, b) } @@ -890,9 +950,12 @@ mod test { } quickcheck! { + // FIXME: This test can't handle larger values at the moment fn solve_linear_diophantine_eq_solution_existence( - a: isize, b: isize, c: isize + a: i16, b: i16, c: i16 ) -> TestResult { + let (a, b, c) = (a as isize, b as isize, c as isize); + if a == 0 || b == 0 { TestResult::discard() } else { @@ -902,9 +965,12 @@ mod test { } } + // FIXME: This test can't handle larger values at the moment fn solve_linear_diophantine_eq_correct_solution( - a: isize, b: isize, c: isize, t: isize + a: i8, b: i8, c: i8, t: i8 ) -> TestResult { + let (a, b, c, t) = (a as isize, b as isize, c as isize, t as isize); + if a == 0 || b == 0 { TestResult::discard() } else { @@ -921,17 +987,23 @@ mod test { } quickcheck! { + // FIXME: This test is extremely slow, even with i16 values, investigate fn arith_seq_intersect_correct( - first1: isize, len1: isize, step1: isize, - first2: isize, len2: isize, step2: isize + first1: i8, len1: i8, step1: i8, + first2: i8, len2: i8, step2: i8 ) -> TestResult { use std::cmp; + let (len1, len2) = (len1 as isize, len2 as isize); + let (first1, step1) = (first1 as isize, step1 as isize); + let (first2, step2) = (first2 as isize, step2 as isize); + if len1 == 0 || len2 == 0 { // This case is impossible to reach in `arith_seq_intersect()` // because the `min*` and `max*` arguments are inclusive. return TestResult::discard(); } + let len1 = len1.abs(); let len2 = len2.abs(); diff --git a/src/dimension/ndindex.rs b/src/dimension/ndindex.rs index d9bac1d94..4d4046bc8 100644 --- a/src/dimension/ndindex.rs +++ b/src/dimension/ndindex.rs @@ -18,6 +18,7 @@ use crate::{ /// a[[1, 1]] += 1; /// assert_eq!(a[(1, 1)], 4); /// ``` +#[allow(clippy::missing_safety_doc)] // TODO: Add doc pub unsafe trait NdIndex: Debug { #[doc(hidden)] fn index_checked(&self, dim: &E, strides: &E) -> Option; @@ -139,50 +140,6 @@ macro_rules! ndindex_with_array { 0 } } - - // implement NdIndex for Dim<[Ix; 2]> and so on - unsafe impl NdIndex for Dim<[Ix; $n]> { - #[inline] - fn index_checked(&self, dim: &IxDyn, strides: &IxDyn) -> Option { - debug_assert_eq!(strides.ndim(), $n, - "Attempted to index with {:?} in array with {} axes", - self, strides.ndim()); - stride_offset_checked(dim.ix(), strides.ix(), self.ix()) - } - - #[inline] - fn index_unchecked(&self, strides: &IxDyn) -> isize { - debug_assert_eq!(strides.ndim(), $n, - "Attempted to index with {:?} in array with {} axes", - self, strides.ndim()); - $( - stride_offset(get!(self, $index), get!(strides, $index)) + - )* - 0 - } - } - - // implement NdIndex for [Ix; 2] and so on - unsafe impl NdIndex for [Ix; $n] { - #[inline] - fn index_checked(&self, dim: &IxDyn, strides: &IxDyn) -> Option { - debug_assert_eq!(strides.ndim(), $n, - "Attempted to index with {:?} in array with {} axes", - self, strides.ndim()); - stride_offset_checked(dim.ix(), strides.ix(), self) - } - - #[inline] - fn index_unchecked(&self, strides: &IxDyn) -> isize { - debug_assert_eq!(strides.ndim(), $n, - "Attempted to index with {:?} in array with {} axes", - self, strides.ndim()); - $( - stride_offset(self[$index], get!(strides, $index)) + - )* - 0 - } - } )+ }; } @@ -197,6 +154,64 @@ ndindex_with_array! { [6, Ix6 0 1 2 3 4 5] } +// implement NdIndex for Dim<[Ix; 2]> and so on +unsafe impl NdIndex for Dim<[Ix; N]> { + #[inline] + fn index_checked(&self, dim: &IxDyn, strides: &IxDyn) -> Option { + debug_assert_eq!( + strides.ndim(), + N, + "Attempted to index with {:?} in array with {} axes", + self, + strides.ndim() + ); + stride_offset_checked(dim.ix(), strides.ix(), self.ix()) + } + + #[inline] + fn index_unchecked(&self, strides: &IxDyn) -> isize { + debug_assert_eq!( + strides.ndim(), + N, + "Attempted to index with {:?} in array with {} axes", + self, + strides.ndim() + ); + (0..N) + .map(|i| stride_offset(get!(self, i), get!(strides, i))) + .sum() + } +} + +// implement NdIndex for [Ix; 2] and so on +unsafe impl NdIndex for [Ix; N] { + #[inline] + fn index_checked(&self, dim: &IxDyn, strides: &IxDyn) -> Option { + debug_assert_eq!( + strides.ndim(), + N, + "Attempted to index with {:?} in array with {} axes", + self, + strides.ndim() + ); + stride_offset_checked(dim.ix(), strides.ix(), self) + } + + #[inline] + fn index_unchecked(&self, strides: &IxDyn) -> isize { + debug_assert_eq!( + strides.ndim(), + N, + "Attempted to index with {:?} in array with {} axes", + self, + strides.ndim() + ); + (0..N) + .map(|i| stride_offset(self[i], get!(strides, i))) + .sum() + } +} + impl<'a> IntoDimension for &'a [Ix] { type Dim = IxDyn; fn into_dimension(self) -> Self::Dim { @@ -215,7 +230,7 @@ unsafe impl<'a> NdIndex for &'a IxDyn { unsafe impl<'a> NdIndex for &'a [Ix] { fn index_checked(&self, dim: &IxDyn, strides: &IxDyn) -> Option { - stride_offset_checked(dim.ix(), strides.ix(), *self) + stride_offset_checked(dim.ix(), strides.ix(), self) } fn index_unchecked(&self, strides: &IxDyn) -> isize { zip(strides.ix(), *self) diff --git a/src/dimension/reshape.rs b/src/dimension/reshape.rs new file mode 100644 index 000000000..c6e08848d --- /dev/null +++ b/src/dimension/reshape.rs @@ -0,0 +1,241 @@ + +use crate::{Dimension, Order, ShapeError, ErrorKind}; +use crate::dimension::sequence::{Sequence, SequenceMut, Forward, Reverse}; + +#[inline] +pub(crate) fn reshape_dim(from: &D, strides: &D, to: &E, order: Order) + -> Result +where + D: Dimension, + E: Dimension, +{ + debug_assert_eq!(from.ndim(), strides.ndim()); + let mut to_strides = E::zeros(to.ndim()); + match order { + Order::RowMajor => { + reshape_dim_c(&Forward(from), &Forward(strides), + &Forward(to), Forward(&mut to_strides))?; + } + Order::ColumnMajor => { + reshape_dim_c(&Reverse(from), &Reverse(strides), + &Reverse(to), Reverse(&mut to_strides))?; + } + } + Ok(to_strides) +} + +/// Try to reshape an array with dimensions `from_dim` and strides `from_strides` to the new +/// dimension `to_dim`, while keeping the same layout of elements in memory. The strides needed +/// if this is possible are stored into `to_strides`. +/// +/// This function uses RowMajor index ordering if the inputs are read in the forward direction +/// (index 0 is axis 0 etc) and ColumnMajor index ordering if the inputs are read in reversed +/// direction (as made possible with the Sequence trait). +/// +/// Preconditions: +/// +/// 1. from_dim and to_dim are valid dimensions (product of all non-zero axes +/// fits in isize::MAX). +/// 2. from_dim and to_dim are don't have any axes that are zero (that should be handled before +/// this function). +/// 3. `to_strides` should be an all-zeros or all-ones dimension of the right dimensionality +/// (but it will be overwritten after successful exit of this function). +/// +/// This function returns: +/// +/// - IncompatibleShape if the two shapes are not of matching number of elements +/// - IncompatibleLayout if the input shape and stride can not be remapped to the output shape +/// without moving the array data into a new memory layout. +/// - Ok if the from dim could be mapped to the new to dim. +fn reshape_dim_c(from_dim: &D, from_strides: &D, to_dim: &E, mut to_strides: E2) + -> Result<(), ShapeError> +where + D: Sequence, + E: Sequence, + E2: SequenceMut, +{ + // cursor indexes into the from and to dimensions + let mut fi = 0; // index into `from_dim` + let mut ti = 0; // index into `to_dim`. + + while fi < from_dim.len() && ti < to_dim.len() { + let mut fd = from_dim[fi]; + let mut fs = from_strides[fi] as isize; + let mut td = to_dim[ti]; + + if fd == td { + to_strides[ti] = from_strides[fi]; + fi += 1; + ti += 1; + continue + } + + if fd == 1 { + fi += 1; + continue; + } + + if td == 1 { + to_strides[ti] = 1; + ti += 1; + continue; + } + + if fd == 0 || td == 0 { + debug_assert!(false, "zero dim not handled by this function"); + return Err(ShapeError::from_kind(ErrorKind::IncompatibleShape)); + } + + // stride times element count is to be distributed out over a combination of axes. + let mut fstride_whole = fs * (fd as isize); + let mut fd_product = fd; // cumulative product of axis lengths in the combination (from) + let mut td_product = td; // cumulative product of axis lengths in the combination (to) + + // The two axis lengths are not a match, so try to combine multiple axes + // to get it to match up. + while fd_product != td_product { + if fd_product < td_product { + // Take another axis on the from side + fi += 1; + if fi >= from_dim.len() { + return Err(ShapeError::from_kind(ErrorKind::IncompatibleShape)); + } + fd = from_dim[fi]; + fd_product *= fd; + if fd > 1 { + let fs_old = fs; + fs = from_strides[fi] as isize; + // check if this axis and the next are contiguous together + if fs_old != fd as isize * fs { + return Err(ShapeError::from_kind(ErrorKind::IncompatibleLayout)); + } + } + } else { + // Take another axis on the `to` side + // First assign the stride to the axis we leave behind + fstride_whole /= td as isize; + to_strides[ti] = fstride_whole as usize; + ti += 1; + if ti >= to_dim.len() { + return Err(ShapeError::from_kind(ErrorKind::IncompatibleShape)); + } + + td = to_dim[ti]; + td_product *= td; + } + } + + fstride_whole /= td as isize; + to_strides[ti] = fstride_whole as usize; + + fi += 1; + ti += 1; + } + + // skip past 1-dims at the end + while fi < from_dim.len() && from_dim[fi] == 1 { + fi += 1; + } + + while ti < to_dim.len() && to_dim[ti] == 1 { + to_strides[ti] = 1; + ti += 1; + } + + if fi < from_dim.len() || ti < to_dim.len() { + return Err(ShapeError::from_kind(ErrorKind::IncompatibleShape)); + } + + Ok(()) +} + +#[cfg(feature = "std")] +#[test] +fn test_reshape() { + use crate::Dim; + + macro_rules! test_reshape { + (fail $order:ident from $from:expr, $stride:expr, to $to:expr) => { + let res = reshape_dim(&Dim($from), &Dim($stride), &Dim($to), Order::$order); + println!("Reshape {:?} {:?} to {:?}, order {:?}\n => {:?}", + $from, $stride, $to, Order::$order, res); + let _res = res.expect_err("Expected failed reshape"); + }; + (ok $order:ident from $from:expr, $stride:expr, to $to:expr, $to_stride:expr) => {{ + let res = reshape_dim(&Dim($from), &Dim($stride), &Dim($to), Order::$order); + println!("Reshape {:?} {:?} to {:?}, order {:?}\n => {:?}", + $from, $stride, $to, Order::$order, res); + println!("default stride for from dim: {:?}", Dim($from).default_strides()); + println!("default stride for to dim: {:?}", Dim($to).default_strides()); + let res = res.expect("Expected successful reshape"); + assert_eq!(res, Dim($to_stride), "mismatch in strides"); + }}; + } + + test_reshape!(ok C from [1, 2, 3], [6, 3, 1], to [1, 2, 3], [6, 3, 1]); + test_reshape!(ok C from [1, 2, 3], [6, 3, 1], to [2, 3], [3, 1]); + test_reshape!(ok C from [1, 2, 3], [6, 3, 1], to [6], [1]); + test_reshape!(fail C from [1, 2, 3], [6, 3, 1], to [1]); + test_reshape!(fail F from [1, 2, 3], [6, 3, 1], to [1]); + + test_reshape!(ok C from [6], [1], to [3, 2], [2, 1]); + test_reshape!(ok C from [3, 4, 5], [20, 5, 1], to [4, 15], [15, 1]); + + test_reshape!(ok C from [4, 4, 4], [16, 4, 1], to [16, 4], [4, 1]); + + test_reshape!(ok C from [4, 4], [4, 1], to [2, 2, 4, 1], [8, 4, 1, 1]); + test_reshape!(ok C from [4, 4], [4, 1], to [2, 2, 4], [8, 4, 1]); + test_reshape!(ok C from [4, 4], [4, 1], to [2, 2, 2, 2], [8, 4, 2, 1]); + + test_reshape!(ok C from [4, 4], [4, 1], to [2, 2, 1, 4], [8, 4, 1, 1]); + + test_reshape!(ok C from [4, 4, 4], [16, 4, 1], to [16, 4], [4, 1]); + test_reshape!(ok C from [3, 4, 4], [16, 4, 1], to [3, 16], [16, 1]); + + test_reshape!(ok C from [4, 4], [8, 1], to [2, 2, 2, 2], [16, 8, 2, 1]); + + test_reshape!(fail C from [4, 4], [8, 1], to [2, 1, 4, 2]); + + test_reshape!(ok C from [16], [4], to [2, 2, 4], [32, 16, 4]); + test_reshape!(ok C from [16], [-4isize as usize], to [2, 2, 4], + [-32isize as usize, -16isize as usize, -4isize as usize]); + test_reshape!(ok F from [16], [4], to [2, 2, 4], [4, 8, 16]); + test_reshape!(ok F from [16], [-4isize as usize], to [2, 2, 4], + [-4isize as usize, -8isize as usize, -16isize as usize]); + + test_reshape!(ok C from [3, 4, 5], [20, 5, 1], to [12, 5], [5, 1]); + test_reshape!(ok C from [3, 4, 5], [20, 5, 1], to [4, 15], [15, 1]); + test_reshape!(fail F from [3, 4, 5], [20, 5, 1], to [4, 15]); + test_reshape!(ok C from [3, 4, 5, 7], [140, 35, 7, 1], to [28, 15], [15, 1]); + + // preserve stride if shape matches + test_reshape!(ok C from [10], [2], to [10], [2]); + test_reshape!(ok F from [10], [2], to [10], [2]); + test_reshape!(ok C from [2, 10], [1, 2], to [2, 10], [1, 2]); + test_reshape!(ok F from [2, 10], [1, 2], to [2, 10], [1, 2]); + test_reshape!(ok C from [3, 4, 5], [20, 5, 1], to [3, 4, 5], [20, 5, 1]); + test_reshape!(ok F from [3, 4, 5], [20, 5, 1], to [3, 4, 5], [20, 5, 1]); + + test_reshape!(ok C from [3, 4, 5], [4, 1, 1], to [12, 5], [1, 1]); + test_reshape!(ok F from [3, 4, 5], [1, 3, 12], to [12, 5], [1, 12]); + test_reshape!(ok F from [3, 4, 5], [1, 3, 1], to [12, 5], [1, 1]); + + // broadcast shapes + test_reshape!(ok C from [3, 4, 5, 7], [0, 0, 7, 1], to [12, 35], [0, 1]); + test_reshape!(fail C from [3, 4, 5, 7], [0, 0, 7, 1], to [28, 15]); + + // one-filled shapes + test_reshape!(ok C from [10], [1], to [1, 10, 1, 1, 1], [1, 1, 1, 1, 1]); + test_reshape!(ok F from [10], [1], to [1, 10, 1, 1, 1], [1, 1, 1, 1, 1]); + test_reshape!(ok C from [1, 10], [10, 1], to [1, 10, 1, 1, 1], [10, 1, 1, 1, 1]); + test_reshape!(ok F from [1, 10], [10, 1], to [1, 10, 1, 1, 1], [10, 1, 1, 1, 1]); + test_reshape!(ok C from [1, 10], [1, 1], to [1, 5, 1, 1, 2], [1, 2, 2, 2, 1]); + test_reshape!(ok F from [1, 10], [1, 1], to [1, 5, 1, 1, 2], [1, 1, 5, 5, 5]); + test_reshape!(ok C from [10, 1, 1, 1, 1], [1, 1, 1, 1, 1], to [10], [1]); + test_reshape!(ok F from [10, 1, 1, 1, 1], [1, 1, 1, 1, 1], to [10], [1]); + test_reshape!(ok C from [1, 5, 1, 2, 1], [1, 2, 1, 1, 1], to [10], [1]); + test_reshape!(fail F from [1, 5, 1, 2, 1], [1, 2, 1, 1, 1], to [10]); + test_reshape!(ok F from [1, 5, 1, 2, 1], [1, 1, 1, 5, 1], to [10], [1]); + test_reshape!(fail C from [1, 5, 1, 2, 1], [1, 1, 1, 5, 1], to [10]); +} + diff --git a/src/dimension/sequence.rs b/src/dimension/sequence.rs new file mode 100644 index 000000000..835e00d18 --- /dev/null +++ b/src/dimension/sequence.rs @@ -0,0 +1,109 @@ +use std::ops::Index; +use std::ops::IndexMut; + +use crate::dimension::Dimension; + +pub(in crate::dimension) struct Forward(pub(crate) D); +pub(in crate::dimension) struct Reverse(pub(crate) D); + +impl Index for Forward<&D> +where + D: Dimension, +{ + type Output = usize; + + #[inline] + fn index(&self, index: usize) -> &usize { + &self.0[index] + } +} + +impl Index for Forward<&mut D> +where + D: Dimension, +{ + type Output = usize; + + #[inline] + fn index(&self, index: usize) -> &usize { + &self.0[index] + } +} + +impl IndexMut for Forward<&mut D> +where + D: Dimension, +{ + #[inline] + fn index_mut(&mut self, index: usize) -> &mut usize { + &mut self.0[index] + } +} + +impl Index for Reverse<&D> +where + D: Dimension, +{ + type Output = usize; + + #[inline] + fn index(&self, index: usize) -> &usize { + &self.0[self.len() - index - 1] + } +} + +impl Index for Reverse<&mut D> +where + D: Dimension, +{ + type Output = usize; + + #[inline] + fn index(&self, index: usize) -> &usize { + &self.0[self.len() - index - 1] + } +} + +impl IndexMut for Reverse<&mut D> +where + D: Dimension, +{ + #[inline] + fn index_mut(&mut self, index: usize) -> &mut usize { + let len = self.len(); + &mut self.0[len - index - 1] + } +} + +/// Indexable sequence with length +pub(in crate::dimension) trait Sequence: Index { + fn len(&self) -> usize; +} + +/// Indexable sequence with length (mut) +pub(in crate::dimension) trait SequenceMut: Sequence + IndexMut { } + +impl Sequence for Forward<&D> where D: Dimension { + #[inline] + fn len(&self) -> usize { self.0.ndim() } +} + +impl Sequence for Forward<&mut D> where D: Dimension { + #[inline] + fn len(&self) -> usize { self.0.ndim() } +} + +impl SequenceMut for Forward<&mut D> where D: Dimension { } + +impl Sequence for Reverse<&D> where D: Dimension { + #[inline] + fn len(&self) -> usize { self.0.ndim() } +} + +impl Sequence for Reverse<&mut D> where D: Dimension { + #[inline] + fn len(&self) -> usize { self.0.ndim() } +} + +impl SequenceMut for Reverse<&mut D> where D: Dimension { } + diff --git a/src/doc/crate_feature_flags.rs b/src/doc/crate_feature_flags.rs new file mode 100644 index 000000000..b396755c7 --- /dev/null +++ b/src/doc/crate_feature_flags.rs @@ -0,0 +1,35 @@ +//! Crate Feature Flags +//! +//! The following crate feature flags are available. They are configured in your +//! `Cargo.toml` where the dependency on `ndarray` is defined. +//! +//! ## `std` +//! - Rust standard library (enabled by default) +//! - This crate can be used without the standard library by disabling the +//! default `std` feature. To do so, use `default-features = false` in +//! your `Cargo.toml`. +//! - The `geomspace` `linspace` `logspace` `range` `std` `var` `var_axis` +//! and `std_axis` methods are only available when `std` is enabled. +//! +//! ## `serde` +//! - Enables serialization support for serde 1.x +//! +//! ## `rayon` +//! - Enables parallel iterators, parallelized methods, the [`parallel`] module and [`par_azip!`]. +//! - Implies std +//! +//! ## `approx` +//! - Enables implementations of traits from version 0.4 of the [`approx`] crate. +//! +//! ## `approx-0_5` +//! - Enables implementations of traits from version 0.5 of the [`approx`] crate. +//! +//! ## `blas` +//! - Enable transparent BLAS support for matrix multiplication. +//! Uses ``blas-src`` for pluggable backend, which needs to be configured +//! separately (see the README). +//! +//! ## `matrixmultiply-threading` +//! - Enable the ``threading`` feature in the matrixmultiply package +//! +//! [`parallel`]: crate::parallel diff --git a/src/doc/mod.rs b/src/doc/mod.rs index b98c9cab8..c0d7fab91 100644 --- a/src/doc/mod.rs +++ b/src/doc/mod.rs @@ -1,3 +1,4 @@ //! Standalone documentation pages. +pub mod crate_feature_flags; pub mod ndarray_for_numpy_users; diff --git a/src/doc/ndarray_for_numpy_users/coord_transform.rs b/src/doc/ndarray_for_numpy_users/coord_transform.rs index e019fd1b1..1529e8746 100644 --- a/src/doc/ndarray_for_numpy_users/coord_transform.rs +++ b/src/doc/ndarray_for_numpy_users/coord_transform.rs @@ -51,40 +51,38 @@ //! ``` //! use ndarray::prelude::*; //! -//! fn main() { -//! let nelems = 4; -//! let bunge = Array::ones((3, nelems)); -//! -//! let s1 = bunge.slice(s![0, ..]).mapv(f64::sin); -//! let c1 = bunge.slice(s![0, ..]).mapv(f64::cos); -//! let s2 = bunge.slice(s![1, ..]).mapv(f64::sin); -//! let c2 = bunge.slice(s![1, ..]).mapv(f64::cos); -//! let s3 = bunge.slice(s![2, ..]).mapv(f64::sin); -//! let c3 = bunge.slice(s![2, ..]).mapv(f64::cos); -//! -//! let mut rmat = Array::zeros((3, 3, nelems).f()); -//! for i in 0..nelems { -//! rmat[[0, 0, i]] = c1[i] * c3[i] - s1[i] * s3[i] * c2[i]; -//! rmat[[0, 1, i]] = -c1[i] * s3[i] - s1[i] * c2[i] * c3[i]; -//! rmat[[0, 2, i]] = s1[i] * s2[i]; -//! -//! rmat[[1, 0, i]] = s1[i] * c3[i] + c1[i] * c2[i] * s3[i]; -//! rmat[[1, 1, i]] = -s1[i] * s3[i] + c1[i] * c2[i] * c3[i]; -//! rmat[[1, 2, i]] = -c1[i] * s2[i]; -//! -//! rmat[[2, 0, i]] = s2[i] * s3[i]; -//! rmat[[2, 1, i]] = s2[i] * c3[i]; -//! rmat[[2, 2, i]] = c2[i]; -//! } -//! -//! let eye2d = Array::eye(3); -//! -//! let mut rotated = Array::zeros((3, 3, nelems).f()); -//! for i in 0..nelems { -//! rotated -//! .slice_mut(s![.., .., i]) -//! .assign({ &rmat.slice(s![.., .., i]).dot(&eye2d) }); -//! } +//! let nelems = 4; +//! let bunge = Array::ones((3, nelems)); +//! +//! let s1 = bunge.slice(s![0, ..]).mapv(f64::sin); +//! let c1 = bunge.slice(s![0, ..]).mapv(f64::cos); +//! let s2 = bunge.slice(s![1, ..]).mapv(f64::sin); +//! let c2 = bunge.slice(s![1, ..]).mapv(f64::cos); +//! let s3 = bunge.slice(s![2, ..]).mapv(f64::sin); +//! let c3 = bunge.slice(s![2, ..]).mapv(f64::cos); +//! +//! let mut rmat = Array::zeros((3, 3, nelems).f()); +//! for i in 0..nelems { +//! rmat[[0, 0, i]] = c1[i] * c3[i] - s1[i] * s3[i] * c2[i]; +//! rmat[[0, 1, i]] = -c1[i] * s3[i] - s1[i] * c2[i] * c3[i]; +//! rmat[[0, 2, i]] = s1[i] * s2[i]; +//! +//! rmat[[1, 0, i]] = s1[i] * c3[i] + c1[i] * c2[i] * s3[i]; +//! rmat[[1, 1, i]] = -s1[i] * s3[i] + c1[i] * c2[i] * c3[i]; +//! rmat[[1, 2, i]] = -c1[i] * s2[i]; +//! +//! rmat[[2, 0, i]] = s2[i] * s3[i]; +//! rmat[[2, 1, i]] = s2[i] * c3[i]; +//! rmat[[2, 2, i]] = c2[i]; +//! } +//! +//! let eye2d = Array::eye(3); +//! +//! let mut rotated = Array::zeros((3, 3, nelems).f()); +//! for i in 0..nelems { +//! rotated +//! .slice_mut(s![.., .., i]) +//! .assign(&rmat.slice(s![.., .., i]).dot(&eye2d)); //! } //! ``` //! @@ -96,37 +94,35 @@ //! ``` //! use ndarray::prelude::*; //! -//! fn main() { -//! let nelems = 4; -//! let bunge = Array2::::ones((3, nelems)); -//! -//! let mut rmat = Array::zeros((3, 3, nelems).f()); -//! azip!((mut rmat in rmat.axis_iter_mut(Axis(2)), bunge in bunge.axis_iter(Axis(1))) { -//! let s1 = bunge[0].sin(); -//! let c1 = bunge[0].cos(); -//! let s2 = bunge[1].sin(); -//! let c2 = bunge[1].cos(); -//! let s3 = bunge[2].sin(); -//! let c3 = bunge[2].cos(); -//! -//! rmat[[0, 0]] = c1 * c3 - s1 * s3 * c2; -//! rmat[[0, 1]] = -c1 * s3 - s1 * c2 * c3; -//! rmat[[0, 2]] = s1 * s2; -//! -//! rmat[[1, 0]] = s1 * c3 + c1 * c2 * s3; -//! rmat[[1, 1]] = -s1 * s3 + c1 * c2 * c3; -//! rmat[[1, 2]] = -c1 * s2; -//! -//! rmat[[2, 0]] = s2 * s3; -//! rmat[[2, 1]] = s2 * c3; -//! rmat[[2, 2]] = c2; -//! }); -//! -//! let eye2d = Array2::::eye(3); -//! -//! let mut rotated = Array3::::zeros((3, 3, nelems).f()); -//! azip!((mut rotated in rotated.axis_iter_mut(Axis(2)), rmat in rmat.axis_iter(Axis(2))) { -//! rotated.assign(&rmat.dot(&eye2d)); -//! }); -//! } +//! let nelems = 4; +//! let bunge = Array2::::ones((3, nelems)); +//! +//! let mut rmat = Array::zeros((3, 3, nelems).f()); +//! azip!((mut rmat in rmat.axis_iter_mut(Axis(2)), bunge in bunge.axis_iter(Axis(1))) { +//! let s1 = bunge[0].sin(); +//! let c1 = bunge[0].cos(); +//! let s2 = bunge[1].sin(); +//! let c2 = bunge[1].cos(); +//! let s3 = bunge[2].sin(); +//! let c3 = bunge[2].cos(); +//! +//! rmat[[0, 0]] = c1 * c3 - s1 * s3 * c2; +//! rmat[[0, 1]] = -c1 * s3 - s1 * c2 * c3; +//! rmat[[0, 2]] = s1 * s2; +//! +//! rmat[[1, 0]] = s1 * c3 + c1 * c2 * s3; +//! rmat[[1, 1]] = -s1 * s3 + c1 * c2 * c3; +//! rmat[[1, 2]] = -c1 * s2; +//! +//! rmat[[2, 0]] = s2 * s3; +//! rmat[[2, 1]] = s2 * c3; +//! rmat[[2, 2]] = c2; +//! }); +//! +//! let eye2d = Array2::::eye(3); +//! +//! let mut rotated = Array3::::zeros((3, 3, nelems).f()); +//! azip!((mut rotated in rotated.axis_iter_mut(Axis(2)), rmat in rmat.axis_iter(Axis(2))) { +//! rotated.assign(&rmat.dot(&eye2d)); +//! }); //! ``` diff --git a/src/doc/ndarray_for_numpy_users/mod.rs b/src/doc/ndarray_for_numpy_users/mod.rs index 478cc2cec..5ac15e300 100644 --- a/src/doc/ndarray_for_numpy_users/mod.rs +++ b/src/doc/ndarray_for_numpy_users/mod.rs @@ -19,11 +19,12 @@ //! * [Mathematics](#mathematics) //! * [Array manipulation](#array-manipulation) //! * [Iteration](#iteration) +//! * [Type conversions](#type-conversions) //! * [Convenience methods for 2-D arrays](#convenience-methods-for-2-d-arrays) //! //! # Similarities //! -//! `ndarray`'s array type ([`ArrayBase`][ArrayBase]), is very similar to +//! `ndarray`'s array type ([`ArrayBase`]), is very similar to //! NumPy's array type (`numpy.ndarray`): //! //! * Arrays have a single element type. @@ -70,12 +71,12 @@ //! //! //! -//! In `ndarray`, all arrays are instances of [`ArrayBase`][ArrayBase], but -//! `ArrayBase` is generic over the ownership of the data. [`Array`][Array] -//! owns its data; [`ArrayView`][ArrayView] is a view; -//! [`ArrayViewMut`][ArrayViewMut] is a mutable view; [`CowArray`][CowArray] +//! In `ndarray`, all arrays are instances of [`ArrayBase`], but +//! `ArrayBase` is generic over the ownership of the data. [`Array`] +//! owns its data; [`ArrayView`] is a view; +//! [`ArrayViewMut`] is a mutable view; [`CowArray`] //! either owns its data or is a view (with copy-on-write mutation of the view -//! variant); and [`ArcArray`][ArcArray] has a reference-counted pointer to its +//! variant); and [`ArcArray`] has a reference-counted pointer to its //! data (with copy-on-write mutation). Arrays and views follow Rust's aliasing //! rules. //! @@ -91,7 +92,7 @@ //! //! //! In `ndarray`, you can create fixed-dimension arrays, such as -//! [`Array2`][Array2]. This takes advantage of the type system to help you +//! [`Array2`]. This takes advantage of the type system to help you //! write correct code and also avoids small heap allocations for the shape and //! strides. //! @@ -110,7 +111,7 @@ //! When slicing in `ndarray`, the axis is first sliced with `start..end`. Then if //! `step` is positive, the first index is the front of the slice; if `step` is //! negative, the first index is the back of the slice. This means that the -//! behavior is the same as NumPy except when `step < -1`. See the docs for the +//! behavior is different from NumPy when `step < 0`. See the docs for the //! [`s![]` macro][s!] for more details. //! //! @@ -179,7 +180,7 @@ //! ``` //! use ndarray::prelude::*; //! # -//! # fn main() {} +//! # fn main() { let _ = arr0(1); } //! ``` //! //! ## Array creation @@ -245,8 +246,8 @@ //! methods [`.slice_mut()`][.slice_mut()], [`.slice_move()`][.slice_move()], and //! [`.slice_collapse()`][.slice_collapse()]. //! -//! * The behavior of slicing is slightly different from NumPy for slices with -//! `step < -1`. See the docs for the [`s![]` macro][s!] for more details. +//! * The behavior of slicing is different from NumPy for slices with +//! `step < 0`. See the docs for the [`s![]` macro][s!] for more details. //! //! NumPy | `ndarray` | Notes //! ------|-----------|------ @@ -257,13 +258,14 @@ //! `a[-5:]` or `a[-5:, :]` | [`a.slice(s![-5.., ..])`][.slice()] or [`a.slice_axis(Axis(0), Slice::from(-5..))`][.slice_axis()] | get the last 5 rows of a 2-D array //! `a[:3, 4:9]` | [`a.slice(s![..3, 4..9])`][.slice()] | columns 4, 5, 6, 7, and 8 of the first 3 rows //! `a[1:4:2, ::-1]` | [`a.slice(s![1..4;2, ..;-1])`][.slice()] | rows 1 and 3 with the columns in reverse order +//! `a.take([4, 2])` | `a.select(Axis(0), &[4, 2])` | rows 4 and 2 of the array //! //! ## Shape and strides //! //! Note that [`a.shape()`][.shape()], [`a.dim()`][.dim()], and //! [`a.raw_dim()`][.raw_dim()] all return the shape of the array, but as //! different types. `a.shape()` returns the shape as `&[Ix]`, (where -//! [`Ix`][Ix] is `usize`) which is useful for general operations on the shape. +//! [`Ix`] is `usize`) which is useful for general operations on the shape. //! `a.dim()` returns the shape as `D::Pattern`, which is useful for //! pattern-matching shapes. `a.raw_dim()` returns the shape as `D`, which is //! useful for creating other arrays of the same shape. @@ -376,7 +378,7 @@ //! //! //! -//! [`a * b`, `a + b`, etc.](../../struct.ArrayBase.html#arithmetic-operations) +//! [`a * b`, `a + b`, etc.](ArrayBase#arithmetic-operations) //! //! //! @@ -530,6 +532,8 @@ //! ------|-----------|------ //! `a[:] = 3.` | [`a.fill(3.)`][.fill()] | set all array elements to the same scalar value //! `a[:] = b` | [`a.assign(&b)`][.assign()] | copy the data from array `b` into array `a` +//! `a[:5, 2] = 3.` | [`a.slice_mut(s![..5, 2]).fill(3.)`][.fill()] | set a portion of the array to the same scalar value +//! `a[:5, 2] = b` | [`a.slice_mut(s![..5, 2]).assign(&b)`][.assign()] | copy the data from array `b` into part of array `a` //! `np.concatenate((a,b), axis=1)` | [`concatenate![Axis(1), a, b]`][concatenate!] or [`concatenate(Axis(1), &[a.view(), b.view()])`][concatenate()] | concatenate arrays `a` and `b` along axis 1 //! `np.stack((a,b), axis=1)` | [`stack![Axis(1), a, b]`][stack!] or [`stack(Axis(1), vec![a.view(), b.view()])`][stack()] | stack arrays `a` and `b` along axis 1 //! `a[:,np.newaxis]` or `np.expand_dims(a, axis=1)` | [`a.slice(s![.., NewAxis])`][.slice()] or [`a.insert_axis(Axis(1))`][.insert_axis()] | create an view of 1-D array `a`, inserting a new axis 1 @@ -540,17 +544,17 @@ //! ## Iteration //! //! `ndarray` has lots of interesting iterators/producers that implement the -//! [`NdProducer`][NdProducer] trait, which is a generalization of `Iterator` +//! [`NdProducer`](crate::NdProducer) trait, which is a generalization of `Iterator` //! to multiple dimensions. This makes it possible to correctly and efficiently //! zip together slices/subviews of arrays in multiple dimensions with -//! [`Zip`][Zip] or [`azip!()`][azip!]. The purpose of this is similar to +//! [`Zip`] or [`azip!()`]. The purpose of this is similar to //! [`np.nditer`](https://docs.scipy.org/doc/numpy/reference/generated/numpy.nditer.html), -//! but [`Zip`][Zip] is implemented and used somewhat differently. +//! but [`Zip`] is implemented and used somewhat differently. //! //! This table lists some of the iterators/producers which have a direct //! equivalent in NumPy. For a more complete introduction to producers and //! iterators, see [*Loops, Producers, and -//! Iterators*](../../struct.ArrayBase.html#loops-producers-and-iterators). +//! Iterators*](ArrayBase#loops-producers-and-iterators). //! Note that there are also variants of these iterators (with a `_mut` suffix) //! that yield `ArrayViewMut` instead of `ArrayView`. //! @@ -560,6 +564,102 @@ //! `np.ndenumerate(a)` | [`a.indexed_iter()`][.indexed_iter()] | flat iterator yielding the index along with each element reference //! `iter(a)` | [`a.outer_iter()`][.outer_iter()] or [`a.axis_iter(Axis(0))`][.axis_iter()] | iterator over the first (outermost) axis, yielding each subview //! +//! ## Type conversions +//! +//! In `ndarray`, conversions between datatypes are done with `mapv()` by +//! passing a closure to convert every element independently. +//! For the conversion itself, we have several options: +//! - `std::convert::From` ensures lossless, safe conversions at compile-time +//! and is generally recommended. +//! - `std::convert::TryFrom` can be used for potentially unsafe conversions. It +//! will return a `Result` which can be handled or `unwrap()`ed to panic if +//! any value at runtime cannot be converted losslessly. +//! - The `as` keyword compiles to lossless/lossy conversions depending on the +//! source and target datatypes. It can be useful when `TryFrom` is a +//! performance issue or does not apply. A notable difference to NumPy is that +//! `as` performs a [*saturating* cast][sat_conv] when casting +//! from floats to integers. Further information can be found in the +//! [reference on type cast expressions][as_typecast]. +//! +//! For details, be sure to check out the type conversion examples. +//! + +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//!
+//! +//! NumPy +//! +//! +//! +//! `ndarray` +//! +//! +//! +//! Notes +//! +//!
+//! +//! `a.astype(np.float32)` +//! +//! +//! +//! `a.mapv(f32::from)` +//! +//! +//! +//! convert `u8` array infallibly to `f32` array with `std::convert::From`, generally recommended +//! +//!
+//! +//! `a.astype(np.int32)` +//! +//! +//! +//! `a.mapv(i32::from)` +//! +//! +//! +//! upcast `u8` array to `i32` array with `std::convert::From`, preferable over `as` because it ensures at compile-time that the conversion is lossless +//! +//!
+//! +//! `a.astype(np.uint8)` +//! +//! +//! +//! `a.mapv(|x| u8::try_from(x).unwrap())` +//! +//! +//! +//! try to convert `i8` array to `u8` array, panic if any value cannot be converted lossless at runtime (e.g. negative value) +//! +//!
+//! +//! `a.astype(np.int32)` +//! +//! +//! +//! `a.mapv(|x| x as i32)` +//! +//! +//! +//! convert `f32` array to `i32` array with ["saturating" conversion][sat_conv]; care needed because it can be a lossy conversion or result in non-finite values! See [the reference for information][as_typecast]. +//! +//!
+//! +//! [as_conv]: https://doc.rust-lang.org/rust-by-example/types/cast.html +//! [sat_conv]: https://blog.rust-lang.org/2020/07/16/Rust-1.45.0.html#fixing-unsoundness-in-casts +//! [as_typecast]: https://doc.rust-lang.org/reference/expressions/operator-expr.html#type-cast-expressions +//! //! ## Convenience methods for 2-D arrays //! //! NumPy | `ndarray` | Notes @@ -570,88 +670,77 @@ //! `a[:,4]` | [`a.column(4)`][.column()] or [`a.column_mut(4)`][.column_mut()] | view (or mutable view) of column 4 in a 2-D array //! `a.shape[0] == a.shape[1]` | [`a.is_square()`][.is_square()] | check if the array is square //! -//! [.abs_diff_eq()]: ../../struct.ArrayBase.html#impl-AbsDiffEq> -//! [ArcArray]: ../../type.ArcArray.html -//! [arr2()]: ../../fn.arr2.html -//! [array!]: ../../macro.array.html -//! [Array]: ../../type.Array.html -//! [Array2]: ../../type.Array2.html -//! [ArrayBase]: ../../struct.ArrayBase.html -//! [ArrayView]: ../../type.ArrayView.html -//! [ArrayViewMut]: ../../type.ArrayViewMut.html -//! [.assign()]: ../../struct.ArrayBase.html#method.assign -//! [.axis_iter()]: ../../struct.ArrayBase.html#method.axis_iter -//! [azip!]: ../../macro.azip.html -//! [.ncols()]: ../../struct.ArrayBase.html#method.ncols -//! [.column()]: ../../struct.ArrayBase.html#method.column -//! [.column_mut()]: ../../struct.ArrayBase.html#method.column_mut -//! [concatenate!]: ../../macro.concatenate.html -//! [concatenate()]: ../../fn.concatenate.html -//! [CowArray]: ../../type.CowArray.html -//! [::default()]: ../../struct.ArrayBase.html#method.default -//! [.diag()]: ../../struct.ArrayBase.html#method.diag -//! [.dim()]: ../../struct.ArrayBase.html#method.dim -//! [::eye()]: ../../struct.ArrayBase.html#method.eye -//! [.fill()]: ../../struct.ArrayBase.html#method.fill -//! [.fold()]: ../../struct.ArrayBase.html#method.fold -//! [.fold_axis()]: ../../struct.ArrayBase.html#method.fold_axis -//! [::from_elem()]: ../../struct.ArrayBase.html#method.from_elem -//! [::from_iter()]: ../../struct.ArrayBase.html#method.from_iter -//! [::from_diag()]: ../../struct.ArrayBase.html#method.from_diag -//! [::from_shape_fn()]: ../../struct.ArrayBase.html#method.from_shape_fn -//! [::from_shape_vec()]: ../../struct.ArrayBase.html#method.from_shape_vec -//! [::from_shape_vec_unchecked()]: ../../struct.ArrayBase.html#method.from_shape_vec_unchecked -//! [::from_vec()]: ../../struct.ArrayBase.html#method.from_vec -//! [.index()]: ../../struct.ArrayBase.html#impl-Index -//! [.indexed_iter()]: ../../struct.ArrayBase.html#method.indexed_iter -//! [.insert_axis()]: ../../struct.ArrayBase.html#method.insert_axis -//! [.is_empty()]: ../../struct.ArrayBase.html#method.is_empty -//! [.is_square()]: ../../struct.ArrayBase.html#method.is_square -//! [.iter()]: ../../struct.ArrayBase.html#method.iter -//! [Ix]: ../../type.Ix.html -//! [.len()]: ../../struct.ArrayBase.html#method.len -//! [.len_of()]: ../../struct.ArrayBase.html#method.len_of -//! [::linspace()]: ../../struct.ArrayBase.html#method.linspace -//! [::logspace()]: ../../struct.ArrayBase.html#method.logspace -//! [::geomspace()]: ../../struct.ArrayBase.html#method.geomspace -//! [.map()]: ../../struct.ArrayBase.html#method.map -//! [.map_axis()]: ../../struct.ArrayBase.html#method.map_axis -//! [.map_inplace()]: ../../struct.ArrayBase.html#method.map_inplace -//! [.mapv()]: ../../struct.ArrayBase.html#method.mapv -//! [.mapv_inplace()]: ../../struct.ArrayBase.html#method.mapv_inplace -//! [.mapv_into()]: ../../struct.ArrayBase.html#method.mapv_into -//! [matrix-* dot]: ../../struct.ArrayBase.html#method.dot-1 -//! [.mean()]: ../../struct.ArrayBase.html#method.mean -//! [.mean_axis()]: ../../struct.ArrayBase.html#method.mean_axis -//! [.ndim()]: ../../struct.ArrayBase.html#method.ndim -//! [NdProducer]: ../../trait.NdProducer.html -//! [::ones()]: ../../struct.ArrayBase.html#method.ones -//! [.outer_iter()]: ../../struct.ArrayBase.html#method.outer_iter -//! [::range()]: ../../struct.ArrayBase.html#method.range -//! [.raw_dim()]: ../../struct.ArrayBase.html#method.raw_dim -//! [.reversed_axes()]: ../../struct.ArrayBase.html#method.reversed_axes -//! [.row()]: ../../struct.ArrayBase.html#method.row -//! [.row_mut()]: ../../struct.ArrayBase.html#method.row_mut -//! [.nrows()]: ../../struct.ArrayBase.html#method.nrows -//! [s!]: ../../macro.s.html -//! [.sum()]: ../../struct.ArrayBase.html#method.sum -//! [.slice()]: ../../struct.ArrayBase.html#method.slice -//! [.slice_axis()]: ../../struct.ArrayBase.html#method.slice_axis -//! [.slice_collapse()]: ../../struct.ArrayBase.html#method.slice_collapse -//! [.slice_move()]: ../../struct.ArrayBase.html#method.slice_move -//! [.slice_mut()]: ../../struct.ArrayBase.html#method.slice_mut -//! [.shape()]: ../../struct.ArrayBase.html#method.shape -//! [stack!]: ../../macro.stack.html -//! [stack()]: ../../fn.stack.html -//! [.strides()]: ../../struct.ArrayBase.html#method.strides -//! [.index_axis()]: ../../struct.ArrayBase.html#method.index_axis -//! [.sum_axis()]: ../../struct.ArrayBase.html#method.sum_axis -//! [.t()]: ../../struct.ArrayBase.html#method.t -//! [vec-* dot]: ../../struct.ArrayBase.html#method.dot -//! [.for_each()]: ../../struct.ArrayBase.html#method.for_each -//! [::zeros()]: ../../struct.ArrayBase.html#method.zeros -//! [Zip]: ../../struct.Zip.html +//! [.abs_diff_eq()]: ArrayBase#impl-AbsDiffEq> +//! [.assign()]: ArrayBase::assign +//! [.axis_iter()]: ArrayBase::axis_iter +//! [.ncols()]: ArrayBase::ncols +//! [.column()]: ArrayBase::column +//! [.column_mut()]: ArrayBase::column_mut +//! [concatenate()]: crate::concatenate() +//! [::default()]: ArrayBase::default +//! [.diag()]: ArrayBase::diag +//! [.dim()]: ArrayBase::dim +//! [::eye()]: ArrayBase::eye +//! [.fill()]: ArrayBase::fill +//! [.fold()]: ArrayBase::fold +//! [.fold_axis()]: ArrayBase::fold_axis +//! [::from_elem()]: ArrayBase::from_elem +//! [::from_iter()]: ArrayBase::from_iter +//! [::from_diag()]: ArrayBase::from_diag +//! [::from_shape_fn()]: ArrayBase::from_shape_fn +//! [::from_shape_vec()]: ArrayBase::from_shape_vec +//! [::from_shape_vec_unchecked()]: ArrayBase::from_shape_vec_unchecked +//! [::from_vec()]: ArrayBase::from_vec +//! [.index()]: ArrayBase#impl-Index +//! [.indexed_iter()]: ArrayBase::indexed_iter +//! [.insert_axis()]: ArrayBase::insert_axis +//! [.is_empty()]: ArrayBase::is_empty +//! [.is_square()]: ArrayBase::is_square +//! [.iter()]: ArrayBase::iter +//! [.len()]: ArrayBase::len +//! [.len_of()]: ArrayBase::len_of +//! [::linspace()]: ArrayBase::linspace +//! [::logspace()]: ArrayBase::logspace +//! [::geomspace()]: ArrayBase::geomspace +//! [.map()]: ArrayBase::map +//! [.map_axis()]: ArrayBase::map_axis +//! [.map_inplace()]: ArrayBase::map_inplace +//! [.mapv()]: ArrayBase::mapv +//! [.mapv_inplace()]: ArrayBase::mapv_inplace +//! [.mapv_into()]: ArrayBase::mapv_into +//! [matrix-* dot]: ArrayBase::dot-1 +//! [.mean()]: ArrayBase::mean +//! [.mean_axis()]: ArrayBase::mean_axis +//! [.ndim()]: ArrayBase::ndim +//! [::ones()]: ArrayBase::ones +//! [.outer_iter()]: ArrayBase::outer_iter +//! [::range()]: ArrayBase::range +//! [.raw_dim()]: ArrayBase::raw_dim +//! [.reversed_axes()]: ArrayBase::reversed_axes +//! [.row()]: ArrayBase::row +//! [.row_mut()]: ArrayBase::row_mut +//! [.nrows()]: ArrayBase::nrows +//! [.sum()]: ArrayBase::sum +//! [.slice()]: ArrayBase::slice +//! [.slice_axis()]: ArrayBase::slice_axis +//! [.slice_collapse()]: ArrayBase::slice_collapse +//! [.slice_move()]: ArrayBase::slice_move +//! [.slice_mut()]: ArrayBase::slice_mut +//! [.shape()]: ArrayBase::shape +//! [stack()]: crate::stack() +//! [.strides()]: ArrayBase::strides +//! [.index_axis()]: ArrayBase::index_axis +//! [.sum_axis()]: ArrayBase::sum_axis +//! [.t()]: ArrayBase::t +//! [vec-* dot]: ArrayBase::dot +//! [.for_each()]: ArrayBase::for_each +//! [::zeros()]: ArrayBase::zeros +//! [`Zip`]: crate::Zip pub mod coord_transform; pub mod rk_step; pub mod simple_math; + +// This is to avoid putting `crate::` everywhere +#[allow(unused_imports)] +use crate::imp_prelude::*; diff --git a/src/doc/ndarray_for_numpy_users/rk_step.rs b/src/doc/ndarray_for_numpy_users/rk_step.rs index 2c5ce2362..0448e0705 100644 --- a/src/doc/ndarray_for_numpy_users/rk_step.rs +++ b/src/doc/ndarray_for_numpy_users/rk_step.rs @@ -104,7 +104,7 @@ //! (y_new, f_new, error) //! } //! # -//! # fn main() {} +//! # fn main() { let _ = rk_step::) -> _>; } //! ``` //! //! It's possible to improve the efficiency by doing the following: @@ -120,10 +120,10 @@ //! * Don't return a newly allocated `f_new` array. If the caller wants this //! information, they can get it from the last row of `k`. //! -//! * Use [`c.mul_add(h, t)`][f64.mul_add()] instead of `t + c * h`. This is +//! * Use [`c.mul_add(h, t)`](f64::mul_add) instead of `t + c * h`. This is //! faster and reduces the floating-point error. It might also be beneficial -//! to use [`.scaled_add()`][.scaled_add()] or a combination of -//! [`azip!()`][azip!] and [`.mul_add()`][f64.mul_add()] on the arrays in +//! to use [`.scaled_add()`] or a combination of +//! [`azip!()`] and [`.mul_add()`](f64::mul_add) on the arrays in //! some places, but that's not demonstrated in the example below. //! //! ``` @@ -165,12 +165,10 @@ //! (y_new, error) //! } //! # -//! # fn main() {} +//! # fn main() { let _ = rk_step::, ArrayViewMut1<'_, f64>)>; } //! ``` //! -//! [f64.mul_add()]: https://doc.rust-lang.org/std/primitive.f64.html#method.mul_add -//! [.scaled_add()]: ../../../struct.ArrayBase.html#method.scaled_add -//! [azip!]: ../../../macro.azip.html +//! [`.scaled_add()`]: crate::ArrayBase::scaled_add //! //! ### SciPy license //! diff --git a/src/extension/nonnull.rs b/src/extension/nonnull.rs index 5aa50fdc2..4deee11ac 100644 --- a/src/extension/nonnull.rs +++ b/src/extension/nonnull.rs @@ -1,4 +1,5 @@ use std::ptr::NonNull; +#[cfg(not(feature = "std"))] use alloc::vec::Vec; /// Return a NonNull pointer to the vector's data diff --git a/src/free_functions.rs b/src/free_functions.rs index 95eb7dc81..11a32a1f0 100644 --- a/src/free_functions.rs +++ b/src/free_functions.rs @@ -6,15 +6,16 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::mem::{forget, size_of}; -use alloc::slice; use alloc::vec; +#[cfg(not(feature = "std"))] use alloc::vec::Vec; +use std::mem::{forget, size_of}; +use std::ptr::NonNull; use crate::imp_prelude::*; use crate::{dimension, ArcArray1, ArcArray2}; -/// Create an [**`Array`**](type.Array.html) with one, two or +/// Create an **[`Array`]** with one, two or /// three dimensions. /// /// ``` @@ -65,47 +66,104 @@ pub fn rcarr1(xs: &[A]) -> ArcArray1 { } /// Create a zero-dimensional array view borrowing `x`. -pub fn aview0(x: &A) -> ArrayView0<'_, A> { - unsafe { ArrayView::from_shape_ptr(Ix0(), x) } +pub const fn aview0(x: &A) -> ArrayView0<'_, A> { + ArrayBase { + data: ViewRepr::new(), + // Safe because references are always non-null. + ptr: unsafe { NonNull::new_unchecked(x as *const A as *mut A) }, + dim: Ix0(), + strides: Ix0(), + } } /// Create a one-dimensional array view with elements borrowing `xs`. /// +/// **Panics** if the length of the slice overflows `isize`. (This can only +/// occur if `A` is zero-sized, because slices cannot contain more than +/// `isize::MAX` number of bytes.) +/// /// ``` -/// use ndarray::aview1; +/// use ndarray::{aview1, ArrayView1}; /// /// let data = [1.0; 1024]; /// /// // Create a 2D array view from borrowed data -/// let a2d = aview1(&data).into_shape((32, 32)).unwrap(); +/// let a2d = aview1(&data).into_shape_with_order((32, 32)).unwrap(); /// /// assert_eq!(a2d.sum(), 1024.0); +/// +/// // Create a const 1D array view +/// const C: ArrayView1<'static, f64> = aview1(&[1., 2., 3.]); +/// +/// assert_eq!(C.sum(), 6.); /// ``` -pub fn aview1(xs: &[A]) -> ArrayView1<'_, A> { - ArrayView::from(xs) +pub const fn aview1(xs: &[A]) -> ArrayView1<'_, A> { + if size_of::() == 0 { + assert!( + xs.len() <= isize::MAX as usize, + "Slice length must fit in `isize`.", + ); + } + ArrayBase { + data: ViewRepr::new(), + // Safe because references are always non-null. + ptr: unsafe { NonNull::new_unchecked(xs.as_ptr() as *mut A) }, + dim: Ix1(xs.len()), + strides: Ix1(1), + } } /// Create a two-dimensional array view with elements borrowing `xs`. /// -/// **Panics** if the product of non-zero axis lengths overflows `isize`. (This -/// can only occur when `V` is zero-sized.) -pub fn aview2>(xs: &[V]) -> ArrayView2<'_, A> { - let cols = V::len(); +/// **Panics** if the product of non-zero axis lengths overflows `isize` (This +/// can only occur if A is zero-sized or if `N` is zero, because slices cannot +/// contain more than `isize::MAX` number of bytes). +/// +/// ``` +/// use ndarray::{aview2, ArrayView2}; +/// +/// let data = vec![[1., 2., 3.], [4., 5., 6.]]; +/// +/// let view = aview2(&data); +/// assert_eq!(view.sum(), 21.); +/// +/// // Create a const 2D array view +/// const C: ArrayView2<'static, f64> = aview2(&[[1., 2., 3.], [4., 5., 6.]]); +/// assert_eq!(C.sum(), 21.); +/// ``` +pub const fn aview2(xs: &[[A; N]]) -> ArrayView2<'_, A> { + let cols = N; let rows = xs.len(); - let dim = Ix2(rows, cols); - if size_of::() == 0 { - dimension::size_of_shape_checked(&dim) - .expect("Product of non-zero axis lengths must not overflow isize."); + if size_of::() == 0 { + if let Some(n_elems) = rows.checked_mul(cols) { + assert!( + rows <= isize::MAX as usize + && cols <= isize::MAX as usize + && n_elems <= isize::MAX as usize, + "Product of non-zero axis lengths must not overflow isize.", + ); + } else { + panic!("Overflow in number of elements."); + } + } else if N == 0 { + assert!( + rows <= isize::MAX as usize, + "Product of non-zero axis lengths must not overflow isize.", + ); } - // `rows` is guaranteed to fit in `isize` because we've checked the ZST - // case and slices never contain > `isize::MAX` bytes. `cols` is guaranteed - // to fit in `isize` because `FixedInitializer` is not implemented for any - // array lengths > `isize::MAX`. `cols * rows` is guaranteed to fit in - // `isize` because we've checked the ZST case and slices never contain > - // `isize::MAX` bytes. - unsafe { - let data = slice::from_raw_parts(xs.as_ptr() as *const A, cols * rows); - ArrayView::from_shape_ptr(dim, data.as_ptr()) + // Safe because references are always non-null. + let ptr = unsafe { NonNull::new_unchecked(xs.as_ptr() as *mut A) }; + let dim = Ix2(rows, cols); + let strides = if rows == 0 || cols == 0 { + Ix2(0, 0) + } else { + Ix2(cols, 1) + }; + ArrayBase { + data: ViewRepr::new(), + ptr, + dim, + strides, } } @@ -116,7 +174,7 @@ pub fn aview2>(xs: &[V]) -> ArrayView2<'_, A> { /// // Create an array view over some data, then slice it and modify it. /// let mut data = [0; 1024]; /// { -/// let mut a = aview_mut1(&mut data).into_shape((32, 32)).unwrap(); +/// let mut a = aview_mut1(&mut data).into_shape_with_order((32, 32)).unwrap(); /// a.slice_mut(s![.., ..;3]).fill(5); /// } /// assert_eq!(&data[..10], [5, 0, 0, 5, 0, 0, 5, 0, 0, 5]); @@ -127,16 +185,15 @@ pub fn aview_mut1(xs: &mut [A]) -> ArrayViewMut1<'_, A> { /// Create a two-dimensional read-write array view with elements borrowing `xs`. /// -/// **Panics** if the product of non-zero axis lengths overflows `isize`. (This -/// can only occur when `V` is zero-sized.) +/// **Panics** if the product of non-zero axis lengths overflows `isize` (This can only occur if A +/// is zero-sized because slices cannot contain more than `isize::MAX` number of bytes). /// /// # Example /// /// ``` /// use ndarray::aview_mut2; /// -/// // The inner (nested) array must be of length 1 to 16, but the outer -/// // can be of any length. +/// // The inner (nested) and outer arrays can be of any length. /// let mut data = [[0.; 2]; 128]; /// { /// // Make a 128 x 2 mut array view then turn it into 2 x 128 @@ -148,56 +205,10 @@ pub fn aview_mut1(xs: &mut [A]) -> ArrayViewMut1<'_, A> { /// // look at the start of the result /// assert_eq!(&data[..3], [[1., -1.], [1., -1.], [1., -1.]]); /// ``` -pub fn aview_mut2>(xs: &mut [V]) -> ArrayViewMut2<'_, A> { - let cols = V::len(); - let rows = xs.len(); - let dim = Ix2(rows, cols); - if size_of::() == 0 { - dimension::size_of_shape_checked(&dim) - .expect("Product of non-zero axis lengths must not overflow isize."); - } - // `rows` is guaranteed to fit in `isize` because we've checked the ZST - // case and slices never contain > `isize::MAX` bytes. `cols` is guaranteed - // to fit in `isize` because `FixedInitializer` is not implemented for any - // array lengths > `isize::MAX`. `cols * rows` is guaranteed to fit in - // `isize` because we've checked the ZST case and slices never contain > - // `isize::MAX` bytes. - unsafe { - let data = slice::from_raw_parts_mut(xs.as_mut_ptr() as *mut A, cols * rows); - ArrayViewMut::from_shape_ptr(dim, data.as_mut_ptr()) - } +pub fn aview_mut2(xs: &mut [[A; N]]) -> ArrayViewMut2<'_, A> { + ArrayViewMut2::from(xs) } -/// Fixed-size array used for array initialization -pub unsafe trait FixedInitializer { - type Elem; - fn as_init_slice(&self) -> &[Self::Elem]; - fn len() -> usize; -} - -macro_rules! impl_arr_init { - (__impl $n: expr) => ( - unsafe impl FixedInitializer for [T; $n] { - type Elem = T; - fn as_init_slice(&self) -> &[T] { self } - fn len() -> usize { $n } - } - ); - () => (); - ($n: expr, $($m:expr,)*) => ( - impl_arr_init!(__impl $n); - impl_arr_init!($($m,)*); - ) - -} - -// For implementors: If you ever implement `FixedInitializer` for array lengths -// > `isize::MAX` (e.g. once Rust adds const generics), you must update -// `aview2` and `aview_mut2` to perform the necessary checks. In particular, -// the assumption that `cols` can never exceed `isize::MAX` would be incorrect. -// (Consider e.g. `let xs: &[[i32; ::std::usize::MAX]] = &[]`.) -impl_arr_init!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,); - /// Create a two-dimensional array with elements from `xs`. /// /// ``` @@ -209,22 +220,16 @@ impl_arr_init!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,); /// a.shape() == [2, 3] /// ); /// ``` -pub fn arr2>(xs: &[V]) -> Array2 -where - V: Clone, -{ +pub fn arr2(xs: &[[A; N]]) -> Array2 { Array2::from(xs.to_vec()) } -impl From> for Array2 -where - V: FixedInitializer, -{ +impl From> for Array2 { /// Converts the `Vec` of arrays to an owned 2-D array. /// /// **Panics** if the product of non-zero axis lengths overflows `isize`. - fn from(mut xs: Vec) -> Self { - let dim = Ix2(xs.len(), V::len()); + fn from(mut xs: Vec<[A; N]>) -> Self { + let dim = Ix2(xs.len(), N); let ptr = xs.as_mut_ptr(); let cap = xs.capacity(); let expand_len = dimension::size_of_shape_checked(&dim) @@ -233,12 +238,12 @@ where unsafe { let v = if size_of::() == 0 { Vec::from_raw_parts(ptr as *mut A, expand_len, expand_len) - } else if V::len() == 0 { + } else if N == 0 { Vec::new() } else { // Guaranteed not to overflow in this case since A is non-ZST // and Vec never allocates more than isize bytes. - let expand_cap = cap * V::len(); + let expand_cap = cap * N; Vec::from_raw_parts(ptr as *mut A, expand_len, expand_cap) }; ArrayBase::from_shape_vec_unchecked(dim, v) @@ -246,16 +251,12 @@ where } } -impl From> for Array3 -where - V: FixedInitializer, - U: FixedInitializer, -{ +impl From> for Array3 { /// Converts the `Vec` of arrays to an owned 3-D array. /// /// **Panics** if the product of non-zero axis lengths overflows `isize`. - fn from(mut xs: Vec) -> Self { - let dim = Ix3(xs.len(), V::len(), U::len()); + fn from(mut xs: Vec<[[A; M]; N]>) -> Self { + let dim = Ix3(xs.len(), N, M); let ptr = xs.as_mut_ptr(); let cap = xs.capacity(); let expand_len = dimension::size_of_shape_checked(&dim) @@ -264,12 +265,12 @@ where unsafe { let v = if size_of::() == 0 { Vec::from_raw_parts(ptr as *mut A, expand_len, expand_len) - } else if V::len() == 0 || U::len() == 0 { + } else if N == 0 || M == 0 { Vec::new() } else { // Guaranteed not to overflow in this case since A is non-ZST // and Vec never allocates more than isize bytes. - let expand_cap = cap * V::len() * U::len(); + let expand_cap = cap * N * M; Vec::from_raw_parts(ptr as *mut A, expand_len, expand_cap) }; ArrayBase::from_shape_vec_unchecked(dim, v) @@ -279,7 +280,7 @@ where /// Create a two-dimensional array with elements from `xs`. /// -pub fn rcarr2>(xs: &[V]) -> ArcArray2 { +pub fn rcarr2(xs: &[[A; N]]) -> ArcArray2 { arr2(xs).into_shared() } @@ -300,23 +301,11 @@ pub fn rcarr2>(xs: &[V]) -> ArcA /// a.shape() == [3, 2, 2] /// ); /// ``` -pub fn arr3, U: FixedInitializer>( - xs: &[V], -) -> Array3 -where - V: Clone, - U: Clone, -{ +pub fn arr3(xs: &[[[A; M]; N]]) -> Array3 { Array3::from(xs.to_vec()) } /// Create a three-dimensional array with elements from `xs`. -pub fn rcarr3, U: FixedInitializer>( - xs: &[V], -) -> ArcArray -where - V: Clone, - U: Clone, -{ +pub fn rcarr3(xs: &[[[A; M]; N]]) -> ArcArray { arr3(xs).into_shared() } diff --git a/src/impl_1d.rs b/src/impl_1d.rs index 704d8643d..a9fe84407 100644 --- a/src/impl_1d.rs +++ b/src/impl_1d.rs @@ -7,8 +7,12 @@ // except according to those terms. //! Methods for one-dimensional arrays. +#[cfg(not(feature = "std"))] use alloc::vec::Vec; +use std::mem::MaybeUninit; + use crate::imp_prelude::*; +use crate::low_level_util::AbortIfPanic; /// # Methods For 1-D Arrays impl ArrayBase @@ -27,4 +31,35 @@ where crate::iterators::to_vec(self.iter().cloned()) } } + + /// Rotate the elements of the array by 1 element towards the front; + /// the former first element becomes the last. + pub(crate) fn rotate1_front(&mut self) + where + S: DataMut, + { + // use swapping to keep all elements initialized (as required by owned storage) + let mut lane_iter = self.iter_mut(); + let mut dst = if let Some(dst) = lane_iter.next() { dst } else { return }; + + // Logically we do a circular swap here, all elements in a chain + // Using MaybeUninit to avoid unnecessary writes in the safe swap solution + // + // for elt in lane_iter { + // std::mem::swap(dst, elt); + // dst = elt; + // } + // + let guard = AbortIfPanic(&"rotate1_front: temporarily moving out of owned value"); + let mut slot = MaybeUninit::::uninit(); + unsafe { + slot.as_mut_ptr().copy_from_nonoverlapping(dst, 1); + for elt in lane_iter { + (dst as *mut A).copy_from_nonoverlapping(elt, 1); + dst = elt; + } + (dst as *mut A).copy_from_nonoverlapping(slot.as_ptr(), 1); + } + guard.defuse(); + } } diff --git a/src/impl_2d.rs b/src/impl_2d.rs index cf1510618..cd5cf7e5c 100644 --- a/src/impl_2d.rs +++ b/src/impl_2d.rs @@ -52,9 +52,19 @@ where /// Return the number of rows (length of `Axis(0)`) in the two-dimensional array. /// /// ``` - /// use ndarray::array; - /// let array = array![[1., 2.], [3., 4.]]; - /// assert_eq!(array.nrows(), 2usize); + /// use ndarray::{array, Axis}; + /// + /// let array = array![[1., 2.], + /// [3., 4.], + /// [5., 6.]]; + /// assert_eq!(array.nrows(), 3); + /// + /// // equivalent ways of getting the dimensions + /// // get nrows, ncols by using dim: + /// let (m, n) = array.dim(); + /// assert_eq!(m, array.nrows()); + /// // get length of any particular axis with .len_of() + /// assert_eq!(m, array.len_of(Axis(0))); /// ``` pub fn nrows(&self) -> usize { self.len_of(Axis(0)) @@ -98,9 +108,19 @@ where /// Return the number of columns (length of `Axis(1)`) in the two-dimensional array. /// /// ``` - /// use ndarray::array; - /// let array = array![[1., 2.], [3., 4.]]; - /// assert_eq!(array.ncols(), 2usize); + /// use ndarray::{array, Axis}; + /// + /// let array = array![[1., 2.], + /// [3., 4.], + /// [5., 6.]]; + /// assert_eq!(array.ncols(), 2); + /// + /// // equivalent ways of getting the dimensions + /// // get nrows, ncols by using dim: + /// let (m, n) = array.dim(); + /// assert_eq!(n, array.ncols()); + /// // get length of any particular axis with .len_of() + /// assert_eq!(n, array.len_of(Axis(1))); /// ``` pub fn ncols(&self) -> usize { self.len_of(Axis(1)) @@ -109,13 +129,13 @@ where /// Return true if the array is square, false otherwise. /// /// # Examples - /// Sqaure: + /// Square: /// ``` /// use ndarray::array; /// let array = array![[1., 2.], [3., 4.]]; /// assert!(array.is_square()); /// ``` - /// Not sqaure: + /// Not square: /// ``` /// use ndarray::array; /// let array = array![[1., 2., 5.], [3., 4., 6.]]; diff --git a/src/impl_constructors.rs b/src/impl_constructors.rs index d082a5ce3..94ddebcd6 100644 --- a/src/impl_constructors.rs +++ b/src/impl_constructors.rs @@ -17,10 +17,11 @@ use num_traits::{One, Zero}; use std::mem; use std::mem::MaybeUninit; use alloc::vec; +#[cfg(not(feature = "std"))] use alloc::vec::Vec; use crate::dimension; -use crate::dimension::offset_from_ptr_to_memory; +use crate::dimension::offset_from_low_addr_ptr_to_logical_ptr; use crate::error::{self, ShapeError}; use crate::extension::nonnull::nonnull_from_vec_data; use crate::imp_prelude::*; @@ -29,6 +30,7 @@ use crate::indices; #[cfg(feature = "std")] use crate::iterators::to_vec; use crate::iterators::to_vec_mapped; +use crate::iterators::TrustedIterator; use crate::StrideShape; #[cfg(feature = "std")] use crate::{geomspace, linspace, logspace}; @@ -132,10 +134,10 @@ where /// to type `A` fails. /// /// ```rust + /// # #[cfg(feature = "approx")] { /// use approx::assert_abs_diff_eq; /// use ndarray::{Array, arr1}; /// - /// # #[cfg(feature = "approx")] { /// let array = Array::logspace(10.0, 0.0, 3.0, 4); /// assert_abs_diff_eq!(array, arr1(&[1e0, 1e1, 1e2, 1e3])); /// @@ -162,11 +164,11 @@ where /// to type `A` fails. /// /// ```rust + /// # fn example() -> Option<()> { + /// # #[cfg(feature = "approx")] { /// use approx::assert_abs_diff_eq; /// use ndarray::{Array, arr1}; /// - /// # fn example() -> Option<()> { - /// # #[cfg(feature = "approx")] { /// let array = Array::geomspace(1e0, 1e3, 4)?; /// assert_abs_diff_eq!(array, arr1(&[1e0, 1e1, 1e2, 1e3]), epsilon = 1e-12); /// @@ -226,9 +228,32 @@ where { let n = diag.len(); let mut arr = Self::zeros((n, n)); - arr.diag_mut().assign(&diag); + arr.diag_mut().assign(diag); arr } + + /// Create a square 2D matrix of the specified size, with the specified + /// element along the diagonal and zeros elsewhere. + /// + /// **Panics** if `n * n` would overflow `isize`. + /// + /// ```rust + /// use ndarray::{array, Array2}; + /// + /// let array = Array2::from_diag_elem(2, 5.); + /// assert_eq!(array, array![[5., 0.], [0., 5.]]); + /// ``` + pub fn from_diag_elem(n: usize, elem: A) -> Self + where + S: DataMut, + A: Clone + Zero, + { + let mut eye = Self::zeros((n, n)); + for a_ii in eye.diag_mut() { + *a_ii = elem.clone(); + } + eye + } } #[cfg(not(debug_assertions))] @@ -268,7 +293,7 @@ macro_rules! size_of_shape_checked_unwrap { /// column major (“f” order) memory layout instead of the default row major. /// For example `Array::zeros((5, 6).f())` makes a column major 5 × 6 array. /// -/// Use [`IxDyn`](type.IxDyn.html) for the shape to create an array with dynamic +/// Use [`type@IxDyn`] for the shape to create an array with dynamic /// number of axes. /// /// Finally, the few constructors that take a completely general @@ -304,7 +329,7 @@ where A: Clone, Sh: ShapeBuilder, { - let shape = shape.into_shape(); + let shape = shape.into_shape_with_order(); let size = size_of_shape_checked_unwrap!(&shape.dim); let v = vec![elem; size]; unsafe { Self::from_shape_vec_unchecked(shape, v) } @@ -358,7 +383,7 @@ where Sh: ShapeBuilder, F: FnMut() -> A, { - let shape = shape.into_shape(); + let shape = shape.into_shape_with_order(); let len = size_of_shape_checked_unwrap!(&shape.dim); let v = to_vec_mapped(0..len, move |_| f()); unsafe { Self::from_shape_vec_unchecked(shape, v) } @@ -389,7 +414,7 @@ where Sh: ShapeBuilder, F: FnMut(D::Pattern) -> A, { - let shape = shape.into_shape(); + let shape = shape.into_shape_with_order(); let _ = size_of_shape_checked_unwrap!(&shape.dim); if shape.is_c() { let v = to_vec_mapped(indices(shape.dim.clone()).into_iter(), f); @@ -491,11 +516,32 @@ where // debug check for issues that indicates wrong use of this constructor debug_assert!(dimension::can_index_slice(&v, &dim, &strides).is_ok()); - let ptr = nonnull_from_vec_data(&mut v).offset(-offset_from_ptr_to_memory(&dim, &strides)); + let ptr = nonnull_from_vec_data(&mut v).add(offset_from_low_addr_ptr_to_logical_ptr(&dim, &strides)); ArrayBase::from_data_ptr(DataOwned::new(v), ptr).with_strides_dim(strides, dim) } - /// Create an array with uninitalized elements, shape `shape`. + /// Creates an array from an iterator, mapped by `map` and interpret it according to the + /// provided shape and strides. + /// + /// # Safety + /// + /// See from_shape_vec_unchecked + pub(crate) unsafe fn from_shape_trusted_iter_unchecked(shape: Sh, iter: I, map: F) + -> Self + where + Sh: Into>, + I: TrustedIterator + ExactSizeIterator, + F: FnMut(I::Item) -> A, + { + let shape = shape.into(); + let dim = shape.dim; + let strides = shape.strides.strides_for_dim(&dim); + let v = to_vec_mapped(iter, map); + Self::from_vec_dim_stride_unchecked(dim, strides, v) + } + + + /// Create an array with uninitialized elements, shape `shape`. /// /// The uninitialized elements of type `A` are represented by the type `MaybeUninit`, /// an easier way to handle uninit values correctly. @@ -508,7 +554,7 @@ where /// ### Safety /// /// The whole of the array must be initialized before it is converted - /// using [`.assume_init()`] or otherwise traversed. + /// using [`.assume_init()`] or otherwise traversed/read with the element type `A`. /// /// ### Examples /// @@ -519,8 +565,6 @@ where /// /// ``` /// use ndarray::{s, Array2}; - /// use ndarray::Zip; - /// use ndarray::Axis; /// /// // Example Task: Let's create a column shifted copy of the input /// @@ -539,13 +583,15 @@ where /// b.assume_init() /// } /// } + /// + /// # let _ = shift_by_two; /// ``` pub fn uninit(shape: Sh) -> ArrayBase where Sh: ShapeBuilder, { unsafe { - let shape = shape.into_shape(); + let shape = shape.into_shape_with_order(); let size = size_of_shape_checked_unwrap!(&shape.dim); let mut v = Vec::with_capacity(size); v.set_len(size); @@ -553,15 +599,15 @@ where } } - /// Create an array with uninitalized elements, shape `shape`. + /// Create an array with uninitialized elements, shape `shape`. /// /// The uninitialized elements of type `A` are represented by the type `MaybeUninit`, /// an easier way to handle uninit values correctly. /// - /// The `builder` closure gets unshared access to the array through a raw view - /// and can use it to modify the array before it is returned. This allows initializing - /// the array for any owned array type (avoiding clone requirements for copy-on-write, - /// because the array is unshared when initially created). + /// The `builder` closure gets unshared access to the array through a view and can use it to + /// modify the array before it is returned. This allows initializing the array for any owned + /// array type (avoiding clone requirements for copy-on-write, because the array is unshared + /// when initially created). /// /// Only *when* the array is completely initialized with valid elements, can it be /// converted to an array of `A` elements using [`.assume_init()`]. @@ -571,24 +617,26 @@ where /// ### Safety /// /// The whole of the array must be initialized before it is converted - /// using [`.assume_init()`] or otherwise traversed. + /// using [`.assume_init()`] or otherwise traversed/read with the element type `A`. /// - pub(crate) fn build_uninit(shape: Sh, builder: F) -> ArrayBase + /// [`.assume_init()`]: ArrayBase::assume_init + pub fn build_uninit(shape: Sh, builder: F) -> ArrayBase where Sh: ShapeBuilder, - F: FnOnce(RawArrayViewMut, D>), + F: FnOnce(ArrayViewMut, D>), { let mut array = Self::uninit(shape); // Safe because: the array is unshared here unsafe { - builder(array.raw_view_mut_unchecked()); + builder(array.raw_view_mut_unchecked().deref_into_view_mut()); } array } #[deprecated(note = "This method is hard to use correctly. Use `uninit` instead.", since = "0.15.0")] - /// Create an array with uninitalized elements, shape `shape`. + #[allow(clippy::uninit_vec)] // this is explicitly intended to create uninitialized memory + /// Create an array with uninitialized elements, shape `shape`. /// /// Prefer to use [`uninit()`](ArrayBase::uninit) if possible, because it is /// easier to use correctly. @@ -597,7 +645,7 @@ where /// /// ### Safety /// - /// Accessing uninitalized values is undefined behaviour. You must overwrite *all* the elements + /// Accessing uninitialized values is undefined behaviour. You must overwrite *all* the elements /// in the array after it is created; for example using /// [`raw_view_mut`](ArrayBase::raw_view_mut) or other low-level element access. /// @@ -616,7 +664,7 @@ where A: Copy, Sh: ShapeBuilder, { - let shape = shape.into_shape(); + let shape = shape.into_shape_with_order(); let size = size_of_shape_checked_unwrap!(&shape.dim); let mut v = Vec::with_capacity(size); v.set_len(size); @@ -630,7 +678,7 @@ where S: DataOwned>, D: Dimension, { - /// Create an array with uninitalized elements, shape `shape`. + /// Create an array with uninitialized elements, shape `shape`. /// /// This method has been renamed to `uninit` #[deprecated(note = "Renamed to `uninit`", since = "0.15.0")] @@ -639,7 +687,7 @@ where Sh: ShapeBuilder, { unsafe { - let shape = shape.into_shape(); + let shape = shape.into_shape_with_order(); let size = size_of_shape_checked_unwrap!(&shape.dim); let mut v = Vec::with_capacity(size); v.set_len(size); diff --git a/src/impl_cow.rs b/src/impl_cow.rs index b880fd62c..22d5c78b2 100644 --- a/src/impl_cow.rs +++ b/src/impl_cow.rs @@ -11,8 +11,6 @@ use crate::imp_prelude::*; /// Methods specific to `CowArray`. /// /// ***See also all methods for [`ArrayBase`]*** -/// -/// [`ArrayBase`]: struct.ArrayBase.html impl<'a, A, D> CowArray<'a, A, D> where D: Dimension, @@ -53,3 +51,34 @@ where } } } + +impl<'a, A, Slice: ?Sized> From<&'a Slice> for CowArray<'a, A, Ix1> +where + Slice: AsRef<[A]>, +{ + /// Create a one-dimensional clone-on-write view of the data in `slice`. + /// + /// **Panics** if the slice length is greater than [`isize::MAX`]. + /// + /// ``` + /// use ndarray::{array, CowArray}; + /// + /// let array = CowArray::from(&[1., 2., 3., 4.]); + /// assert!(array.is_view()); + /// assert_eq!(array, array![1., 2., 3., 4.]); + /// ``` + fn from(slice: &'a Slice) -> Self { + Self::from(ArrayView1::from(slice)) + } +} + +impl<'a, A, S, D> From<&'a ArrayBase> for CowArray<'a, A, D> +where + S: Data, + D: Dimension, +{ + /// Create a read-only clone-on-write view of the array. + fn from(array: &'a ArrayBase) -> Self { + Self::from(array.view()) + } +} diff --git a/src/impl_methods.rs b/src/impl_methods.rs index 353f7f441..4d736c51b 100644 --- a/src/impl_methods.rs +++ b/src/impl_methods.rs @@ -9,6 +9,7 @@ use std::mem::{size_of, ManuallyDrop}; use alloc::slice; use alloc::vec; +#[cfg(not(feature = "std"))] use alloc::vec::Vec; use rawpointer::PointerExt; @@ -20,14 +21,17 @@ use crate::dimension; use crate::dimension::IntoDimension; use crate::dimension::{ abs_index, axes_of, do_slice, merge_axes, move_min_stride_axis_to_last, - offset_from_ptr_to_memory, size_of_shape_checked, stride_offset, Axes, + offset_from_low_addr_ptr_to_logical_ptr, size_of_shape_checked, stride_offset, Axes, }; use crate::dimension::broadcast::co_broadcast; +use crate::dimension::reshape_dim; use crate::error::{self, ErrorKind, ShapeError, from_kind}; use crate::math_cell::MathCell; use crate::itertools::zip; -use crate::zip::{IntoNdProducer, Zip}; use crate::AxisDescription; +use crate::order::Order; +use crate::shape_builder::ShapeArg; +use crate::zip::{IntoNdProducer, Zip}; use crate::iter::{ AxisChunksIter, AxisChunksIterMut, AxisIter, AxisIterMut, ExactChunks, ExactChunksMut, @@ -178,7 +182,7 @@ where /// If the input array is contiguous, then the output array will have the same /// memory layout. Otherwise, the layout of the output array is unspecified. /// If you need a particular layout, you can allocate a new array with the - /// desired memory layout and [`.assign()`](#method.assign) the data. + /// desired memory layout and [`.assign()`](Self::assign) the data. /// Alternatively, you can collectan iterator, like this for a result in /// standard layout: /// @@ -216,7 +220,7 @@ where ) } } else { - self.map(|x| x.clone()) + self.map(A::clone) } } @@ -240,6 +244,34 @@ where S::into_owned(self) } + /// Converts the array into `Array` if this is possible without + /// cloning the array elements. Otherwise, returns `self` unchanged. + /// + /// ``` + /// use ndarray::{array, rcarr2, ArcArray2, Array2}; + /// + /// // Reference-counted, clone-on-write `ArcArray`. + /// let a: ArcArray2<_> = rcarr2(&[[1., 2.], [3., 4.]]); + /// { + /// // Another reference to the same data. + /// let b: ArcArray2<_> = a.clone(); + /// // Since there are two references to the same data, `.into_owned()` + /// // would require cloning the data, so `.try_into_owned_nocopy()` + /// // returns `Err`. + /// assert!(b.try_into_owned_nocopy().is_err()); + /// } + /// // Here, since the second reference has been dropped, the `ArcArray` + /// // can be converted into an `Array` without cloning the data. + /// let unique: Array2<_> = a.try_into_owned_nocopy().unwrap(); + /// assert_eq!(unique, array![[1., 2.], [3., 4.]]); + /// ``` + pub fn try_into_owned_nocopy(self) -> Result, Self> + where + S: Data, + { + S::try_into_owned_nocopy(self) + } + /// Turn the array into a shared ownership (copy on write) array, /// without any copying. pub fn into_shared(self) -> ArcArray @@ -255,6 +287,19 @@ where /// Returns a reference to the first element of the array, or `None` if it /// is empty. + /// + /// # Example + /// + /// ```rust + /// use ndarray::Array3; + /// + /// let mut a = Array3::::zeros([3, 4, 2]); + /// a[[0, 0, 0]] = 42.; + /// assert_eq!(a.first(), Some(&42.)); + /// + /// let b = Array3::::zeros([3, 0, 5]); + /// assert_eq!(b.first(), None); + /// ``` pub fn first(&self) -> Option<&A> where S: Data, @@ -268,6 +313,19 @@ where /// Returns a mutable reference to the first element of the array, or /// `None` if it is empty. + /// + /// # Example + /// + /// ```rust + /// use ndarray::Array3; + /// + /// let mut a = Array3::::zeros([3, 4, 2]); + /// *a.first_mut().unwrap() = 42.; + /// assert_eq!(a[[0, 0, 0]], 42.); + /// + /// let mut b = Array3::::zeros([3, 0, 5]); + /// assert_eq!(b.first_mut(), None); + /// ``` pub fn first_mut(&mut self) -> Option<&mut A> where S: DataMut, @@ -279,6 +337,66 @@ where } } + /// Returns a reference to the last element of the array, or `None` if it + /// is empty. + /// + /// # Example + /// + /// ```rust + /// use ndarray::Array3; + /// + /// let mut a = Array3::::zeros([3, 4, 2]); + /// a[[2, 3, 1]] = 42.; + /// assert_eq!(a.last(), Some(&42.)); + /// + /// let b = Array3::::zeros([3, 0, 5]); + /// assert_eq!(b.last(), None); + /// ``` + pub fn last(&self) -> Option<&A> + where + S: Data, + { + if self.is_empty() { + None + } else { + let mut index = self.raw_dim(); + for ax in 0..index.ndim() { + index[ax] -= 1; + } + Some(unsafe { self.uget(index) }) + } + } + + /// Returns a mutable reference to the last element of the array, or `None` + /// if it is empty. + /// + /// # Example + /// + /// ```rust + /// use ndarray::Array3; + /// + /// let mut a = Array3::::zeros([3, 4, 2]); + /// *a.last_mut().unwrap() = 42.; + /// assert_eq!(a[[2, 3, 1]], 42.); + /// + /// let mut b = Array3::::zeros([3, 0, 5]); + /// assert_eq!(b.last_mut(), None); + /// ``` + pub fn last_mut(&mut self) -> Option<&mut A> + where + S: DataMut, + { + if self.is_empty() { + None + } else { + let mut index = self.raw_dim(); + for ax in 0..index.ndim() { + index[ax] -= 1; + } + Some(unsafe { self.uget_mut(index) }) + } + } + /// Return an iterator of references to the elements of the array. /// /// Elements are visited in the *logical order* of the array, which @@ -313,7 +431,7 @@ where /// /// Iterator element type is `(D::Pattern, &A)`. /// - /// See also [`Zip::indexed`](struct.Zip.html) + /// See also [`Zip::indexed`] pub fn indexed_iter(&self) -> IndexedIter<'_, A, D> where S: Data, @@ -461,9 +579,9 @@ where /// collapsed, as in [`.collapse_axis()`], rather than removed, as in /// [`.slice_move()`] or [`.index_axis_move()`]. /// - /// [`.collapse_axis()`]: #method.collapse_axis - /// [`.slice_move()`]: #method.slice_move - /// [`.index_axis_move()`]: #method.index_axis_move + /// [`.collapse_axis()`]: Self::collapse_axis + /// [`.slice_move()`]: Self::slice_move + /// [`.index_axis_move()`]: Self::index_axis_move /// /// See [*Slicing*](#slicing) for full documentation. /// See also [`s!`], [`SliceArg`], and [`SliceInfo`](crate::SliceInfo). @@ -506,6 +624,7 @@ where /// **Panics** if an index is out of bounds or step size is zero.
/// **Panics** if `axis` is out of bounds. #[track_caller] + #[must_use = "slice_axis returns an array view with the sliced result"] pub fn slice_axis(&self, axis: Axis, indices: Slice) -> ArrayView<'_, A, D> where S: Data, @@ -520,6 +639,7 @@ where /// **Panics** if an index is out of bounds or step size is zero.
/// **Panics** if `axis` is out of bounds. #[track_caller] + #[must_use = "slice_axis_mut returns an array view with the sliced result"] pub fn slice_axis_mut(&mut self, axis: Axis, indices: Slice) -> ArrayViewMut<'_, A, D> where S: DataMut, @@ -626,13 +746,26 @@ where /// ``` pub fn get(&self, index: I) -> Option<&A> where - I: NdIndex, S: Data, + I: NdIndex, { unsafe { self.get_ptr(index).map(|ptr| &*ptr) } } - pub(crate) fn get_ptr(&self, index: I) -> Option<*const A> + /// Return a raw pointer to the element at `index`, or return `None` + /// if the index is out of bounds. + /// + /// ``` + /// use ndarray::arr2; + /// + /// let a = arr2(&[[1., 2.], [3., 4.]]); + /// + /// let v = a.raw_view(); + /// let p = a.get_ptr((0, 1)).unwrap(); + /// + /// assert_eq!(unsafe { *p }, 2.); + /// ``` + pub fn get_ptr(&self, index: I) -> Option<*const A> where I: NdIndex, { @@ -649,10 +782,27 @@ where S: DataMut, I: NdIndex, { - unsafe { self.get_ptr_mut(index).map(|ptr| &mut *ptr) } + unsafe { self.get_mut_ptr(index).map(|ptr| &mut *ptr) } } - pub(crate) fn get_ptr_mut(&mut self, index: I) -> Option<*mut A> + /// Return a raw pointer to the element at `index`, or return `None` + /// if the index is out of bounds. + /// + /// ``` + /// use ndarray::arr2; + /// + /// let mut a = arr2(&[[1., 2.], [3., 4.]]); + /// + /// let v = a.raw_view_mut(); + /// let p = a.get_mut_ptr((0, 1)).unwrap(); + /// + /// unsafe { + /// *p = 5.; + /// } + /// + /// assert_eq!(a.get((0, 1)), Some(&5.)); + /// ``` + pub fn get_mut_ptr(&mut self, index: I) -> Option<*mut A> where S: RawDataMut, I: NdIndex, @@ -841,7 +991,7 @@ where /// Collapses the array to `index` along the axis and removes the axis. /// - /// See [`.index_axis()`](#method.index_axis) and [*Subviews*](#subviews) for full documentation. + /// See [`.index_axis()`](Self::index_axis) and [*Subviews*](#subviews) for full documentation. /// /// **Panics** if `axis` or `index` is out of bounds. #[track_caller] @@ -892,20 +1042,39 @@ where #[track_caller] pub fn select(&self, axis: Axis, indices: &[Ix]) -> Array where - A: Copy, + A: Clone, S: Data, D: RemoveAxis, { - let mut subs = vec![self.view(); indices.len()]; - for (&i, sub) in zip(indices, &mut subs[..]) { - sub.collapse_axis(axis, i); - } - if subs.is_empty() { - let mut dim = self.raw_dim(); - dim.set_axis(axis, 0); - unsafe { Array::from_shape_vec_unchecked(dim, vec![]) } + if self.ndim() == 1 { + // using .len_of(axis) means that we check if `axis` is in bounds too. + let axis_len = self.len_of(axis); + // bounds check the indices first + if let Some(max_index) = indices.iter().cloned().max() { + if max_index >= axis_len { + panic!("ndarray: index {} is out of bounds in array of len {}", + max_index, self.len_of(axis)); + } + } // else: indices empty is ok + let view = self.view().into_dimensionality::().unwrap(); + Array::from_iter(indices.iter().map(move |&index| { + // Safety: bounds checked indexes + unsafe { + view.uget(index).clone() + } + })).into_dimensionality::().unwrap() } else { - concatenate(axis, &subs).unwrap() + let mut subs = vec![self.view(); indices.len()]; + for (&i, sub) in zip(indices, &mut subs[..]) { + sub.collapse_axis(axis, i); + } + if subs.is_empty() { + let mut dim = self.raw_dim(); + dim.set_axis(axis, 0); + unsafe { Array::from_shape_vec_unchecked(dim, vec![]) } + } else { + concatenate(axis, &subs).unwrap() + } } } @@ -923,7 +1092,7 @@ where /// Iterator element is `ArrayView1
` (1D array view). /// /// ``` - /// use ndarray::{arr3, Axis, arr1}; + /// use ndarray::arr3; /// /// let a = arr3(&[[[ 0, 1, 2], // -- row 0, 0 /// [ 3, 4, 5]], // -- row 0, 1 @@ -991,7 +1160,7 @@ where /// Iterator element is `ArrayView1` (1D array view). /// /// ``` - /// use ndarray::{arr3, Axis, arr1}; + /// use ndarray::arr3; /// /// // The generalized columns of a 3D array: /// // are directed along the 0th axis: 0 and 6, 1 and 7 and so on... @@ -999,7 +1168,7 @@ where /// [[ 6, 7, 8], [ 9, 10, 11]]]); /// /// // Here `columns` will yield the six generalized columns of the array. - /// for row in a.columns() { + /// for column in a.columns() { /// /* loop body */ /// } /// ``` @@ -1174,9 +1343,8 @@ where /// ``` /// use ndarray::Array; /// use ndarray::{arr3, Axis}; - /// use std::iter::FromIterator; /// - /// let a = Array::from_iter(0..28).into_shape((2, 7, 2)).unwrap(); + /// let a = Array::from_iter(0..28).into_shape_with_order((2, 7, 2)).unwrap(); /// let mut iter = a.axis_chunks_iter(Axis(1), 2); /// /// // first iteration yields a 2 × 2 × 2 view @@ -1276,44 +1444,111 @@ where /// The windows are all distinct overlapping views of size `window_size` /// that fit into the array's shape. /// - /// This produces no elements if the window size is larger than the actual array size along any - /// axis. + /// This is essentially equivalent to [`.windows_with_stride()`] with unit stride. + #[track_caller] + pub fn windows(&self, window_size: E) -> Windows<'_, A, D> + where + E: IntoDimension, + S: Data, + { + Windows::new(self.view(), window_size) + } + + /// Return a window producer and iterable. + /// + /// The windows are all distinct views of size `window_size` + /// that fit into the array's shape. + /// + /// The stride is ordered by the outermost axis.
+ /// Hence, a (x₀, x₁, ..., xₙ) stride will be applied to + /// (A₀, A₁, ..., Aₙ) where Aₓ stands for `Axis(x)`. + /// + /// This produces all windows that fit within the array for the given stride, + /// assuming the window size is not larger than the array size. /// /// The produced element is an `ArrayView` with exactly the dimension /// `window_size`. /// - /// **Panics** if any dimension of `window_size` is zero.
- /// (**Panics** if `D` is `IxDyn` and `window_size` does not match the + /// Note that passing a stride of only ones is similar to + /// calling [`ArrayBase::windows()`]. + /// + /// **Panics** if any dimension of `window_size` or `stride` is zero.
+ /// (**Panics** if `D` is `IxDyn` and `window_size` or `stride` does not match the /// number of array axes.) /// - /// This is an illustration of the 2×2 windows in a 3×4 array: + /// This is the same illustration found in [`ArrayBase::windows()`], + /// 2×2 windows in a 3×4 array, but now with a (1, 2) stride: /// /// ```text /// ──▶ Axis(1) /// - /// │ ┏━━━━━┳━━━━━┱─────┬─────┐ ┌─────┲━━━━━┳━━━━━┱─────┐ ┌─────┬─────┲━━━━━┳━━━━━┓ - /// ▼ ┃ a₀₀ ┃ a₀₁ ┃ │ │ │ ┃ a₀₁ ┃ a₀₂ ┃ │ │ │ ┃ a₀₂ ┃ a₀₃ ┃ - /// Axis(0) ┣━━━━━╋━━━━━╉─────┼─────┤ ├─────╊━━━━━╋━━━━━╉─────┤ ├─────┼─────╊━━━━━╋━━━━━┫ - /// ┃ a₁₀ ┃ a₁₁ ┃ │ │ │ ┃ a₁₁ ┃ a₁₂ ┃ │ │ │ ┃ a₁₂ ┃ a₁₃ ┃ - /// ┡━━━━━╇━━━━━╃─────┼─────┤ ├─────╄━━━━━╇━━━━━╃─────┤ ├─────┼─────╄━━━━━╇━━━━━┩ - /// │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ - /// └─────┴─────┴─────┴─────┘ └─────┴─────┴─────┴─────┘ └─────┴─────┴─────┴─────┘ - /// - /// ┌─────┬─────┬─────┬─────┐ ┌─────┬─────┬─────┬─────┐ ┌─────┬─────┬─────┬─────┐ - /// │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ - /// ┢━━━━━╈━━━━━╅─────┼─────┤ ├─────╆━━━━━╈━━━━━╅─────┤ ├─────┼─────╆━━━━━╈━━━━━┪ - /// ┃ a₁₀ ┃ a₁₁ ┃ │ │ │ ┃ a₁₁ ┃ a₁₂ ┃ │ │ │ ┃ a₁₂ ┃ a₁₃ ┃ - /// ┣━━━━━╋━━━━━╉─────┼─────┤ ├─────╊━━━━━╋━━━━━╉─────┤ ├─────┼─────╊━━━━━╋━━━━━┫ - /// ┃ a₂₀ ┃ a₂₁ ┃ │ │ │ ┃ a₂₁ ┃ a₂₂ ┃ │ │ │ ┃ a₂₂ ┃ a₂₃ ┃ - /// ┗━━━━━┻━━━━━┹─────┴─────┘ └─────┺━━━━━┻━━━━━┹─────┘ └─────┴─────┺━━━━━┻━━━━━┛ + /// │ ┏━━━━━┳━━━━━┱─────┬─────┐ ┌─────┬─────┲━━━━━┳━━━━━┓ + /// ▼ ┃ a₀₀ ┃ a₀₁ ┃ │ │ │ │ ┃ a₀₂ ┃ a₀₃ ┃ + /// Axis(0) ┣━━━━━╋━━━━━╉─────┼─────┤ ├─────┼─────╊━━━━━╋━━━━━┫ + /// ┃ a₁₀ ┃ a₁₁ ┃ │ │ │ │ ┃ a₁₂ ┃ a₁₃ ┃ + /// ┡━━━━━╇━━━━━╃─────┼─────┤ ├─────┼─────╄━━━━━╇━━━━━┩ + /// │ │ │ │ │ │ │ │ │ │ + /// └─────┴─────┴─────┴─────┘ └─────┴─────┴─────┴─────┘ + /// + /// ┌─────┬─────┬─────┬─────┐ ┌─────┬─────┬─────┬─────┐ + /// │ │ │ │ │ │ │ │ │ │ + /// ┢━━━━━╈━━━━━╅─────┼─────┤ ├─────┼─────╆━━━━━╈━━━━━┪ + /// ┃ a₁₀ ┃ a₁₁ ┃ │ │ │ │ ┃ a₁₂ ┃ a₁₃ ┃ + /// ┣━━━━━╋━━━━━╉─────┼─────┤ ├─────┼─────╊━━━━━╋━━━━━┫ + /// ┃ a₂₀ ┃ a₂₁ ┃ │ │ │ │ ┃ a₂₂ ┃ a₂₃ ┃ + /// ┗━━━━━┻━━━━━┹─────┴─────┘ └─────┴─────┺━━━━━┻━━━━━┛ /// ``` #[track_caller] - pub fn windows(&self, window_size: E) -> Windows<'_, A, D> + pub fn windows_with_stride(&self, window_size: E, stride: E) -> Windows<'_, A, D> where E: IntoDimension, S: Data, { - Windows::new(self.view(), window_size) + Windows::new_with_stride(self.view(), window_size, stride) + } + + /// Returns a producer which traverses over all windows of a given length along an axis. + /// + /// The windows are all distinct, possibly-overlapping views. The shape of each window + /// is the shape of `self`, with the length of `axis` replaced with `window_size`. + /// + /// **Panics** if `axis` is out-of-bounds or if `window_size` is zero. + /// + /// ``` + /// use ndarray::{Array3, Axis, s}; + /// + /// let arr = Array3::from_shape_fn([4, 5, 2], |(i, j, k)| i * 100 + j * 10 + k); + /// let correct = vec![ + /// arr.slice(s![.., 0..3, ..]), + /// arr.slice(s![.., 1..4, ..]), + /// arr.slice(s![.., 2..5, ..]), + /// ]; + /// for (window, correct) in arr.axis_windows(Axis(1), 3).into_iter().zip(&correct) { + /// assert_eq!(window, correct); + /// assert_eq!(window.shape(), &[4, 3, 2]); + /// } + /// ``` + pub fn axis_windows(&self, axis: Axis, window_size: usize) -> Windows<'_, A, D> + where + S: Data, + { + let axis_index = axis.index(); + + ndassert!( + axis_index < self.ndim(), + concat!( + "Window axis {} does not match array dimension {} ", + "(with array of shape {:?})" + ), + axis_index, + self.ndim(), + self.shape() + ); + + let mut size = self.raw_dim(); + size[axis_index] = window_size; + + Windows::new(self.view(), size) } // Return (length, stride) for diagonal @@ -1384,23 +1619,7 @@ where /// Return `false` otherwise, i.e. the array is possibly not /// contiguous in memory, it has custom strides, etc. pub fn is_standard_layout(&self) -> bool { - fn is_standard_layout(dim: &D, strides: &D) -> bool { - if let Some(1) = D::NDIM { - return strides[0] == 1 || dim[0] <= 1; - } - if dim.slice().iter().any(|&d| d == 0) { - return true; - } - let defaults = dim.default_strides(); - // check all dimensions -- a dimension of length 1 can have unequal strides - for (&dim, &s, &ds) in izip!(dim.slice(), strides.slice(), defaults.slice()) { - if dim != 1 && s != ds { - return false; - } - } - true - } - is_standard_layout(&self.dim, &self.strides) + dimension::is_layout_c(&self.dim, &self.strides) } /// Return true if the array is known to be contiguous. @@ -1465,6 +1684,15 @@ where } /// Return a mutable pointer to the first element in the array. + /// + /// This method attempts to unshare the data. If `S: DataMut`, then the + /// data is guaranteed to be uniquely held on return. + /// + /// # Warning + /// + /// When accessing elements through this pointer, make sure to use strides + /// obtained *after* calling this method, since the process of unsharing + /// the data may change the strides. #[inline(always)] pub fn as_mut_ptr(&mut self) -> *mut A where @@ -1481,6 +1709,9 @@ where } /// Return a raw mutable view of the array. + /// + /// This method attempts to unshare the data. If `S: DataMut`, then the + /// data is guaranteed to be uniquely held on return. #[inline] pub fn raw_view_mut(&mut self) -> RawArrayViewMut where @@ -1541,10 +1772,10 @@ where S: Data, { if self.is_contiguous() { - let offset = offset_from_ptr_to_memory(&self.dim, &self.strides); + let offset = offset_from_low_addr_ptr_to_logical_ptr(&self.dim, &self.strides); unsafe { Some(slice::from_raw_parts( - self.ptr.offset(offset).as_ptr(), + self.ptr.sub(offset).as_ptr(), self.len(), )) } @@ -1555,6 +1786,10 @@ where /// Return the array’s data as a slice if it is contiguous, /// return `None` otherwise. + /// + /// In the contiguous case, in order to return a unique reference, this + /// method unshares the data if necessary, but it preserves the existing + /// strides. pub fn as_slice_memory_order_mut(&mut self) -> Option<&mut [A]> where S: DataMut, @@ -1570,10 +1805,10 @@ where { if self.is_contiguous() { self.ensure_unique(); - let offset = offset_from_ptr_to_memory(&self.dim, &self.strides); + let offset = offset_from_low_addr_ptr_to_logical_ptr(&self.dim, &self.strides); unsafe { Ok(slice::from_raw_parts_mut( - self.ptr.offset(offset).as_ptr(), + self.ptr.sub(offset).as_ptr(), self.len(), )) } @@ -1582,10 +1817,201 @@ where } } + /// Transform the array into `new_shape`; any shape with the same number of elements is + /// accepted. + /// + /// `order` specifies the *logical* order in which the array is to be read and reshaped. + /// The array is returned as a `CowArray`; a view if possible, otherwise an owned array. + /// + /// For example, when starting from the one-dimensional sequence 1 2 3 4 5 6, it would be + /// understood as a 2 x 3 array in row major ("C") order this way: + /// + /// ```text + /// 1 2 3 + /// 4 5 6 + /// ``` + /// + /// and as 2 x 3 in column major ("F") order this way: + /// + /// ```text + /// 1 3 5 + /// 2 4 6 + /// ``` + /// + /// This example should show that any time we "reflow" the elements in the array to a different + /// number of rows and columns (or more axes if applicable), it is important to pick an index + /// ordering, and that's the reason for the function parameter for `order`. + /// + /// The `new_shape` parameter should be a dimension and an optional order like these examples: + /// + /// ```text + /// (3, 4) // Shape 3 x 4 with default order (RowMajor) + /// ((3, 4), Order::RowMajor)) // use specific order + /// ((3, 4), Order::ColumnMajor)) // use specific order + /// ((3, 4), Order::C)) // use shorthand for order - shorthands C and F + /// ``` + /// + /// **Errors** if the new shape doesn't have the same number of elements as the array's current + /// shape. + /// + /// # Example + /// + /// ``` + /// use ndarray::array; + /// use ndarray::Order; + /// + /// assert!( + /// array![1., 2., 3., 4., 5., 6.].to_shape(((2, 3), Order::RowMajor)).unwrap() + /// == array![[1., 2., 3.], + /// [4., 5., 6.]] + /// ); + /// + /// assert!( + /// array![1., 2., 3., 4., 5., 6.].to_shape(((2, 3), Order::ColumnMajor)).unwrap() + /// == array![[1., 3., 5.], + /// [2., 4., 6.]] + /// ); + /// ``` + pub fn to_shape(&self, new_shape: E) -> Result, ShapeError> + where + E: ShapeArg, + A: Clone, + S: Data, + { + let (shape, order) = new_shape.into_shape_and_order(); + self.to_shape_order(shape, order.unwrap_or(Order::RowMajor)) + } + + fn to_shape_order(&self, shape: E, order: Order) + -> Result, ShapeError> + where + E: Dimension, + A: Clone, + S: Data, + { + let len = self.dim.size(); + if size_of_shape_checked(&shape) != Ok(len) { + return Err(error::incompatible_shapes(&self.dim, &shape)); + } + + // Create a view if the length is 0, safe because the array and new shape is empty. + if len == 0 { + unsafe { + return Ok(CowArray::from(ArrayView::from_shape_ptr(shape, self.as_ptr()))); + } + } + + // Try to reshape the array as a view into the existing data + match reshape_dim(&self.dim, &self.strides, &shape, order) { + Ok(to_strides) => unsafe { + return Ok(CowArray::from(ArrayView::new(self.ptr, shape, to_strides))); + } + Err(err) if err.kind() == ErrorKind::IncompatibleShape => { + return Err(error::incompatible_shapes(&self.dim, &shape)); + } + _otherwise => { } + } + + // otherwise create a new array and copy the elements + unsafe { + let (shape, view) = match order { + Order::RowMajor => (shape.set_f(false), self.view()), + Order::ColumnMajor => (shape.set_f(true), self.t()), + }; + Ok(CowArray::from(Array::from_shape_trusted_iter_unchecked( + shape, view.into_iter(), A::clone))) + } + } + + /// Transform the array into `shape`; any shape with the same number of + /// elements is accepted, but the source array must be contiguous. + /// + /// If an index ordering is not specified, the default is `RowMajor`. + /// The operation will only succeed if the array's memory layout is compatible with + /// the index ordering, so that the array elements can be rearranged in place. + /// + /// If required use `.to_shape()` or `.into_shape_clone` instead for more flexible reshaping of + /// arrays, which allows copying elements if required. + /// + /// **Errors** if the shapes don't have the same number of elements.
+ /// **Errors** if order RowMajor is given but input is not c-contiguous. + /// **Errors** if order ColumnMajor is given but input is not f-contiguous. + /// + /// If shape is not given: use memory layout of incoming array. Row major arrays are + /// reshaped using row major index ordering, column major arrays with column major index + /// ordering. + /// + /// The `new_shape` parameter should be a dimension and an optional order like these examples: + /// + /// ```text + /// (3, 4) // Shape 3 x 4 with default order (RowMajor) + /// ((3, 4), Order::RowMajor)) // use specific order + /// ((3, 4), Order::ColumnMajor)) // use specific order + /// ((3, 4), Order::C)) // use shorthand for order - shorthands C and F + /// ``` + /// + /// # Example + /// + /// ``` + /// use ndarray::{aview1, aview2}; + /// use ndarray::Order; + /// + /// assert!( + /// aview1(&[1., 2., 3., 4.]).into_shape_with_order((2, 2)).unwrap() + /// == aview2(&[[1., 2.], + /// [3., 4.]]) + /// ); + /// + /// assert!( + /// aview1(&[1., 2., 3., 4.]).into_shape_with_order(((2, 2), Order::ColumnMajor)).unwrap() + /// == aview2(&[[1., 3.], + /// [2., 4.]]) + /// ); + /// ``` + pub fn into_shape_with_order(self, shape: E) -> Result, ShapeError> + where + E: ShapeArg, + { + let (shape, order) = shape.into_shape_and_order(); + self.into_shape_with_order_impl(shape, order.unwrap_or(Order::RowMajor)) + } + + fn into_shape_with_order_impl(self, shape: E, order: Order) + -> Result, ShapeError> + where + E: Dimension, + { + let shape = shape.into_dimension(); + if size_of_shape_checked(&shape) != Ok(self.dim.size()) { + return Err(error::incompatible_shapes(&self.dim, &shape)); + } + + // Check if contiguous, then we can change shape + unsafe { + // safe because arrays are contiguous and len is unchanged + match order { + Order::RowMajor if self.is_standard_layout() => { + Ok(self.with_strides_dim(shape.default_strides(), shape)) + } + Order::ColumnMajor if self.raw_view().reversed_axes().is_standard_layout() => { + Ok(self.with_strides_dim(shape.fortran_strides(), shape)) + } + _otherwise => Err(error::from_kind(error::ErrorKind::IncompatibleLayout)) + } + } + } + /// Transform the array into `shape`; any shape with the same number of /// elements is accepted, but the source array or view must be in standard /// or column-major (Fortran) layout. /// + /// **Note** that `.into_shape()` "moves" elements differently depending on if the input array + /// is C-contig or F-contig, it follows the index order that corresponds to the memory order. + /// Prefer to use `.to_shape()` or `.into_shape_with_order()`. + /// + /// Because of this, the method **is deprecated**. That reshapes depend on memory order is not + /// intuitive. + /// /// **Errors** if the shapes don't have the same number of elements.
/// **Errors** if the input array is not c- or f-contiguous. /// @@ -1598,6 +2024,7 @@ where /// [3., 4.]]) /// ); /// ``` + #[deprecated = "Use `.into_shape_with_order()` or `.to_shape()`"] pub fn into_shape(self, shape: E) -> Result, ShapeError> where E: IntoDimension, @@ -1619,7 +2046,75 @@ where } } - /// *Note: Reshape is for `ArcArray` only. Use `.into_shape()` for + /// Transform the array into `shape`; any shape with the same number of + /// elements is accepted. Array elements are reordered in place if + /// possible, otherwise they are copied to create a new array. + /// + /// If an index ordering is not specified, the default is `RowMajor`. + /// The operation will only succeed if the array's memory layout is compatible with + /// the index ordering, so that the array elements can be rearranged in place. + /// + /// # `.to_shape` vs `.into_shape_clone` + /// + /// - `to_shape` supports views and outputting views + /// - `to_shape` borrows the original array, `into_shape_clone` consumes the original + /// - `into_shape_clone` preserves array type (Array vs ArcArray), but does not support views. + /// + /// **Errors** if the shapes don't have the same number of elements.
+ pub fn into_shape_clone(self, shape: E) -> Result, ShapeError> + where + S: DataOwned, + A: Clone, + E: ShapeArg, + { + let (shape, order) = shape.into_shape_and_order(); + let order = order.unwrap_or(Order::RowMajor); + self.into_shape_clone_order(shape, order) + } + + fn into_shape_clone_order(self, shape: E, order: Order) + -> Result, ShapeError> + where + S: DataOwned, + A: Clone, + E: Dimension, + { + let len = self.dim.size(); + if size_of_shape_checked(&shape) != Ok(len) { + return Err(error::incompatible_shapes(&self.dim, &shape)); + } + + // Safe because the array and new shape is empty. + if len == 0 { + unsafe { + return Ok(self.with_strides_dim(shape.default_strides(), shape)); + } + } + + // Try to reshape the array's current data + match reshape_dim(&self.dim, &self.strides, &shape, order) { + Ok(to_strides) => unsafe { + return Ok(self.with_strides_dim(to_strides, shape)); + } + Err(err) if err.kind() == ErrorKind::IncompatibleShape => { + return Err(error::incompatible_shapes(&self.dim, &shape)); + } + _otherwise => { } + } + + // otherwise, clone and allocate a new array + unsafe { + let (shape, view) = match order { + Order::RowMajor => (shape.set_f(false), self.view()), + Order::ColumnMajor => (shape.set_f(true), self.t()), + }; + + Ok(ArrayBase::from_shape_trusted_iter_unchecked( + shape, view.into_iter(), A::clone)) + } + } + + /// *Note: Reshape is for `ArcArray` only. Use `.into_shape_with_order()` for /// other arrays and array views.* /// /// Transform the array into `shape`; any shape with the same number of @@ -1630,6 +2125,9 @@ where /// /// **Panics** if shapes are incompatible. /// + /// *This method is obsolete, because it is inflexible in how logical order + /// of the array is handled. See [`.to_shape()`].* + /// /// ``` /// use ndarray::{rcarr1, rcarr2}; /// @@ -1640,6 +2138,7 @@ where /// ); /// ``` #[track_caller] + #[deprecated(note="Obsolete, use `to_shape` or `into_shape_with_order` instead.", since="0.15.2")] pub fn reshape(&self, shape: E) -> ArrayBase where S: DataShared + DataOwned, @@ -1711,7 +2210,7 @@ where let strides = unlimited_transmute::(self.strides); return Ok(ArrayBase::from_data_ptr(self.data, self.ptr) .with_strides_dim(strides, dim)); - } else if D::NDIM == None || D2::NDIM == None { // one is dynamic dim + } else if D::NDIM.is_none() || D2::NDIM.is_none() { // one is dynamic dim // safe because dim, strides are equivalent under a different type if let Some(dim) = D2::from_dimension(&self.dim) { if let Some(strides) = D2::from_dimension(&self.strides) { @@ -2065,17 +2564,7 @@ where } pub(crate) fn pointer_is_inbounds(&self) -> bool { - match self.data._data_slice() { - None => { - // special case for non-owned views - true - } - Some(slc) => { - let ptr = slc.as_ptr() as *mut A; - let end = unsafe { ptr.add(slc.len()) }; - self.ptr.as_ptr() >= ptr && self.ptr.as_ptr() <= end - } - } + self.data._is_pointer_inbounds(self.as_ptr()) } /// Perform an elementwise assigment to `self` from `rhs`. @@ -2090,7 +2579,7 @@ where A: Clone, S2: Data, { - self.zip_mut_with(rhs, |x, y| *x = y.clone()); + self.zip_mut_with(rhs, |x, y| x.clone_from(y)); } /// Perform an elementwise assigment of values cloned from `self` into array or producer `to`. @@ -2117,7 +2606,7 @@ where S: DataMut, A: Clone, { - self.map_inplace(move |elt| *elt = x.clone()); + self.map_inplace(move |elt| elt.clone_from(&x)); } pub(crate) fn zip_mut_with_same_shape(&mut self, rhs: &ArrayBase, mut f: F) @@ -2133,7 +2622,7 @@ where if let Some(self_s) = self.as_slice_memory_order_mut() { if let Some(rhs_s) = rhs.as_slice_memory_order() { for (s, r) in self_s.iter_mut().zip(rhs_s) { - f(s, &r); + f(s, r); } return; } @@ -2241,17 +2730,14 @@ where A: 'a, S: Data, { - if let Some(slc) = self.as_slice_memory_order() { - let v = crate::iterators::to_vec_mapped(slc.iter(), f); - unsafe { - ArrayBase::from_shape_vec_unchecked( + unsafe { + if let Some(slc) = self.as_slice_memory_order() { + ArrayBase::from_shape_trusted_iter_unchecked( self.dim.clone().strides(self.strides.clone()), - v, - ) + slc.iter(), f) + } else { + ArrayBase::from_shape_trusted_iter_unchecked(self.dim.clone(), self.iter(), f) } - } else { - let v = crate::iterators::to_vec_mapped(self.iter(), f); - unsafe { ArrayBase::from_shape_vec_unchecked(self.dim.clone(), v) } } } @@ -2271,11 +2757,10 @@ where if self.is_contiguous() { let strides = self.strides.clone(); let slc = self.as_slice_memory_order_mut().unwrap(); - let v = crate::iterators::to_vec_mapped(slc.iter_mut(), f); - unsafe { ArrayBase::from_shape_vec_unchecked(dim.strides(strides), v) } + unsafe { ArrayBase::from_shape_trusted_iter_unchecked(dim.strides(strides), + slc.iter_mut(), f) } } else { - let v = crate::iterators::to_vec_mapped(self.iter_mut(), f); - unsafe { ArrayBase::from_shape_vec_unchecked(dim, v) } + unsafe { ArrayBase::from_shape_trusted_iter_unchecked(dim, self.iter_mut(), f) } } } @@ -2319,6 +2804,48 @@ where self } + /// Consume the array, call `f` by **v**alue on each element, and return an + /// owned array with the new values. Works for **any** `F: FnMut(A)->B`. + /// + /// If `A` and `B` are the same type then the map is performed by delegating + /// to [`mapv_into`] and then converting into an owned array. This avoids + /// unnecessary memory allocations in [`mapv`]. + /// + /// If `A` and `B` are different types then a new array is allocated and the + /// map is performed as in [`mapv`]. + /// + /// Elements are visited in arbitrary order. + /// + /// [`mapv_into`]: ArrayBase::mapv_into + /// [`mapv`]: ArrayBase::mapv + pub fn mapv_into_any(self, mut f: F) -> Array + where + S: DataMut, + F: FnMut(A) -> B, + A: Clone + 'static, + B: 'static, + { + if core::any::TypeId::of::
() == core::any::TypeId::of::() { + // A and B are the same type. + // Wrap f in a closure of type FnMut(A) -> A . + let f = |a| { + let b = f(a); + // Safe because A and B are the same type. + unsafe { unlimited_transmute::(b) } + }; + // Delegate to mapv_into() using the wrapped closure. + // Convert output to a uniquely owned array of type Array. + let output = self.mapv_into(f).into_owned(); + // Change the return type from Array to Array. + // Again, safe because A and B are the same type. + unsafe { unlimited_transmute::, Array>(output) } + } else { + // A and B are not the same type. + // Fallback to mapv(). + self.mapv(f) + } + } + /// Modify the array in place by calling `f` by mutable reference on each element. /// /// Elements are visited in arbitrary order. @@ -2344,10 +2871,10 @@ where /// Elements are visited in arbitrary order. /// /// ``` + /// # #[cfg(feature = "approx")] { /// use approx::assert_abs_diff_eq; /// use ndarray::arr2; /// - /// # #[cfg(feature = "approx")] { /// let mut a = arr2(&[[ 0., 1.], /// [-1., 2.]]); /// a.mapv_inplace(f32::exp); @@ -2433,17 +2960,11 @@ where A: 'a, S: Data, { - let view_len = self.len_of(axis); - let view_stride = self.strides.axis(axis); - if view_len == 0 { + if self.len_of(axis) == 0 { let new_dim = self.dim.remove_axis(axis); Array::from_shape_simple_fn(new_dim, move || mapping(ArrayView::from(&[]))) } else { - // use the 0th subview as a map to each 1d array view extended from - // the 0th element. - self.index_axis(axis, 0).map(|first_elt| unsafe { - mapping(ArrayView::new_(first_elt, Ix1(view_len), Ix1(view_stride))) - }) + Zip::from(self.lanes(axis)).map_collect(mapping) } } @@ -2465,24 +2986,38 @@ where A: 'a, S: DataMut, { - let view_len = self.len_of(axis); - let view_stride = self.strides.axis(axis); - if view_len == 0 { + if self.len_of(axis) == 0 { let new_dim = self.dim.remove_axis(axis); Array::from_shape_simple_fn(new_dim, move || mapping(ArrayViewMut::from(&mut []))) } else { - // use the 0th subview as a map to each 1d array view extended from - // the 0th element. - self.index_axis_mut(axis, 0).map_mut(|first_elt| unsafe { - mapping(ArrayViewMut::new_( - first_elt, - Ix1(view_len), - Ix1(view_stride), - )) - }) + Zip::from(self.lanes_mut(axis)).map_collect(mapping) } } + /// Remove the `index`th elements along `axis` and shift down elements from higher indexes. + /// + /// Note that this "removes" the elements by swapping them around to the end of the axis and + /// shortening the length of the axis; the elements are not deinitialized or dropped by this, + /// just moved out of view (this only matters for elements with ownership semantics). It's + /// similar to slicing an owned array in place. + /// + /// Decreases the length of `axis` by one. + /// + /// ***Panics*** if `axis` is out of bounds
+ /// ***Panics*** if not `index < self.len_of(axis)`. + pub fn remove_index(&mut self, axis: Axis, index: usize) + where + S: DataOwned + DataMut, + { + assert!(index < self.len_of(axis), "index {} must be less than length of Axis({})", + index, axis.index()); + let (_, mut tail) = self.view_mut().split_at(axis, index); + // shift elements to the front + Zip::from(tail.lanes_mut(axis)).for_each(|mut lane| lane.rotate1_front()); + // then slice the axis in place to cut out the removed final element + self.slice_axis_inplace(axis, Slice::new(0, Some(-1), 1)); + } + /// Iterates over pairs of consecutive elements along the axis. /// /// The first argument to the closure is an element, and the second diff --git a/src/impl_ops.rs b/src/impl_ops.rs index 0b62017de..8d02364d1 100644 --- a/src/impl_ops.rs +++ b/src/impl_ops.rs @@ -292,7 +292,6 @@ mod arithmetic_ops { use super::*; use crate::imp_prelude::*; - use num_complex::Complex; use std::ops::*; fn clone_opf(f: impl Fn(A, B) -> C) -> impl FnMut(&A, &B) -> C { @@ -340,6 +339,8 @@ mod arithmetic_ops { all_scalar_ops!(u32); all_scalar_ops!(i64); all_scalar_ops!(u64); + all_scalar_ops!(isize); + all_scalar_ops!(usize); all_scalar_ops!(i128); all_scalar_ops!(u128); diff --git a/src/impl_owned_array.rs b/src/impl_owned_array.rs index 41eff2b11..3e9001132 100644 --- a/src/impl_owned_array.rs +++ b/src/impl_owned_array.rs @@ -1,12 +1,23 @@ +#[cfg(not(feature = "std"))] use alloc::vec::Vec; +use std::mem; +use std::mem::MaybeUninit; + +use rawpointer::PointerExt; + use crate::imp_prelude::*; +use crate::dimension; +use crate::error::{ErrorKind, ShapeError}; +use crate::iterators::Baseiter; +use crate::low_level_util::AbortIfPanic; +use crate::OwnedRepr; +use crate::Zip; + /// Methods specific to `Array0`. /// /// ***See also all methods for [`ArrayBase`]*** -/// -/// [`ArrayBase`]: struct.ArrayBase.html impl
Array { /// Returns the single element in the array without cloning it. /// @@ -22,7 +33,7 @@ impl Array { /// assert_eq!(scalar, Foo); /// ``` pub fn into_scalar(self) -> A { - let size = ::std::mem::size_of::(); + let size = mem::size_of::(); if size == 0 { // Any index in the `Vec` is fine since all elements are identical. self.data.into_vec().remove(0) @@ -44,8 +55,6 @@ impl Array { /// Methods specific to `Array`. /// /// ***See also all methods for [`ArrayBase`]*** -/// -/// [`ArrayBase`]: struct.ArrayBase.html impl Array where D: Dimension, @@ -59,3 +68,753 @@ where self.data.into_vec() } } + +/// Methods specific to `Array2`. +/// +/// ***See also all methods for [`ArrayBase`]*** +impl Array { + /// Append a row to an array + /// + /// The elements from `row` are cloned and added as a new row in the array. + /// + /// ***Errors*** with a shape error if the length of the row does not match the length of the + /// rows in the array. + /// + /// The memory layout of the `self` array matters for ensuring that the append is efficient. + /// Appending automatically changes memory layout of the array so that it is appended to + /// along the "growing axis". However, if the memory layout needs adjusting, the array must + /// reallocate and move memory. + /// + /// The operation leaves the existing data in place and is most efficent if one of these is + /// true: + /// + /// - The axis being appended to is the longest stride axis, i.e the array is in row major + /// ("C") layout. + /// - The array has 0 or 1 rows (It is converted to row major) + /// + /// Ensure appending is efficient by, for example, appending to an empty array and then always + /// pushing/appending along the same axis. For pushing rows, ndarray's default layout (C order) + /// is efficient. + /// + /// When repeatedly appending to a single axis, the amortized average complexity of each + /// append is O(m), where *m* is the length of the row. + /// + /// ```rust + /// use ndarray::{Array, ArrayView, array}; + /// + /// // create an empty array and append + /// let mut a = Array::zeros((0, 4)); + /// a.push_row(ArrayView::from(&[ 1., 2., 3., 4.])).unwrap(); + /// a.push_row(ArrayView::from(&[-1., -2., -3., -4.])).unwrap(); + /// + /// assert_eq!( + /// a, + /// array![[ 1., 2., 3., 4.], + /// [-1., -2., -3., -4.]]); + /// ``` + pub fn push_row(&mut self, row: ArrayView) -> Result<(), ShapeError> + where + A: Clone, + { + self.append(Axis(0), row.insert_axis(Axis(0))) + } + + /// Append a column to an array + /// + /// The elements from `column` are cloned and added as a new column in the array. + /// + /// ***Errors*** with a shape error if the length of the column does not match the length of + /// the columns in the array. + /// + /// The memory layout of the `self` array matters for ensuring that the append is efficient. + /// Appending automatically changes memory layout of the array so that it is appended to + /// along the "growing axis". However, if the memory layout needs adjusting, the array must + /// reallocate and move memory. + /// + /// The operation leaves the existing data in place and is most efficent if one of these is + /// true: + /// + /// - The axis being appended to is the longest stride axis, i.e the array is in column major + /// ("F") layout. + /// - The array has 0 or 1 columns (It is converted to column major) + /// + /// Ensure appending is efficient by, for example, appending to an empty array and then always + /// pushing/appending along the same axis. For pushing columns, column major layout (F order) + /// is efficient. + /// + /// When repeatedly appending to a single axis, the amortized average complexity of each append + /// is O(m), where *m* is the length of the column. + /// + /// ```rust + /// use ndarray::{Array, ArrayView, array}; + /// + /// // create an empty array and append + /// let mut a = Array::zeros((2, 0)); + /// a.push_column(ArrayView::from(&[1., 2.])).unwrap(); + /// a.push_column(ArrayView::from(&[-1., -2.])).unwrap(); + /// + /// assert_eq!( + /// a, + /// array![[1., -1.], + /// [2., -2.]]); + /// ``` + pub fn push_column(&mut self, column: ArrayView) -> Result<(), ShapeError> + where + A: Clone, + { + self.append(Axis(1), column.insert_axis(Axis(1))) + } +} + +impl Array + where D: Dimension +{ + /// Move all elements from self into `new_array`, which must be of the same shape but + /// can have a different memory layout. The destination is overwritten completely. + /// + /// The destination should be a mut reference to an array or an `ArrayViewMut` with + /// `A` elements. + /// + /// ***Panics*** if the shapes don't agree. + /// + /// ## Example + /// + /// ``` + /// use ndarray::Array; + /// + /// // Usage example of move_into in safe code + /// let mut a = Array::default((10, 10)); + /// let b = Array::from_shape_fn((10, 10), |(i, j)| (i + j).to_string()); + /// b.move_into(&mut a); + /// ``` + pub fn move_into<'a, AM>(self, new_array: AM) + where + AM: Into>, + A: 'a, + { + // Remove generic parameter P and call the implementation + let new_array = new_array.into(); + if mem::needs_drop::() { + self.move_into_needs_drop(new_array); + } else { + // If `A` doesn't need drop, we can overwrite the destination. + // Safe because: move_into_uninit only writes initialized values + unsafe { + self.move_into_uninit(new_array.into_maybe_uninit()) + } + } + } + + fn move_into_needs_drop(mut self, new_array: ArrayViewMut) { + // Simple case where `A` has a destructor: just swap values between self and new_array. + // Afterwards, `self` drops full of initialized values and dropping works as usual. + // This avoids moving out of owned values in `self` while at the same time managing + // the dropping if the values being overwritten in `new_array`. + Zip::from(&mut self).and(new_array) + .for_each(|src, dst| mem::swap(src, dst)); + } + + /// Move all elements from self into `new_array`, which must be of the same shape but + /// can have a different memory layout. The destination is overwritten completely. + /// + /// The destination should be a mut reference to an array or an `ArrayViewMut` with + /// `MaybeUninit` elements (which are overwritten without dropping any existing value). + /// + /// Minor implementation note: Owned arrays like `self` may be sliced in place and own elements + /// that are not part of their active view; these are dropped at the end of this function, + /// after all elements in the "active view" are moved into `new_array`. If there is a panic in + /// drop of any such element, other elements may be leaked. + /// + /// ***Panics*** if the shapes don't agree. + /// + /// ## Example + /// + /// ``` + /// use ndarray::Array; + /// + /// let a = Array::from_iter(0..100).into_shape_with_order((10, 10)).unwrap(); + /// let mut b = Array::uninit((10, 10)); + /// a.move_into_uninit(&mut b); + /// unsafe { + /// // we can now promise we have fully initialized `b`. + /// let b = b.assume_init(); + /// } + /// ``` + pub fn move_into_uninit<'a, AM>(self, new_array: AM) + where + AM: Into, D>>, + A: 'a, + { + // Remove generic parameter AM and call the implementation + self.move_into_impl(new_array.into()) + } + + fn move_into_impl(mut self, new_array: ArrayViewMut, D>) { + unsafe { + // Safety: copy_to_nonoverlapping cannot panic + let guard = AbortIfPanic(&"move_into: moving out of owned value"); + // Move all reachable elements; we move elements out of `self`. + // and thus must not panic for the whole section until we call `self.data.set_len(0)`. + Zip::from(self.raw_view_mut()) + .and(new_array) + .for_each(|src, dst| { + src.copy_to_nonoverlapping(dst.as_mut_ptr(), 1); + }); + guard.defuse(); + // Drop all unreachable elements + self.drop_unreachable_elements(); + } + } + + /// This drops all "unreachable" elements in the data storage of self. + /// + /// That means those elements that are not visible in the slicing of the array. + /// *Reachable elements are assumed to already have been moved from.* + /// + /// # Safety + /// + /// This is a panic critical section since `self` is already moved-from. + fn drop_unreachable_elements(mut self) -> OwnedRepr { + let self_len = self.len(); + + // "deconstruct" self; the owned repr releases ownership of all elements and we + // and carry on with raw view methods + let data_len = self.data.len(); + + let has_unreachable_elements = self_len != data_len; + if !has_unreachable_elements || mem::size_of::() == 0 || !mem::needs_drop::() { + unsafe { + self.data.set_len(0); + } + self.data + } else { + self.drop_unreachable_elements_slow() + } + } + + #[inline(never)] + #[cold] + fn drop_unreachable_elements_slow(mut self) -> OwnedRepr { + // "deconstruct" self; the owned repr releases ownership of all elements and we + // carry on with raw view methods + let data_len = self.data.len(); + let data_ptr = self.data.as_nonnull_mut().as_ptr(); + + unsafe { + // Safety: self.data releases ownership of the elements. Any panics below this point + // will result in leaking elements instead of double drops. + let self_ = self.raw_view_mut(); + self.data.set_len(0); + + drop_unreachable_raw(self_, data_ptr, data_len); + } + + self.data + } + + /// Create an empty array with an all-zeros shape + /// + /// ***Panics*** if D is zero-dimensional, because it can't be empty + pub(crate) fn empty() -> Array { + assert_ne!(D::NDIM, Some(0)); + let ndim = D::NDIM.unwrap_or(1); + Array::from_shape_simple_fn(D::zeros(ndim), || unreachable!()) + } + + /// Create new_array with the right layout for appending to `growing_axis` + #[cold] + fn change_to_contig_append_layout(&mut self, growing_axis: Axis) { + let ndim = self.ndim(); + let mut dim = self.raw_dim(); + + // The array will be created with 0 (C) or ndim-1 (F) as the biggest stride + // axis. Rearrange the shape so that `growing_axis` is the biggest stride axis + // afterwards. + let mut new_array; + if growing_axis == Axis(ndim - 1) { + new_array = Self::uninit(dim.f()); + } else { + dim.slice_mut()[..=growing_axis.index()].rotate_right(1); + new_array = Self::uninit(dim); + new_array.dim.slice_mut()[..=growing_axis.index()].rotate_left(1); + new_array.strides.slice_mut()[..=growing_axis.index()].rotate_left(1); + } + + // self -> old_self. + // dummy array -> self. + // old_self elements are moved -> new_array. + let old_self = std::mem::replace(self, Self::empty()); + old_self.move_into_uninit(new_array.view_mut()); + + // new_array -> self. + unsafe { + *self = new_array.assume_init(); + } + } + + /// Append an array to the array along an axis. + /// + /// The elements of `array` are cloned and extend the axis `axis` in the present array; + /// `self` will grow in size by 1 along `axis`. + /// + /// Append to the array, where the array being pushed to the array has one dimension less than + /// the `self` array. This method is equivalent to [append](ArrayBase::append) in this way: + /// `self.append(axis, array.insert_axis(axis))`. + /// + /// ***Errors*** with a shape error if the shape of self does not match the array-to-append; + /// all axes *except* the axis along which it being appended matter for this check: + /// the shape of `self` with `axis` removed must be the same as the shape of `array`. + /// + /// The memory layout of the `self` array matters for ensuring that the append is efficient. + /// Appending automatically changes memory layout of the array so that it is appended to + /// along the "growing axis". However, if the memory layout needs adjusting, the array must + /// reallocate and move memory. + /// + /// The operation leaves the existing data in place and is most efficent if `axis` is a + /// "growing axis" for the array, i.e. one of these is true: + /// + /// - The axis is the longest stride axis, for example the 0th axis in a C-layout or the + /// *n-1*th axis in an F-layout array. + /// - The axis has length 0 or 1 (It is converted to the new growing axis) + /// + /// Ensure appending is efficient by for example starting from an empty array and/or always + /// appending to an array along the same axis. + /// + /// The amortized average complexity of the append, when appending along its growing axis, is + /// O(*m*) where *m* is the number of individual elements to append. + /// + /// The memory layout of the argument `array` does not matter to the same extent. + /// + /// ```rust + /// use ndarray::{Array, ArrayView, array, Axis}; + /// + /// // create an empty array and push rows to it + /// let mut a = Array::zeros((0, 4)); + /// let ones = ArrayView::from(&[1.; 4]); + /// let zeros = ArrayView::from(&[0.; 4]); + /// a.push(Axis(0), ones).unwrap(); + /// a.push(Axis(0), zeros).unwrap(); + /// a.push(Axis(0), ones).unwrap(); + /// + /// assert_eq!( + /// a, + /// array![[1., 1., 1., 1.], + /// [0., 0., 0., 0.], + /// [1., 1., 1., 1.]]); + /// ``` + pub fn push(&mut self, axis: Axis, array: ArrayView) + -> Result<(), ShapeError> + where + A: Clone, + D: RemoveAxis, + { + // same-dimensionality conversion + self.append(axis, array.insert_axis(axis).into_dimensionality::().unwrap()) + } + + + /// Append an array to the array along an axis. + /// + /// The elements of `array` are cloned and extend the axis `axis` in the present array; + /// `self` will grow in size by `array.len_of(axis)` along `axis`. + /// + /// ***Errors*** with a shape error if the shape of self does not match the array-to-append; + /// all axes *except* the axis along which it being appended matter for this check: + /// the shape of `self` with `axis` removed must be the same as the shape of `array` with + /// `axis` removed. + /// + /// The memory layout of the `self` array matters for ensuring that the append is efficient. + /// Appending automatically changes memory layout of the array so that it is appended to + /// along the "growing axis". However, if the memory layout needs adjusting, the array must + /// reallocate and move memory. + /// + /// The operation leaves the existing data in place and is most efficent if `axis` is a + /// "growing axis" for the array, i.e. one of these is true: + /// + /// - The axis is the longest stride axis, for example the 0th axis in a C-layout or the + /// *n-1*th axis in an F-layout array. + /// - The axis has length 0 or 1 (It is converted to the new growing axis) + /// + /// Ensure appending is efficient by for example starting from an empty array and/or always + /// appending to an array along the same axis. + /// + /// The amortized average complexity of the append, when appending along its growing axis, is + /// O(*m*) where *m* is the number of individual elements to append. + /// + /// The memory layout of the argument `array` does not matter to the same extent. + /// + /// ```rust + /// use ndarray::{Array, ArrayView, array, Axis}; + /// + /// // create an empty array and append two rows at a time + /// let mut a = Array::zeros((0, 4)); + /// let ones = ArrayView::from(&[1.; 8]).into_shape_with_order((2, 4)).unwrap(); + /// let zeros = ArrayView::from(&[0.; 8]).into_shape_with_order((2, 4)).unwrap(); + /// a.append(Axis(0), ones).unwrap(); + /// a.append(Axis(0), zeros).unwrap(); + /// a.append(Axis(0), ones).unwrap(); + /// + /// assert_eq!( + /// a, + /// array![[1., 1., 1., 1.], + /// [1., 1., 1., 1.], + /// [0., 0., 0., 0.], + /// [0., 0., 0., 0.], + /// [1., 1., 1., 1.], + /// [1., 1., 1., 1.]]); + /// ``` + pub fn append(&mut self, axis: Axis, mut array: ArrayView) + -> Result<(), ShapeError> + where + A: Clone, + D: RemoveAxis, + { + if self.ndim() == 0 { + return Err(ShapeError::from_kind(ErrorKind::IncompatibleShape)); + } + + let current_axis_len = self.len_of(axis); + let self_dim = self.raw_dim(); + let array_dim = array.raw_dim(); + let remaining_shape = self_dim.remove_axis(axis); + let array_rem_shape = array_dim.remove_axis(axis); + + if remaining_shape != array_rem_shape { + return Err(ShapeError::from_kind(ErrorKind::IncompatibleShape)); + } + + let len_to_append = array.len(); + + let mut res_dim = self_dim; + res_dim[axis.index()] += array_dim[axis.index()]; + let new_len = dimension::size_of_shape_checked(&res_dim)?; + + if len_to_append == 0 { + // There are no elements to append and shapes are compatible: + // either the dimension increment is zero, or there is an existing + // zero in another axis in self. + debug_assert_eq!(self.len(), new_len); + self.dim = res_dim; + return Ok(()); + } + + let self_is_empty = self.is_empty(); + let mut incompatible_layout = false; + + // array must be empty or have `axis` as the outermost (longest stride) axis + if !self_is_empty && current_axis_len > 1 { + // `axis` must be max stride axis or equal to its stride + let axis_stride = self.stride_of(axis); + if axis_stride < 0 { + incompatible_layout = true; + } else { + for ax in self.axes() { + if ax.axis == axis { + continue; + } + if ax.len > 1 && ax.stride.abs() > axis_stride { + incompatible_layout = true; + break; + } + } + } + } + + // array must be be "full" (contiguous and have no exterior holes) + if self.len() != self.data.len() { + incompatible_layout = true; + } + + if incompatible_layout { + self.change_to_contig_append_layout(axis); + // safety-check parameters after remodeling + debug_assert_eq!(self_is_empty, self.is_empty()); + debug_assert_eq!(current_axis_len, self.len_of(axis)); + } + + let strides = if self_is_empty { + // recompute strides - if the array was previously empty, it could have zeros in + // strides. + // The new order is based on c/f-contig but must have `axis` as outermost axis. + if axis == Axis(self.ndim() - 1) { + // prefer f-contig when appending to the last axis + // Axis n - 1 is outermost axis + res_dim.fortran_strides() + } else { + // standard axis order except for the growing axis; + // anticipates that it's likely that `array` has standard order apart from the + // growing axis. + res_dim.slice_mut()[..=axis.index()].rotate_right(1); + let mut strides = res_dim.default_strides(); + res_dim.slice_mut()[..=axis.index()].rotate_left(1); + strides.slice_mut()[..=axis.index()].rotate_left(1); + strides + } + } else if current_axis_len == 1 { + // This is the outermost/longest stride axis; so we find the max across the other axes + let new_stride = self.axes().fold(1, |acc, ax| { + if ax.axis == axis || ax.len <= 1 { + acc + } else { + let this_ax = ax.len as isize * ax.stride.abs(); + if this_ax > acc { this_ax } else { acc } + } + }); + let mut strides = self.strides.clone(); + strides[axis.index()] = new_stride as usize; + strides + } else { + self.strides.clone() + }; + + unsafe { + // grow backing storage and update head ptr + let data_to_array_offset = if std::mem::size_of::() != 0 { + self.as_ptr().offset_from(self.data.as_ptr()) + } else { + 0 + }; + debug_assert!(data_to_array_offset >= 0); + self.ptr = self.data.reserve(len_to_append).offset(data_to_array_offset); + + // clone elements from view to the array now + // + // To be robust for panics and drop the right elements, we want + // to fill the tail in memory order, so that we can drop the right elements on panic. + // + // We have: Zip::from(tail_view).and(array) + // Transform tail_view into standard order by inverting and moving its axes. + // Keep the Zip traversal unchanged by applying the same axis transformations to + // `array`. This ensures the Zip traverses the underlying memory in order. + // + // XXX It would be possible to skip this transformation if the element + // doesn't have drop. However, in the interest of code coverage, all elements + // use this code initially. + + // Invert axes in tail_view by inverting strides + let mut tail_strides = strides.clone(); + if tail_strides.ndim() > 1 { + for i in 0..tail_strides.ndim() { + let s = tail_strides[i] as isize; + if s < 0 { + tail_strides.set_axis(Axis(i), -s as usize); + array.invert_axis(Axis(i)); + } + } + } + + // With > 0 strides, the current end of data is the correct base pointer for tail_view + let tail_ptr = self.data.as_end_nonnull(); + let mut tail_view = RawArrayViewMut::new(tail_ptr, array_dim, tail_strides); + + if tail_view.ndim() > 1 { + sort_axes_in_default_order_tandem(&mut tail_view, &mut array); + debug_assert!(tail_view.is_standard_layout(), + "not std layout dim: {:?}, strides: {:?}", + tail_view.shape(), tail_view.strides()); + } + + // Keep track of currently filled length of `self.data` and update it + // on scope exit (panic or loop finish). This "indirect" way to + // write the length is used to help the compiler, the len store to self.data may + // otherwise be mistaken to alias with other stores in the loop. + struct SetLenOnDrop<'a, A: 'a> { + len: usize, + data: &'a mut OwnedRepr, + } + + impl Drop for SetLenOnDrop<'_, A> { + fn drop(&mut self) { + unsafe { + self.data.set_len(self.len); + } + } + } + + let mut data_length_guard = SetLenOnDrop { + len: self.data.len(), + data: &mut self.data, + }; + + + // Safety: tail_view is constructed to have the same shape as array + Zip::from(tail_view) + .and_unchecked(array) + .debug_assert_c_order() + .for_each(|to, from| { + to.write(from.clone()); + data_length_guard.len += 1; + }); + drop(data_length_guard); + + // update array dimension + self.strides = strides; + self.dim = res_dim; + } + // multiple assertions after pointer & dimension update + debug_assert_eq!(self.data.len(), self.len()); + debug_assert_eq!(self.len(), new_len); + debug_assert!(self.pointer_is_inbounds()); + + Ok(()) + } +} + +/// This drops all "unreachable" elements in `self_` given the data pointer and data length. +/// +/// # Safety +/// +/// This is an internal function for use by move_into and IntoIter only, safety invariants may need +/// to be upheld across the calls from those implementations. +pub(crate) unsafe fn drop_unreachable_raw(mut self_: RawArrayViewMut, data_ptr: *mut A, data_len: usize) +where + D: Dimension, +{ + let self_len = self_.len(); + + for i in 0..self_.ndim() { + if self_.stride_of(Axis(i)) < 0 { + self_.invert_axis(Axis(i)); + } + } + sort_axes_in_default_order(&mut self_); + // with uninverted axes this is now the element with lowest address + let array_memory_head_ptr = self_.ptr.as_ptr(); + let data_end_ptr = data_ptr.add(data_len); + debug_assert!(data_ptr <= array_memory_head_ptr); + debug_assert!(array_memory_head_ptr <= data_end_ptr); + + // The idea is simply this: the iterator will yield the elements of self_ in + // increasing address order. + // + // The pointers produced by the iterator are those that we *do not* touch. + // The pointers *not mentioned* by the iterator are those we have to drop. + // + // We have to drop elements in the range from `data_ptr` until (not including) + // `data_end_ptr`, except those that are produced by `iter`. + + // As an optimization, the innermost axis is removed if it has stride 1, because + // we then have a long stretch of contiguous elements we can skip as one. + let inner_lane_len; + if self_.ndim() > 1 && self_.strides.last_elem() == 1 { + self_.dim.slice_mut().rotate_right(1); + self_.strides.slice_mut().rotate_right(1); + inner_lane_len = self_.dim[0]; + self_.dim[0] = 1; + self_.strides[0] = 1; + } else { + inner_lane_len = 1; + } + + // iter is a raw pointer iterator traversing the array in memory order now with the + // sorted axes. + let mut iter = Baseiter::new(self_.ptr.as_ptr(), self_.dim, self_.strides); + let mut dropped_elements = 0; + + let mut last_ptr = data_ptr; + + while let Some(elem_ptr) = iter.next() { + // The interval from last_ptr up until (not including) elem_ptr + // should now be dropped. This interval may be empty, then we just skip this loop. + while last_ptr != elem_ptr { + debug_assert!(last_ptr < data_end_ptr); + std::ptr::drop_in_place(last_ptr); + last_ptr = last_ptr.add(1); + dropped_elements += 1; + } + // Next interval will continue one past the current lane + last_ptr = elem_ptr.add(inner_lane_len); + } + + while last_ptr < data_end_ptr { + std::ptr::drop_in_place(last_ptr); + last_ptr = last_ptr.add(1); + dropped_elements += 1; + } + + assert_eq!(data_len, dropped_elements + self_len, + "Internal error: inconsistency in move_into"); +} + +/// Sort axes to standard order, i.e Axis(0) has biggest stride and Axis(n - 1) least stride +/// +/// The axes should have stride >= 0 before calling this method. +fn sort_axes_in_default_order(a: &mut ArrayBase) +where + S: RawData, + D: Dimension, +{ + if a.ndim() <= 1 { + return; + } + sort_axes1_impl(&mut a.dim, &mut a.strides); +} + +fn sort_axes1_impl(adim: &mut D, astrides: &mut D) +where + D: Dimension, +{ + debug_assert!(adim.ndim() > 1); + debug_assert_eq!(adim.ndim(), astrides.ndim()); + // bubble sort axes + let mut changed = true; + while changed { + changed = false; + for i in 0..adim.ndim() - 1 { + let axis_i = i; + let next_axis = i + 1; + + // make sure higher stride axes sort before. + debug_assert!(astrides.slice()[axis_i] as isize >= 0); + if (astrides.slice()[axis_i] as isize) < astrides.slice()[next_axis] as isize { + changed = true; + adim.slice_mut().swap(axis_i, next_axis); + astrides.slice_mut().swap(axis_i, next_axis); + } + } + } +} + + +/// Sort axes to standard order, i.e Axis(0) has biggest stride and Axis(n - 1) least stride +/// +/// Axes in a and b are sorted by the strides of `a`, and `a`'s axes should have stride >= 0 before +/// calling this method. +fn sort_axes_in_default_order_tandem(a: &mut ArrayBase, b: &mut ArrayBase) +where + S: RawData, + S2: RawData, + D: Dimension, +{ + if a.ndim() <= 1 { + return; + } + sort_axes2_impl(&mut a.dim, &mut a.strides, &mut b.dim, &mut b.strides); +} + +fn sort_axes2_impl(adim: &mut D, astrides: &mut D, bdim: &mut D, bstrides: &mut D) +where + D: Dimension, +{ + debug_assert!(adim.ndim() > 1); + debug_assert_eq!(adim.ndim(), bdim.ndim()); + // bubble sort axes + let mut changed = true; + while changed { + changed = false; + for i in 0..adim.ndim() - 1 { + let axis_i = i; + let next_axis = i + 1; + + // make sure higher stride axes sort before. + debug_assert!(astrides.slice()[axis_i] as isize >= 0); + if (astrides.slice()[axis_i] as isize) < astrides.slice()[next_axis] as isize { + changed = true; + adim.slice_mut().swap(axis_i, next_axis); + astrides.slice_mut().swap(axis_i, next_axis); + bdim.slice_mut().swap(axis_i, next_axis); + bstrides.slice_mut().swap(axis_i, next_axis); + } + } + } +} diff --git a/src/impl_raw_views.rs b/src/impl_raw_views.rs index cbb16c103..9f026318c 100644 --- a/src/impl_raw_views.rs +++ b/src/impl_raw_views.rs @@ -1,3 +1,4 @@ +use num_complex::Complex; use std::mem; use std::ptr::NonNull; @@ -75,7 +76,7 @@ where assert!(!ptr.is_null(), "The pointer must be non-null."); if let Strides::Custom(strides) = &shape.strides { dimension::strides_non_negative(strides).unwrap(); - dimension::max_abs_offset_check_overflow::(&dim, &strides).unwrap(); + dimension::max_abs_offset_check_overflow::(&dim, strides).unwrap(); } else { dimension::size_of_shape_checked(&dim).unwrap(); } @@ -151,6 +152,73 @@ where } } +impl RawArrayView, D> +where + D: Dimension, +{ + /// Splits the view into views of the real and imaginary components of the + /// elements. + pub fn split_complex(self) -> Complex> { + // Check that the size and alignment of `Complex` are as expected. + // These assertions should always pass, for arbitrary `T`. + assert_eq!( + mem::size_of::>(), + mem::size_of::().checked_mul(2).unwrap() + ); + assert_eq!(mem::align_of::>(), mem::align_of::()); + + let dim = self.dim.clone(); + + // Double the strides. In the zero-sized element case and for axes of + // length <= 1, we leave the strides as-is to avoid possible overflow. + let mut strides = self.strides.clone(); + if mem::size_of::() != 0 { + for ax in 0..strides.ndim() { + if dim[ax] > 1 { + strides[ax] = (strides[ax] as isize * 2) as usize; + } + } + } + + let ptr_re: *mut T = self.ptr.as_ptr().cast(); + let ptr_im: *mut T = if self.is_empty() { + // In the empty case, we can just reuse the existing pointer since + // it won't be dereferenced anyway. It is not safe to offset by + // one, since the allocation may be empty. + ptr_re + } else { + // In the nonempty case, we can safely offset into the first + // (complex) element. + unsafe { ptr_re.add(1) } + }; + + // `Complex` is `repr(C)` with only fields `re: T` and `im: T`. So, the + // real components of the elements start at the same pointer, and the + // imaginary components start at the pointer offset by one, with + // exactly double the strides. The new, doubled strides still meet the + // overflow constraints: + // + // - For the zero-sized element case, the strides are unchanged in + // units of bytes and in units of the element type. + // + // - For the nonzero-sized element case: + // + // - In units of bytes, the strides are unchanged. The only exception + // is axes of length <= 1, but those strides are irrelevant anyway. + // + // - Since `Complex` for nonzero `T` is always at least 2 bytes, + // and the original strides did not overflow in units of bytes, we + // know that the new, doubled strides will not overflow in units of + // `T`. + unsafe { + Complex { + re: RawArrayView::new_(ptr_re, dim.clone(), strides.clone()), + im: RawArrayView::new_(ptr_im, dim, strides), + } + } + } +} + impl RawArrayViewMut where D: Dimension, @@ -219,7 +287,7 @@ where assert!(!ptr.is_null(), "The pointer must be non-null."); if let Strides::Custom(strides) = &shape.strides { dimension::strides_non_negative(strides).unwrap(); - dimension::max_abs_offset_check_overflow::(&dim, &strides).unwrap(); + dimension::max_abs_offset_check_overflow::(&dim, strides).unwrap(); } else { dimension::size_of_shape_checked(&dim).unwrap(); } @@ -304,3 +372,20 @@ where unsafe { RawArrayViewMut::new(ptr, self.dim, self.strides) } } } + +impl RawArrayViewMut, D> +where + D: Dimension, +{ + /// Splits the view into views of the real and imaginary components of the + /// elements. + pub fn split_complex(self) -> Complex> { + let Complex { re, im } = self.into_raw_view().split_complex(); + unsafe { + Complex { + re: RawArrayViewMut::new(re.ptr, re.dim, re.strides), + im: RawArrayViewMut::new(im.ptr, im.dim, im.strides), + } + } + } +} diff --git a/src/impl_special_element_types.rs b/src/impl_special_element_types.rs index bf4384e42..5d5f18491 100644 --- a/src/impl_special_element_types.rs +++ b/src/impl_special_element_types.rs @@ -15,8 +15,6 @@ use crate::RawDataSubst; /// Methods specific to arrays with `MaybeUninit` elements. /// /// ***See also all methods for [`ArrayBase`]*** -/// -/// [`ArrayBase`]: struct.ArrayBase.html impl ArrayBase where S: RawDataSubst>, diff --git a/src/impl_views/constructors.rs b/src/impl_views/constructors.rs index fd9457fa1..98cb81fcc 100644 --- a/src/impl_views/constructors.rs +++ b/src/impl_views/constructors.rs @@ -13,7 +13,7 @@ use crate::error::ShapeError; use crate::extension::nonnull::nonnull_debug_checked_from_ptr; use crate::imp_prelude::*; use crate::{is_aligned, StrideShape}; -use crate::dimension::offset_from_ptr_to_memory; +use crate::dimension::offset_from_low_addr_ptr_to_logical_ptr; /// Methods for read-only array views. impl<'a, A, D> ArrayView<'a, A, D> @@ -57,7 +57,7 @@ where let dim = shape.dim; dimension::can_index_slice_with_strides(xs, &dim, &shape.strides)?; let strides = shape.strides.strides_for_dim(&dim); - unsafe { Ok(Self::new_(xs.as_ptr().offset(-offset_from_ptr_to_memory(&dim, &strides)), dim, strides)) } + unsafe { Ok(Self::new_(xs.as_ptr().add(offset_from_low_addr_ptr_to_logical_ptr(&dim, &strides)), dim, strides)) } } /// Create an `ArrayView` from shape information and a raw pointer to @@ -154,7 +154,7 @@ where let dim = shape.dim; dimension::can_index_slice_with_strides(xs, &dim, &shape.strides)?; let strides = shape.strides.strides_for_dim(&dim); - unsafe { Ok(Self::new_(xs.as_mut_ptr().offset(-offset_from_ptr_to_memory(&dim, &strides)), dim, strides)) } + unsafe { Ok(Self::new_(xs.as_mut_ptr().add(offset_from_low_addr_ptr_to_logical_ptr(&dim, &strides)), dim, strides)) } } /// Create an `ArrayViewMut` from shape information and a diff --git a/src/impl_views/conversions.rs b/src/impl_views/conversions.rs index cfd7f9aa0..ca571a761 100644 --- a/src/impl_views/conversions.rs +++ b/src/impl_views/conversions.rs @@ -7,11 +7,14 @@ // except according to those terms. use alloc::slice; +use rawpointer::PointerExt; +use std::mem::MaybeUninit; use crate::imp_prelude::*; use crate::{Baseiter, ElementsBase, ElementsBaseMut, Iter, IterMut}; +use crate::dimension::offset_from_low_addr_ptr_to_logical_ptr; use crate::iter::{self, AxisIter, AxisIterMut}; use crate::math_cell::MathCell; use crate::IndexLonger; @@ -33,7 +36,7 @@ where /// Return the array’s data as a slice, if it is contiguous and in standard order. /// Return `None` otherwise. /// - /// Note that while the method is similar to [`ArrayBase::as_slice()`], this method tranfers + /// Note that while the method is similar to [`ArrayBase::as_slice()`], this method transfers /// the view's lifetime to the slice, so it is a bit more powerful. pub fn to_slice(&self) -> Option<&'a [A]> { if self.is_standard_layout() { @@ -43,6 +46,26 @@ where } } + /// Return the array’s data as a slice, if it is contiguous. + /// Return `None` otherwise. + /// + /// Note that while the method is similar to + /// [`ArrayBase::as_slice_memory_order()`], this method transfers the view's + /// lifetime to the slice, so it is a bit more powerful. + pub fn to_slice_memory_order(&self) -> Option<&'a [A]> { + if self.is_contiguous() { + let offset = offset_from_low_addr_ptr_to_logical_ptr(&self.dim, &self.strides); + unsafe { + Some(slice::from_raw_parts( + self.ptr.sub(offset).as_ptr(), + self.len(), + )) + } + } else { + None + } + } + /// Converts to a raw array view. pub(crate) fn into_raw_view(self) -> RawArrayView { unsafe { RawArrayView::new(self.ptr, self.dim, self.strides) } @@ -52,9 +75,6 @@ where /// Methods specific to `ArrayView0`. /// /// ***See also all methods for [`ArrayView`] and [`ArrayBase`]*** -/// -/// [`ArrayBase`]: struct.ArrayBase.html -/// [`ArrayView`]: struct.ArrayView.html impl<'a, A> ArrayView<'a, A, Ix0> { /// Consume the view and return a reference to the single element in the array. /// @@ -81,9 +101,6 @@ impl<'a, A> ArrayView<'a, A, Ix0> { /// Methods specific to `ArrayViewMut0`. /// /// ***See also all methods for [`ArrayViewMut`] and [`ArrayBase`]*** -/// -/// [`ArrayBase`]: struct.ArrayBase.html -/// [`ArrayViewMut`]: struct.ArrayViewMut.html impl<'a, A> ArrayViewMut<'a, A, Ix0> { /// Consume the mutable view and return a mutable reference to the single element in the array. /// @@ -95,7 +112,7 @@ impl<'a, A> ArrayViewMut<'a, A, Ix0> { /// /// let mut array: Array0 = arr0(5.); /// let view = array.view_mut(); - /// let mut scalar = view.into_scalar(); + /// let scalar = view.into_scalar(); /// *scalar = 7.; /// assert_eq!(scalar, &7.); /// assert_eq!(array[()], 7.); @@ -113,12 +130,22 @@ where /// Return the array’s data as a slice, if it is contiguous and in standard order. /// Return `None` otherwise. /// - /// Note that while this is similar to [`ArrayBase::as_slice_mut()`], this method tranfers the + /// Note that while this is similar to [`ArrayBase::as_slice_mut()`], this method transfers the /// view's lifetime to the slice. pub fn into_slice(self) -> Option<&'a mut [A]> { self.try_into_slice().ok() } + /// Return the array’s data as a slice, if it is contiguous. + /// Return `None` otherwise. + /// + /// Note that while this is similar to + /// [`ArrayBase::as_slice_memory_order_mut()`], this method transfers the + /// view's lifetime to the slice. + pub fn into_slice_memory_order(self) -> Option<&'a mut [A]> { + self.try_into_slice_memory_order().ok() + } + /// Return a shared view of the array with elements as if they were embedded in cells. /// /// The cell view itself can be copied and accessed without exclusivity. @@ -133,6 +160,48 @@ where self.into_raw_view_mut().cast::>().deref_into_view() } } + + /// Return the array view as a view of `MaybeUninit` elements + /// + /// This conversion leaves the elements as they were (presumably initialized), but + /// they are represented with the `MaybeUninit` type. Effectively this means that + /// the elements can be overwritten without dropping the old element in its place. + /// (In some situations this is not what you want, while for `Copy` elements it makes + /// no difference at all.) + /// + /// # Safety + /// + /// This method allows writing uninitialized data into the view, which could leave any + /// original array that we borrow from in an inconsistent state. This is not allowed + /// when using the resulting array view. + pub(crate) unsafe fn into_maybe_uninit(self) -> ArrayViewMut<'a, MaybeUninit, D> { + // Safe because: A and MaybeUninit have the same representation; + // and we can go from initialized to (maybe) not unconditionally in terms of + // representation. However, the user must be careful to not write uninit elements + // through the view. + self.into_raw_view_mut().cast::>().deref_into_view_mut() + } +} + +/// Private raw array view methods +impl RawArrayView +where + D: Dimension, +{ + #[inline] + pub(crate) fn into_base_iter(self) -> Baseiter { + unsafe { Baseiter::new(self.ptr.as_ptr(), self.dim, self.strides) } + } +} + +impl RawArrayViewMut +where + D: Dimension, +{ + #[inline] + pub(crate) fn into_base_iter(self) -> Baseiter { + unsafe { Baseiter::new(self.ptr.as_ptr(), self.dim, self.strides) } + } } /// Private array view methods @@ -199,6 +268,22 @@ where } } + /// Return the array’s data as a slice, if it is contiguous. + /// Otherwise return self in the Err branch of the result. + fn try_into_slice_memory_order(self) -> Result<&'a mut [A], Self> { + if self.is_contiguous() { + let offset = offset_from_low_addr_ptr_to_logical_ptr(&self.dim, &self.strides); + unsafe { + Ok(slice::from_raw_parts_mut( + self.ptr.sub(offset).as_ptr(), + self.len(), + )) + } + } else { + Err(self) + } + } + pub(crate) fn into_iter_(self) -> IterMut<'a, A, D> { IterMut::new(self) } diff --git a/src/impl_views/indexing.rs b/src/impl_views/indexing.rs index f8056f930..3494b91db 100644 --- a/src/impl_views/indexing.rs +++ b/src/impl_views/indexing.rs @@ -33,7 +33,7 @@ use crate::NdIndex; /// let data = [0.; 256]; /// let long_life_ref = { /// // make a 16 × 16 array view -/// let view = ArrayView::from(&data[..]).into_shape((16, 16)).unwrap(); +/// let view = ArrayView::from(&data[..]).into_shape_with_order((16, 16)).unwrap(); /// /// // index the view and with `IndexLonger`. /// // Note here that we get a reference with a life that is derived from @@ -59,7 +59,7 @@ pub trait IndexLonger { /// See also [the `get` method][1] which works for all arrays and array /// views. /// - /// [1]: struct.ArrayBase.html#method.get + /// [1]: ArrayBase::get /// /// **Panics** if index is out of bounds. #[track_caller] @@ -74,8 +74,8 @@ pub trait IndexLonger { /// See also [the `get` method][1] (and [`get_mut`][2]) which works for all arrays and array /// views. /// - /// [1]: struct.ArrayBase.html#method.get - /// [2]: struct.ArrayBase.html#method.get_mut + /// [1]: ArrayBase::get + /// [2]: ArrayBase::get_mut /// /// **Panics** if index is out of bounds. #[track_caller] @@ -89,7 +89,7 @@ pub trait IndexLonger { /// See also [the `uget` method][1] which works for all arrays and array /// views. /// - /// [1]: struct.ArrayBase.html#method.uget + /// [1]: ArrayBase::uget /// /// **Note:** only unchecked for non-debug builds of ndarray. /// @@ -115,7 +115,7 @@ where /// See also [the `get` method][1] which works for all arrays and array /// views. /// - /// [1]: struct.ArrayBase.html#method.get + /// [1]: ArrayBase::get /// /// **Panics** if index is out of bounds. #[track_caller] @@ -136,7 +136,7 @@ where /// See also [the `uget` method][1] which works for all arrays and array /// views. /// - /// [1]: struct.ArrayBase.html#method.uget + /// [1]: ArrayBase::uget /// /// **Note:** only unchecked for non-debug builds of ndarray. unsafe fn uget(self, index: I) -> &'a A { @@ -161,14 +161,14 @@ where /// See also [the `get_mut` method][1] which works for all arrays and array /// views. /// - /// [1]: struct.ArrayBase.html#method.get_mut + /// [1]: ArrayBase::get_mut /// /// **Panics** if index is out of bounds. #[track_caller] fn index(mut self, index: I) -> &'a mut A { debug_bounds_check!(self, index); unsafe { - match self.get_ptr_mut(index) { + match self.get_mut_ptr(index) { Some(ptr) => &mut *ptr, None => array_out_of_bounds(), } @@ -181,12 +181,12 @@ where /// See also [the `get_mut` method][1] which works for all arrays and array /// views. /// - /// [1]: struct.ArrayBase.html#method.get_mut + /// [1]: ArrayBase::get_mut /// fn get(mut self, index: I) -> Option<&'a mut A> { debug_bounds_check!(self, index); unsafe { - match self.get_ptr_mut(index) { + match self.get_mut_ptr(index) { Some(ptr) => Some(&mut *ptr), None => None, } @@ -199,7 +199,7 @@ where /// See also [the `uget_mut` method][1] which works for all arrays and array /// views. /// - /// [1]: struct.ArrayBase.html#method.uget_mut + /// [1]: ArrayBase::uget_mut /// /// **Note:** only unchecked for non-debug builds of ndarray. unsafe fn uget(mut self, index: I) -> &'a mut A { diff --git a/src/impl_views/mod.rs b/src/impl_views/mod.rs index 487cc3cb2..cabea5b37 100644 --- a/src/impl_views/mod.rs +++ b/src/impl_views/mod.rs @@ -3,7 +3,4 @@ mod conversions; mod indexing; mod splitting; -pub use constructors::*; -pub use conversions::*; pub use indexing::*; -pub use splitting::*; diff --git a/src/impl_views/splitting.rs b/src/impl_views/splitting.rs index 8b58f4762..2ccc3ee91 100644 --- a/src/impl_views/splitting.rs +++ b/src/impl_views/splitting.rs @@ -8,6 +8,7 @@ use crate::imp_prelude::*; use crate::slice::MultiSliceArg; +use num_complex::Complex; /// Methods for read-only array views. impl<'a, A, D> ArrayView<'a, A, D> @@ -96,6 +97,37 @@ where } } +impl<'a, T, D> ArrayView<'a, Complex, D> +where + D: Dimension, +{ + /// Splits the view into views of the real and imaginary components of the + /// elements. + /// + /// ``` + /// use ndarray::prelude::*; + /// use num_complex::{Complex, Complex64}; + /// + /// let arr = array![ + /// [Complex64::new(1., 2.), Complex64::new(3., 4.)], + /// [Complex64::new(5., 6.), Complex64::new(7., 8.)], + /// [Complex64::new(9., 10.), Complex64::new(11., 12.)], + /// ]; + /// let Complex { re, im } = arr.view().split_complex(); + /// assert_eq!(re, array![[1., 3.], [5., 7.], [9., 11.]]); + /// assert_eq!(im, array![[2., 4.], [6., 8.], [10., 12.]]); + /// ``` + pub fn split_complex(self) -> Complex> { + unsafe { + let Complex { re, im } = self.into_raw_view().split_complex(); + Complex { + re: re.deref_into_view(), + im: im.deref_into_view(), + } + } + } +} + /// Methods for read-write array views. impl<'a, A, D> ArrayViewMut<'a, A, D> where @@ -123,7 +155,7 @@ where /// [`MultiSliceArg`], [`s!`], [`SliceArg`](crate::SliceArg), and /// [`SliceInfo`](crate::SliceInfo). /// - /// [`.multi_slice_mut()`]: struct.ArrayBase.html#method.multi_slice_mut + /// [`.multi_slice_mut()`]: ArrayBase::multi_slice_mut /// /// **Panics** if any of the following occur: /// @@ -138,3 +170,41 @@ where info.multi_slice_move(self) } } + +impl<'a, T, D> ArrayViewMut<'a, Complex, D> +where + D: Dimension, +{ + /// Splits the view into views of the real and imaginary components of the + /// elements. + /// + /// ``` + /// use ndarray::prelude::*; + /// use num_complex::{Complex, Complex64}; + /// + /// let mut arr = array![ + /// [Complex64::new(1., 2.), Complex64::new(3., 4.)], + /// [Complex64::new(5., 6.), Complex64::new(7., 8.)], + /// [Complex64::new(9., 10.), Complex64::new(11., 12.)], + /// ]; + /// + /// let Complex { mut re, mut im } = arr.view_mut().split_complex(); + /// assert_eq!(re, array![[1., 3.], [5., 7.], [9., 11.]]); + /// assert_eq!(im, array![[2., 4.], [6., 8.], [10., 12.]]); + /// + /// re[[0, 1]] = 13.; + /// im[[2, 0]] = 14.; + /// + /// assert_eq!(arr[[0, 1]], Complex64::new(13., 4.)); + /// assert_eq!(arr[[2, 0]], Complex64::new(9., 14.)); + /// ``` + pub fn split_complex(self) -> Complex> { + unsafe { + let Complex { re, im } = self.into_raw_view_mut().split_complex(); + Complex { + re: re.deref_into_view_mut(), + im: im.deref_into_view_mut(), + } + } + } +} diff --git a/src/indexes.rs b/src/indexes.rs index 6f45fb3de..541570184 100644 --- a/src/indexes.rs +++ b/src/indexes.rs @@ -75,7 +75,7 @@ where .slice() .iter() .zip(ix.slice().iter()) - .fold(0, |s, (&a, &b)| s + a as usize * b as usize); + .fold(0, |s, (&a, &b)| s + a * b); self.dim.size() - gone } }; @@ -180,22 +180,18 @@ impl NdProducer for Indices { private_impl! {} - #[doc(hidden)] fn raw_dim(&self) -> Self::Dim { self.dim } - #[doc(hidden)] fn equal_dim(&self, dim: &Self::Dim) -> bool { self.dim.equal(dim) } - #[doc(hidden)] fn as_ptr(&self) -> Self::Ptr { IndexPtr { index: self.start } } - #[doc(hidden)] fn layout(&self) -> Layout { if self.dim.ndim() <= 1 { Layout::one_dimensional() @@ -204,19 +200,16 @@ impl NdProducer for Indices { } } - #[doc(hidden)] unsafe fn as_ref(&self, ptr: Self::Ptr) -> Self::Item { ptr.index.into_pattern() } - #[doc(hidden)] unsafe fn uget_ptr(&self, i: &Self::Dim) -> Self::Ptr { let mut index = *i; index += &self.start; IndexPtr { index } } - #[doc(hidden)] fn stride_of(&self, axis: Axis) -> Self::Stride { axis.index() } @@ -226,7 +219,6 @@ impl NdProducer for Indices { 0 } - #[doc(hidden)] fn split_at(self, axis: Axis, index: usize) -> (Self, Self) { let start_a = self.start; let mut start_b = start_a; @@ -294,7 +286,7 @@ where .slice() .iter() .zip(self.index.slice().iter()) - .fold(0, |s, (&a, &b)| s + a as usize * b as usize); + .fold(0, |s, (&a, &b)| s + a * b); let l = self.dim.size() - gone; (l, Some(l)) } diff --git a/src/iterators/chunks.rs b/src/iterators/chunks.rs index e41c1bf25..9cf06b55f 100644 --- a/src/iterators/chunks.rs +++ b/src/iterators/chunks.rs @@ -1,6 +1,7 @@ +use std::marker::PhantomData; + use crate::imp_prelude::*; -use crate::ElementsBase; -use crate::ElementsBaseMut; +use crate::Baseiter; use crate::IntoDimension; use crate::{Layout, NdProducer}; @@ -9,6 +10,7 @@ impl_ndproducer! { [Clone => 'a, A, D: Clone ] ExactChunks { base, + life, chunk, inner_strides, } @@ -23,16 +25,14 @@ impl_ndproducer! { } } -type BaseProducerRef<'a, A, D> = ArrayView<'a, A, D>; -type BaseProducerMut<'a, A, D> = ArrayViewMut<'a, A, D>; - /// Exact chunks producer and iterable. /// -/// See [`.exact_chunks()`](../struct.ArrayBase.html#method.exact_chunks) for more +/// See [`.exact_chunks()`](ArrayBase::exact_chunks) for more /// information. //#[derive(Debug)] pub struct ExactChunks<'a, A, D> { - base: BaseProducerRef<'a, A, D>, + base: RawArrayView, + life: PhantomData<&'a A>, chunk: D, inner_strides: D, } @@ -41,10 +41,11 @@ impl<'a, A, D: Dimension> ExactChunks<'a, A, D> { /// Creates a new exact chunks producer. /// /// **Panics** if any chunk dimension is zero - pub(crate) fn new(mut a: ArrayView<'a, A, D>, chunk: E) -> Self + pub(crate) fn new(a: ArrayView<'a, A, D>, chunk: E) -> Self where E: IntoDimension, { + let mut a = a.into_raw_view(); let chunk = chunk.into_dimension(); ndassert!( a.ndim() == chunk.ndim(), @@ -59,11 +60,12 @@ impl<'a, A, D: Dimension> ExactChunks<'a, A, D> { for i in 0..a.ndim() { a.dim[i] /= chunk[i]; } - let inner_strides = a.raw_strides(); + let inner_strides = a.strides.clone(); a.strides *= &chunk; ExactChunks { base: a, + life: PhantomData, chunk, inner_strides, } @@ -79,7 +81,8 @@ where type IntoIter = ExactChunksIter<'a, A, D>; fn into_iter(self) -> Self::IntoIter { ExactChunksIter { - iter: self.base.into_elements_base(), + iter: self.base.into_base_iter(), + life: self.life, chunk: self.chunk, inner_strides: self.inner_strides, } @@ -88,10 +91,11 @@ where /// Exact chunks iterator. /// -/// See [`.exact_chunks()`](../struct.ArrayBase.html#method.exact_chunks) for more +/// See [`.exact_chunks()`](ArrayBase::exact_chunks) for more /// information. pub struct ExactChunksIter<'a, A, D> { - iter: ElementsBase<'a, A, D>, + iter: Baseiter, + life: PhantomData<&'a A>, chunk: D, inner_strides: D, } @@ -101,6 +105,7 @@ impl_ndproducer! { [Clone => ] ExactChunksMut { base, + life, chunk, inner_strides, } @@ -118,11 +123,12 @@ impl_ndproducer! { /// Exact chunks producer and iterable. /// -/// See [`.exact_chunks_mut()`](../struct.ArrayBase.html#method.exact_chunks_mut) +/// See [`.exact_chunks_mut()`](ArrayBase::exact_chunks_mut) /// for more information. //#[derive(Debug)] pub struct ExactChunksMut<'a, A, D> { - base: BaseProducerMut<'a, A, D>, + base: RawArrayViewMut, + life: PhantomData<&'a mut A>, chunk: D, inner_strides: D, } @@ -131,10 +137,11 @@ impl<'a, A, D: Dimension> ExactChunksMut<'a, A, D> { /// Creates a new exact chunks producer. /// /// **Panics** if any chunk dimension is zero - pub(crate) fn new(mut a: ArrayViewMut<'a, A, D>, chunk: E) -> Self + pub(crate) fn new(a: ArrayViewMut<'a, A, D>, chunk: E) -> Self where E: IntoDimension, { + let mut a = a.into_raw_view_mut(); let chunk = chunk.into_dimension(); ndassert!( a.ndim() == chunk.ndim(), @@ -149,11 +156,12 @@ impl<'a, A, D: Dimension> ExactChunksMut<'a, A, D> { for i in 0..a.ndim() { a.dim[i] /= chunk[i]; } - let inner_strides = a.raw_strides(); + let inner_strides = a.strides.clone(); a.strides *= &chunk; ExactChunksMut { base: a, + life: PhantomData, chunk, inner_strides, } @@ -169,7 +177,8 @@ where type IntoIter = ExactChunksIterMut<'a, A, D>; fn into_iter(self) -> Self::IntoIter { ExactChunksIterMut { - iter: self.base.into_elements_base(), + iter: self.base.into_base_iter(), + life: self.life, chunk: self.chunk, inner_strides: self.inner_strides, } @@ -181,16 +190,17 @@ impl_iterator! { [Clone => 'a, A, D: Clone] ExactChunksIter { iter, + life, chunk, inner_strides, } ExactChunksIter<'a, A, D> { type Item = ArrayView<'a, A, D>; - fn item(&mut self, elt) { + fn item(&mut self, ptr) { unsafe { ArrayView::new_( - elt, + ptr, self.chunk.clone(), self.inner_strides.clone()) } @@ -209,10 +219,10 @@ impl_iterator! { ExactChunksIterMut<'a, A, D> { type Item = ArrayViewMut<'a, A, D>; - fn item(&mut self, elt) { + fn item(&mut self, ptr) { unsafe { ArrayViewMut::new_( - elt, + ptr, self.chunk.clone(), self.inner_strides.clone()) } @@ -222,10 +232,17 @@ impl_iterator! { /// Exact chunks iterator. /// -/// See [`.exact_chunks_mut()`](../struct.ArrayBase.html#method.exact_chunks_mut) +/// See [`.exact_chunks_mut()`](ArrayBase::exact_chunks_mut) /// for more information. pub struct ExactChunksIterMut<'a, A, D> { - iter: ElementsBaseMut<'a, A, D>, + iter: Baseiter, + life: PhantomData<&'a mut A>, chunk: D, inner_strides: D, } + +send_sync_read_only!(ExactChunks); +send_sync_read_only!(ExactChunksIter); + +send_sync_read_write!(ExactChunksMut); +send_sync_read_write!(ExactChunksIterMut); diff --git a/src/iterators/into_iter.rs b/src/iterators/into_iter.rs new file mode 100644 index 000000000..cfa48299a --- /dev/null +++ b/src/iterators/into_iter.rs @@ -0,0 +1,136 @@ +// Copyright 2020-2021 bluss and ndarray developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::mem; +use std::ptr::NonNull; + +use crate::imp_prelude::*; +use crate::OwnedRepr; + +use super::Baseiter; +use crate::impl_owned_array::drop_unreachable_raw; + + +/// By-value iterator for an array +pub struct IntoIter +where + D: Dimension, +{ + array_data: OwnedRepr, + inner: Baseiter, + data_len: usize, + /// first memory address of an array element + array_head_ptr: NonNull, + // if true, the array owns elements that are not reachable by indexing + // through all the indices of the dimension. + has_unreachable_elements: bool, +} + +impl IntoIter +where + D: Dimension, +{ + /// Create a new by-value iterator that consumes `array` + pub(crate) fn new(mut array: Array) -> Self { + unsafe { + let array_head_ptr = array.ptr; + let ptr = array.as_mut_ptr(); + let mut array_data = array.data; + let data_len = array_data.release_all_elements(); + debug_assert!(data_len >= array.dim.size()); + let has_unreachable_elements = array.dim.size() != data_len; + let inner = Baseiter::new(ptr, array.dim, array.strides); + + IntoIter { + array_data, + inner, + data_len, + array_head_ptr, + has_unreachable_elements, + } + } + } +} + +impl Iterator for IntoIter { + type Item = A; + + #[inline] + fn next(&mut self) -> Option { + self.inner.next().map(|p| unsafe { p.read() }) + } + + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} + +impl ExactSizeIterator for IntoIter { + fn len(&self) -> usize { self.inner.len() } +} + +impl Drop for IntoIter +where + D: Dimension +{ + fn drop(&mut self) { + if !self.has_unreachable_elements || mem::size_of::() == 0 || !mem::needs_drop::() { + return; + } + + // iterate til the end + while let Some(_) = self.next() { } + + unsafe { + let data_ptr = self.array_data.as_ptr_mut(); + let view = RawArrayViewMut::new(self.array_head_ptr, self.inner.dim.clone(), + self.inner.strides.clone()); + debug_assert!(self.inner.dim.size() < self.data_len, "data_len {} and dim size {}", + self.data_len, self.inner.dim.size()); + drop_unreachable_raw(view, data_ptr, self.data_len); + } + } +} + +impl IntoIterator for Array +where + D: Dimension +{ + type Item = A; + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter::new(self) + } +} + +impl IntoIterator for ArcArray +where + D: Dimension, + A: Clone, +{ + type Item = A; + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter::new(self.into_owned()) + } +} + +impl IntoIterator for CowArray<'_, A, D> +where + D: Dimension, + A: Clone, +{ + type Item = A; + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter::new(self.into_owned()) + } +} diff --git a/src/iterators/iter.rs b/src/iterators/iter.rs index 7352e5e18..3f8b05009 100644 --- a/src/iterators/iter.rs +++ b/src/iterators/iter.rs @@ -4,12 +4,27 @@ //! implementation structs. //! //! -//! See also [`NdProducer`](../trait.NdProducer.html). +//! See also [`NdProducer`](crate::NdProducer). pub use crate::dimension::Axes; pub use crate::indexes::{Indices, IndicesIter}; pub use crate::iterators::{ - AxisChunksIter, AxisChunksIterMut, AxisIter, AxisIterMut, ExactChunks, ExactChunksIter, - ExactChunksIterMut, ExactChunksMut, IndexedIter, IndexedIterMut, Iter, IterMut, Lanes, - LanesIter, LanesIterMut, LanesMut, Windows, + AxisChunksIter, + AxisChunksIterMut, + AxisIter, + AxisIterMut, + ExactChunks, + ExactChunksIter, + ExactChunksIterMut, + ExactChunksMut, + IndexedIter, + IndexedIterMut, + Iter, + IterMut, + IntoIter, + Lanes, + LanesIter, + LanesIterMut, + LanesMut, + Windows, }; diff --git a/src/iterators/lanes.rs b/src/iterators/lanes.rs index 2163c58a6..7286e0696 100644 --- a/src/iterators/lanes.rs +++ b/src/iterators/lanes.rs @@ -23,7 +23,7 @@ impl_ndproducer! { } } -/// See [`.lanes()`](../struct.ArrayBase.html#method.lanes) +/// See [`.lanes()`](ArrayBase::lanes) /// for more information. pub struct Lanes<'a, A, D> { base: ArrayView<'a, A, D>, @@ -39,17 +39,16 @@ impl<'a, A, D: Dimension> Lanes<'a, A, D> { let ndim = v.ndim(); let len; let stride; - let iter_v; - if ndim == 0 { + let iter_v = if ndim == 0 { len = 1; stride = 1; - iter_v = v.try_remove_axis(Axis(0)) + v.try_remove_axis(Axis(0)) } else { let i = axis.index(); len = v.dim[i]; stride = v.strides[i] as isize; - iter_v = v.try_remove_axis(axis) - } + v.try_remove_axis(axis) + }; Lanes { inner_len: len, inner_stride: stride, @@ -92,7 +91,7 @@ where } } -/// See [`.lanes_mut()`](../struct.ArrayBase.html#method.lanes_mut) +/// See [`.lanes_mut()`](ArrayBase::lanes_mut) /// for more information. pub struct LanesMut<'a, A, D> { base: ArrayViewMut<'a, A, D>, @@ -108,17 +107,16 @@ impl<'a, A, D: Dimension> LanesMut<'a, A, D> { let ndim = v.ndim(); let len; let stride; - let iter_v; - if ndim == 0 { + let iter_v = if ndim == 0 { len = 1; stride = 1; - iter_v = v.try_remove_axis(Axis(0)) + v.try_remove_axis(Axis(0)) } else { let i = axis.index(); len = v.dim[i]; stride = v.strides[i] as isize; - iter_v = v.try_remove_axis(axis) - } + v.try_remove_axis(axis) + }; LanesMut { inner_len: len, inner_stride: stride, diff --git a/src/iterators/macros.rs b/src/iterators/macros.rs index d3a54453e..7fbe410fe 100644 --- a/src/iterators/macros.rs +++ b/src/iterators/macros.rs @@ -63,42 +63,34 @@ impl<$($typarm)*> NdProducer for $fulltype { type Ptr = *mut A; type Stride = isize; - #[doc(hidden)] fn raw_dim(&self) -> D { self.$base.raw_dim() } - #[doc(hidden)] fn layout(&self) -> Layout { self.$base.layout() } - #[doc(hidden)] fn as_ptr(&self) -> *mut A { self.$base.as_ptr() as *mut _ } - #[doc(hidden)] fn contiguous_stride(&self) -> isize { self.$base.contiguous_stride() } - #[doc(hidden)] unsafe fn as_ref(&$self_, $ptr: *mut A) -> Self::Item { $refexpr } - #[doc(hidden)] unsafe fn uget_ptr(&self, i: &Self::Dim) -> *mut A { - self.$base.uget_ptr(i) + self.$base.uget_ptr(i) as *mut _ } - #[doc(hidden)] fn stride_of(&self, axis: Axis) -> isize { self.$base.stride_of(axis) } - #[doc(hidden)] fn split_at(self, axis: Axis, index: usize) -> (Self, Self) { let (a, b) = self.$base.split_at(axis, index); ($typename { @@ -114,6 +106,7 @@ impl<$($typarm)*> NdProducer for $fulltype { )* }) } + private_impl!{} } diff --git a/src/iterators/mod.rs b/src/iterators/mod.rs index cc35015c8..872d80f0b 100644 --- a/src/iterators/mod.rs +++ b/src/iterators/mod.rs @@ -9,14 +9,16 @@ #[macro_use] mod macros; mod chunks; +mod into_iter; pub mod iter; mod lanes; mod windows; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; use std::iter::FromIterator; use std::marker::PhantomData; use std::ptr; -use alloc::vec::Vec; use crate::Ix1; @@ -24,6 +26,7 @@ use super::{ArrayBase, ArrayView, ArrayViewMut, Axis, Data, NdProducer, RemoveAx use super::{Dimension, Ix, Ixs}; pub use self::chunks::{ExactChunks, ExactChunksIter, ExactChunksIterMut, ExactChunksMut}; +pub use self::into_iter::IntoIter; pub use self::lanes::{Lanes, LanesMut}; pub use self::windows::Windows; @@ -32,6 +35,7 @@ use std::slice::{self, Iter as SliceIter, IterMut as SliceIterMut}; /// Base for iterators over all axes. /// /// Iterator element type is `*mut A`. +#[derive(Debug)] pub struct Baseiter { ptr: *mut A, dim: D, @@ -101,7 +105,7 @@ impl Iterator for Baseiter { } } -impl<'a, A, D: Dimension> ExactSizeIterator for Baseiter { +impl ExactSizeIterator for Baseiter { fn len(&self) -> usize { match self.index { None => 0, @@ -112,7 +116,7 @@ impl<'a, A, D: Dimension> ExactSizeIterator for Baseiter { .slice() .iter() .zip(ix.slice().iter()) - .fold(0, |s, (&a, &b)| s + a as usize * b as usize); + .fold(0, |s, (&a, &b)| s + a * b); self.dim.size() - gone } } @@ -303,7 +307,7 @@ where } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum ElementsRepr { Slice(S), Counted(C), @@ -313,12 +317,14 @@ pub enum ElementsRepr { /// /// Iterator element type is `&'a A`. /// -/// See [`.iter()`](../struct.ArrayBase.html#method.iter) for more information. +/// See [`.iter()`](ArrayBase::iter) for more information. +#[derive(Debug)] pub struct Iter<'a, A, D> { inner: ElementsRepr, ElementsBase<'a, A, D>>, } /// Counted read only iterator +#[derive(Debug)] pub struct ElementsBase<'a, A, D> { inner: Baseiter, life: PhantomData<&'a A>, @@ -328,7 +334,8 @@ pub struct ElementsBase<'a, A, D> { /// /// Iterator element type is `&'a mut A`. /// -/// See [`.iter_mut()`](../struct.ArrayBase.html#method.iter_mut) for more information. +/// See [`.iter_mut()`](ArrayBase::iter_mut) for more information. +#[derive(Debug)] pub struct IterMut<'a, A, D> { inner: ElementsRepr, ElementsBaseMut<'a, A, D>>, } @@ -336,6 +343,7 @@ pub struct IterMut<'a, A, D> { /// An iterator over the elements of an array. /// /// Iterator element type is `&'a mut A`. +#[derive(Debug)] pub struct ElementsBaseMut<'a, A, D> { inner: Baseiter, life: PhantomData<&'a mut A>, @@ -352,12 +360,12 @@ impl<'a, A, D: Dimension> ElementsBaseMut<'a, A, D> { /// An iterator over the indexes and elements of an array. /// -/// See [`.indexed_iter()`](../struct.ArrayBase.html#method.indexed_iter) for more information. +/// See [`.indexed_iter()`](ArrayBase::indexed_iter) for more information. #[derive(Clone)] pub struct IndexedIter<'a, A, D>(ElementsBase<'a, A, D>); /// An iterator over the indexes and elements of an array (mutable). /// -/// See [`.indexed_iter_mut()`](../struct.ArrayBase.html#method.indexed_iter_mut) for more information. +/// See [`.indexed_iter_mut()`](ArrayBase::indexed_iter_mut) for more information. pub struct IndexedIterMut<'a, A, D>(ElementsBaseMut<'a, A, D>); impl<'a, A, D> IndexedIter<'a, A, D> @@ -679,7 +687,7 @@ where /// An iterator that traverses over all axes but one, and yields a view for /// each lane along that axis. /// -/// See [`.lanes()`](../struct.ArrayBase.html#method.lanes) for more information. +/// See [`.lanes()`](ArrayBase::lanes) for more information. pub struct LanesIter<'a, A, D> { inner_len: Ix, inner_stride: Ixs, @@ -730,7 +738,7 @@ where /// An iterator that traverses over all dimensions but the innermost, /// and yields each inner row (mutable). /// -/// See [`.lanes_mut()`](../struct.ArrayBase.html#method.lanes_mut) +/// See [`.lanes_mut()`](ArrayBase::lanes_mut) /// for more information. pub struct LanesIterMut<'a, A, D> { inner_len: Ix, @@ -926,8 +934,8 @@ where /// /// Iterator element type is `ArrayView<'a, A, D>`. /// -/// See [`.outer_iter()`](../struct.ArrayBase.html#method.outer_iter) -/// or [`.axis_iter()`](../struct.ArrayBase.html#method.axis_iter) +/// See [`.outer_iter()`](ArrayBase::outer_iter) +/// or [`.axis_iter()`](ArrayBase::axis_iter) /// for more information. #[derive(Debug)] pub struct AxisIter<'a, A, D> { @@ -1024,8 +1032,8 @@ where /// /// Iterator element type is `ArrayViewMut<'a, A, D>`. /// -/// See [`.outer_iter_mut()`](../struct.ArrayBase.html#method.outer_iter_mut) -/// or [`.axis_iter_mut()`](../struct.ArrayBase.html#method.axis_iter_mut) +/// See [`.outer_iter_mut()`](ArrayBase::outer_iter_mut) +/// or [`.axis_iter_mut()`](ArrayBase::axis_iter_mut) /// for more information. pub struct AxisIterMut<'a, A, D> { iter: AxisIterCore, @@ -1106,15 +1114,14 @@ impl<'a, A, D: Dimension> NdProducer for AxisIter<'a, A, D> { type Ptr = *mut A; type Stride = isize; - #[doc(hidden)] fn layout(&self) -> crate::Layout { crate::Layout::one_dimensional() } - #[doc(hidden)] + fn raw_dim(&self) -> Self::Dim { Ix1(self.len()) } - #[doc(hidden)] + fn as_ptr(&self) -> Self::Ptr { if self.len() > 0 { // `self.iter.index` is guaranteed to be in-bounds if any of the @@ -1132,7 +1139,6 @@ impl<'a, A, D: Dimension> NdProducer for AxisIter<'a, A, D> { self.iter.stride } - #[doc(hidden)] unsafe fn as_ref(&self, ptr: Self::Ptr) -> Self::Item { ArrayView::new_( ptr, @@ -1140,20 +1146,19 @@ impl<'a, A, D: Dimension> NdProducer for AxisIter<'a, A, D> { self.iter.inner_strides.clone(), ) } - #[doc(hidden)] + unsafe fn uget_ptr(&self, i: &Self::Dim) -> Self::Ptr { self.iter.offset(self.iter.index + i[0]) } - #[doc(hidden)] fn stride_of(&self, _axis: Axis) -> isize { self.contiguous_stride() } - #[doc(hidden)] fn split_at(self, _axis: Axis, index: usize) -> (Self, Self) { self.split_at(index) } + private_impl! {} } @@ -1163,15 +1168,14 @@ impl<'a, A, D: Dimension> NdProducer for AxisIterMut<'a, A, D> { type Ptr = *mut A; type Stride = isize; - #[doc(hidden)] fn layout(&self) -> crate::Layout { crate::Layout::one_dimensional() } - #[doc(hidden)] + fn raw_dim(&self) -> Self::Dim { Ix1(self.len()) } - #[doc(hidden)] + fn as_ptr(&self) -> Self::Ptr { if self.len() > 0 { // `self.iter.index` is guaranteed to be in-bounds if any of the @@ -1189,7 +1193,6 @@ impl<'a, A, D: Dimension> NdProducer for AxisIterMut<'a, A, D> { self.iter.stride } - #[doc(hidden)] unsafe fn as_ref(&self, ptr: Self::Ptr) -> Self::Item { ArrayViewMut::new_( ptr, @@ -1197,20 +1200,19 @@ impl<'a, A, D: Dimension> NdProducer for AxisIterMut<'a, A, D> { self.iter.inner_strides.clone(), ) } - #[doc(hidden)] + unsafe fn uget_ptr(&self, i: &Self::Dim) -> Self::Ptr { self.iter.offset(self.iter.index + i[0]) } - #[doc(hidden)] fn stride_of(&self, _axis: Axis) -> isize { self.contiguous_stride() } - #[doc(hidden)] fn split_at(self, _axis: Axis, index: usize) -> (Self, Self) { self.split_at(index) } + private_impl! {} } @@ -1223,7 +1225,7 @@ impl<'a, A, D: Dimension> NdProducer for AxisIterMut<'a, A, D> { /// /// Iterator element type is `ArrayView<'a, A, D>`. /// -/// See [`.axis_chunks_iter()`](../struct.ArrayBase.html#method.axis_chunks_iter) for more information. +/// See [`.axis_chunks_iter()`](ArrayBase::axis_chunks_iter) for more information. pub struct AxisChunksIter<'a, A, D> { iter: AxisIterCore, /// Index of the partial chunk (the chunk smaller than the specified chunk @@ -1404,7 +1406,7 @@ macro_rules! chunk_iter_impl { /// /// Iterator element type is `ArrayViewMut<'a, A, D>`. /// -/// See [`.axis_chunks_iter_mut()`](../struct.ArrayBase.html#method.axis_chunks_iter_mut) +/// See [`.axis_chunks_iter_mut()`](ArrayBase::axis_chunks_iter_mut) /// for more information. pub struct AxisChunksIterMut<'a, A, D> { iter: AxisIterCore, @@ -1448,6 +1450,7 @@ send_sync_read_write!(ElementsBaseMut); /// /// The iterator must produce exactly the number of elements it reported or /// diverge before reaching the end. +#[allow(clippy::missing_safety_doc)] // not nameable downstream pub unsafe trait TrustedIterator {} use crate::indexes::IndicesIterF; @@ -1470,6 +1473,7 @@ unsafe impl TrustedIterator for ::std::ops::Range {} // FIXME: These indices iter are dubious -- size needs to be checked up front. unsafe impl TrustedIterator for IndicesIter where D: Dimension {} unsafe impl TrustedIterator for IndicesIterF where D: Dimension {} +unsafe impl TrustedIterator for IntoIter where D: Dimension {} /// Like Iterator::collect, but only for trusted length iterators pub fn to_vec(iter: I) -> Vec diff --git a/src/iterators/windows.rs b/src/iterators/windows.rs index 4538f7abb..9140f43b9 100644 --- a/src/iterators/windows.rs +++ b/src/iterators/windows.rs @@ -1,15 +1,19 @@ -use super::ElementsBase; +use std::marker::PhantomData; + +use super::Baseiter; use crate::imp_prelude::*; use crate::IntoDimension; use crate::Layout; use crate::NdProducer; +use crate::Slice; /// Window producer and iterable /// -/// See [`.windows()`](../struct.ArrayBase.html#method.windows) for more +/// See [`.windows()`](ArrayBase::windows) for more /// information. pub struct Windows<'a, A, D> { - base: ArrayView<'a, A, D>, + base: RawArrayView, + life: PhantomData<&'a A>, window: D, strides: D, } @@ -20,6 +24,23 @@ impl<'a, A, D: Dimension> Windows<'a, A, D> { E: IntoDimension, { let window = window_size.into_dimension(); + let ndim = window.ndim(); + + let mut unit_stride = D::zeros(ndim); + unit_stride.slice_mut().fill(1); + + Windows::new_with_stride(a, window, unit_stride) + } + + pub(crate) fn new_with_stride(a: ArrayView<'a, A, D>, window_size: E, axis_strides: E) -> Self + where + E: IntoDimension, + { + let window = window_size.into_dimension(); + + let strides = axis_strides.into_dimension(); + let window_strides = a.strides.clone(); + ndassert!( a.ndim() == window.ndim(), concat!( @@ -30,21 +51,36 @@ impl<'a, A, D: Dimension> Windows<'a, A, D> { a.ndim(), a.shape() ); - let mut size = a.dim; - for (sz, &ws) in size.slice_mut().iter_mut().zip(window.slice()) { - assert_ne!(ws, 0, "window-size must not be zero!"); - // cannot use std::cmp::max(0, ..) since arithmetic underflow panics - *sz = if *sz < ws { 0 } else { *sz - ws + 1 }; - } - let window_strides = a.strides.clone(); + ndassert!( + a.ndim() == strides.ndim(), + concat!( + "Stride dimension {} does not match array dimension {} ", + "(with array of shape {:?})" + ), + strides.ndim(), + a.ndim(), + a.shape() + ); - unsafe { - Windows { - base: ArrayView::new(a.ptr, size, a.strides), - window, - strides: window_strides, + let mut base = a; + base.slice_each_axis_inplace(|ax_desc| { + let len = ax_desc.len; + let wsz = window[ax_desc.axis.index()]; + let stride = strides[ax_desc.axis.index()]; + + if len < wsz { + Slice::new(0, Some(0), 1) + } else { + Slice::new(0, Some((len - wsz + 1) as isize), stride as isize) } + }); + + Windows { + base: base.into_raw_view(), + life: PhantomData, + window, + strides: window_strides, } } } @@ -54,6 +90,7 @@ impl_ndproducer! { [Clone => 'a, A, D: Clone ] Windows { base, + life, window, strides, } @@ -77,7 +114,8 @@ where type IntoIter = WindowsIter<'a, A, D>; fn into_iter(self) -> Self::IntoIter { WindowsIter { - iter: self.base.into_elements_base(), + iter: self.base.into_base_iter(), + life: self.life, window: self.window, strides: self.strides, } @@ -86,10 +124,11 @@ where /// Window iterator. /// -/// See [`.windows()`](../struct.ArrayBase.html#method.windows) for more +/// See [`.windows()`](ArrayBase::windows) for more /// information. pub struct WindowsIter<'a, A, D> { - iter: ElementsBase<'a, A, D>, + iter: Baseiter, + life: PhantomData<&'a A>, window: D, strides: D, } @@ -99,19 +138,23 @@ impl_iterator! { [Clone => 'a, A, D: Clone] WindowsIter { iter, + life, window, strides, } WindowsIter<'a, A, D> { type Item = ArrayView<'a, A, D>; - fn item(&mut self, elt) { + fn item(&mut self, ptr) { unsafe { ArrayView::new_( - elt, + ptr, self.window.clone(), self.strides.clone()) } } } } + +send_sync_read_only!(Windows); +send_sync_read_only!(WindowsIter); diff --git a/src/itertools.rs b/src/itertools.rs index 96732f903..8edfd75eb 100644 --- a/src/itertools.rs +++ b/src/itertools.rs @@ -64,11 +64,9 @@ where /// The special cases of one and two arguments produce the equivalent of /// `$a.into_iter()` and `$a.into_iter().zip($b)` respectively. /// -/// Prefer this macro `izip!()` over [`multizip`] for the performance benefits +/// Prefer this macro `izip!()` over `multizip` for the performance benefits /// of using the standard library `.zip()`. /// -/// [`multizip`]: fn.multizip.html -/// /// ``` /// #[macro_use] extern crate itertools; /// # fn main() { @@ -88,7 +86,8 @@ where /// **Note:** To enable the macros in this crate, use the `#[macro_use]` /// attribute when importing the crate: /// -/// ``` +/// ```no_run +/// # #[allow(unused_imports)] /// #[macro_use] extern crate itertools; /// # fn main() { } /// ``` diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 625fc7ff2..9eecf016d 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -9,6 +9,11 @@ mod layoutfmt; pub struct Layout(u32); impl Layout { + pub(crate) const CORDER: u32 = 0b01; + pub(crate) const FORDER: u32 = 0b10; + pub(crate) const CPREFER: u32 = 0b0100; + pub(crate) const FPREFER: u32 = 0b1000; + #[inline(always)] pub(crate) fn is(self, flag: u32) -> bool { self.0 & flag != 0 @@ -33,22 +38,22 @@ impl Layout { #[inline(always)] pub(crate) fn c() -> Layout { - Layout(CORDER | CPREFER) + Layout(Layout::CORDER | Layout::CPREFER) } #[inline(always)] pub(crate) fn f() -> Layout { - Layout(FORDER | FPREFER) + Layout(Layout::FORDER | Layout::FPREFER) } #[inline(always)] pub(crate) fn cpref() -> Layout { - Layout(CPREFER) + Layout(Layout::CPREFER) } #[inline(always)] pub(crate) fn fpref() -> Layout { - Layout(FPREFER) + Layout(Layout::FPREFER) } #[inline(always)] @@ -58,18 +63,14 @@ impl Layout { /// A simple "score" method which scores positive for preferring C-order, negative for F-order /// Subject to change when we can describe other layouts + #[inline] pub(crate) fn tendency(self) -> i32 { - (self.is(CORDER) as i32 - self.is(FORDER) as i32) + - (self.is(CPREFER) as i32 - self.is(FPREFER) as i32) + (self.is(Layout::CORDER) as i32 - self.is(Layout::FORDER) as i32) + + (self.is(Layout::CPREFER) as i32 - self.is(Layout::FPREFER) as i32) } } -pub const CORDER: u32 = 0b01; -pub const FORDER: u32 = 0b10; -pub const CPREFER: u32 = 0b0100; -pub const FPREFER: u32 = 0b1000; - #[cfg(test)] mod tests { @@ -82,10 +83,11 @@ mod tests { type M0 = Array0; macro_rules! assert_layouts { - ($mat:expr, $($layout:expr),*) => {{ + ($mat:expr, $($layout:ident),*) => {{ let layout = $mat.view().layout(); $( - assert!(layout.is($layout), "Assertion failed: array {:?} is not layout {}", + assert!(layout.is(Layout::$layout), + "Assertion failed: array {:?} is not layout {}", $mat, stringify!($layout)); )* @@ -93,10 +95,11 @@ mod tests { } macro_rules! assert_not_layouts { - ($mat:expr, $($layout:expr),*) => {{ + ($mat:expr, $($layout:ident),*) => {{ let layout = $mat.view().layout(); $( - assert!(!layout.is($layout), "Assertion failed: array {:?} show not have layout {}", + assert!(!layout.is(Layout::$layout), + "Assertion failed: array {:?} show not have layout {}", $mat, stringify!($layout)); )* @@ -109,10 +112,10 @@ mod tests { let b = M::zeros((5, 5).f()); let ac = a.view().layout(); let af = b.view().layout(); - assert!(ac.is(CORDER) && ac.is(CPREFER)); - assert!(!ac.is(FORDER) && !ac.is(FPREFER)); - assert!(!af.is(CORDER) && !af.is(CPREFER)); - assert!(af.is(FORDER) && af.is(FPREFER)); + assert!(ac.is(Layout::CORDER) && ac.is(Layout::CPREFER)); + assert!(!ac.is(Layout::FORDER) && !ac.is(Layout::FPREFER)); + assert!(!af.is(Layout::CORDER) && !af.is(Layout::CPREFER)); + assert!(af.is(Layout::FORDER) && af.is(Layout::FPREFER)); } #[test] @@ -151,10 +154,10 @@ mod tests { let v1 = a.slice(s![1.., ..]).layout(); let v2 = a.slice(s![.., 1..]).layout(); - assert!(v1.is(CORDER) && v1.is(CPREFER)); - assert!(!v1.is(FORDER) && !v1.is(FPREFER)); - assert!(!v2.is(CORDER) && v2.is(CPREFER)); - assert!(!v2.is(FORDER) && !v2.is(FPREFER)); + assert!(v1.is(Layout::CORDER) && v1.is(Layout::CPREFER)); + assert!(!v1.is(Layout::FORDER) && !v1.is(Layout::FPREFER)); + assert!(!v2.is(Layout::CORDER) && v2.is(Layout::CPREFER)); + assert!(!v2.is(Layout::FORDER) && !v2.is(Layout::FPREFER)); } let b = M::zeros((5, 5).f()); @@ -163,10 +166,38 @@ mod tests { let v1 = b.slice(s![1.., ..]).layout(); let v2 = b.slice(s![.., 1..]).layout(); - assert!(!v1.is(CORDER) && !v1.is(CPREFER)); - assert!(!v1.is(FORDER) && v1.is(FPREFER)); - assert!(!v2.is(CORDER) && !v2.is(CPREFER)); - assert!(v2.is(FORDER) && v2.is(FPREFER)); + assert!(!v1.is(Layout::CORDER) && !v1.is(Layout::CPREFER)); + assert!(!v1.is(Layout::FORDER) && v1.is(Layout::FPREFER)); + assert!(!v2.is(Layout::CORDER) && !v2.is(Layout::CPREFER)); + assert!(v2.is(Layout::FORDER) && v2.is(Layout::FPREFER)); + } + } + + #[test] + fn no_layouts() { + let a = M::zeros((5, 5)); + let b = M::zeros((5, 5).f()); + + // 2D row/column matrixes + let arow = a.slice(s![0..1, ..]); + let acol = a.slice(s![.., 0..1]); + let brow = b.slice(s![0..1, ..]); + let bcol = b.slice(s![.., 0..1]); + assert_layouts!(arow, CORDER, FORDER); + assert_not_layouts!(acol, CORDER, CPREFER, FORDER, FPREFER); + assert_layouts!(bcol, CORDER, FORDER); + assert_not_layouts!(brow, CORDER, CPREFER, FORDER, FPREFER); + + // 2D row/column matrixes - now made with insert axis + for &axis in &[Axis(0), Axis(1)] { + let arow = a.slice(s![0, ..]).insert_axis(axis); + let acol = a.slice(s![.., 0]).insert_axis(axis); + let brow = b.slice(s![0, ..]).insert_axis(axis); + let bcol = b.slice(s![.., 0]).insert_axis(axis); + assert_layouts!(arow, CORDER, FORDER); + assert_not_layouts!(acol, CORDER, CPREFER, FORDER, FPREFER); + assert_layouts!(bcol, CORDER, FORDER); + assert_not_layouts!(brow, CORDER, CPREFER, FORDER, FPREFER); } } @@ -177,10 +208,10 @@ mod tests { let v1 = a.slice(s![..;2, ..]).layout(); let v2 = a.slice(s![.., ..;2]).layout(); - assert!(!v1.is(CORDER) && v1.is(CPREFER)); - assert!(!v1.is(FORDER) && !v1.is(FPREFER)); - assert!(!v2.is(CORDER) && !v2.is(CPREFER)); - assert!(!v2.is(FORDER) && !v2.is(FPREFER)); + assert!(!v1.is(Layout::CORDER) && v1.is(Layout::CPREFER)); + assert!(!v1.is(Layout::FORDER) && !v1.is(Layout::FPREFER)); + assert!(!v2.is(Layout::CORDER) && !v2.is(Layout::CPREFER)); + assert!(!v2.is(Layout::FORDER) && !v2.is(Layout::FPREFER)); } let b = M::zeros((5, 5).f()); @@ -188,10 +219,10 @@ mod tests { let v1 = b.slice(s![..;2, ..]).layout(); let v2 = b.slice(s![.., ..;2]).layout(); - assert!(!v1.is(CORDER) && !v1.is(CPREFER)); - assert!(!v1.is(FORDER) && !v1.is(FPREFER)); - assert!(!v2.is(CORDER) && !v2.is(CPREFER)); - assert!(!v2.is(FORDER) && v2.is(FPREFER)); + assert!(!v1.is(Layout::CORDER) && !v1.is(Layout::CPREFER)); + assert!(!v1.is(Layout::FORDER) && !v1.is(Layout::FPREFER)); + assert!(!v2.is(Layout::CORDER) && !v2.is(Layout::CPREFER)); + assert!(!v2.is(Layout::FORDER) && v2.is(Layout::FPREFER)); } } } diff --git a/src/lib.rs b/src/lib.rs index 4dea225ef..b15b0ea88 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,40 +7,48 @@ // except according to those terms. #![crate_name = "ndarray"] #![doc(html_root_url = "https://docs.rs/ndarray/0.15/")] +#![doc(html_logo_url = "https://rust-ndarray.github.io/images/rust-ndarray_logo.svg")] #![allow( + unstable_name_collisions, // our `PointerExt` collides with upcoming inherent methods on `NonNull` clippy::many_single_char_names, clippy::deref_addrof, clippy::unreadable_literal, clippy::manual_map, // is not an error + clippy::while_let_on_iterator, // is not an error + clippy::from_iter_instead_of_collect, // using from_iter is good style + clippy::redundant_closure, // false positives clippy #7812 )] +#![doc(test(attr(deny(warnings))))] +#![doc(test(attr(allow(unused_variables))))] +#![doc(test(attr(allow(deprecated))))] #![cfg_attr(not(feature = "std"), no_std)] //! The `ndarray` crate provides an *n*-dimensional container for general elements //! and for numerics. //! -//! In *n*-dimensional we include for example 1-dimensional rows or columns, +//! In *n*-dimensional we include, for example, 1-dimensional rows or columns, //! 2-dimensional matrices, and higher dimensional arrays. If the array has *n* //! dimensions, then an element in the array is accessed by using that many indices. //! Each dimension is also called an *axis*. //! -//! - **[`ArrayBase`](struct.ArrayBase.html)**: +//! - **[`ArrayBase`]**: //! The *n*-dimensional array type itself.
//! It is used to implement both the owned arrays and the views; see its docs //! for an overview of all array features.
-//! - The main specific array type is **[`Array`](type.Array.html)**, which owns +//! - The main specific array type is **[`Array`]**, which owns //! its elements. //! //! ## Highlights //! //! - Generic *n*-dimensional array -//! - Slicing, also with arbitrary step size, and negative indices to mean -//! elements from the end of the axis. +//! - [Slicing](ArrayBase#slicing), also with arbitrary step size, and negative +//! indices to mean elements from the end of the axis. //! - Views and subviews of arrays; iterators that yield subviews. //! - Higher order operations and arithmetic are performant //! - Array views can be used to slice and mutate any `[T]` data using //! `ArrayView::from` and `ArrayViewMut::from`. -//! - [`Zip`](struct.Zip.html) for lock step function application across two or more arrays or other -//! item producers ([`NdProducer`](trait.NdProducer.html) trait). +//! - [`Zip`] for lock step function application across two or more arrays or other +//! item producers ([`NdProducer`] trait). //! //! ## Crate Status //! @@ -63,43 +71,30 @@ //! needs matching memory layout to be efficient (with some exceptions). //! + Efficient floating point matrix multiplication even for very large //! matrices; can optionally use BLAS to improve it further. -//! - **Requires Rust 1.49 or later** +//! - **Requires Rust 1.57 or later** //! //! ## Crate Feature Flags //! //! The following crate feature flags are available. They are configured in your -//! `Cargo.toml`. +//! `Cargo.toml`. See [`doc::crate_feature_flags`] for more information. //! -//! - `std` -//! - Rust standard library (enabled by default) -//! - This crate can be used without the standard library by disabling the -//! default `std` feature. To do so, use `default-features = false` in -//! your `Cargo.toml`. -//! - The `geomspace` `linspace` `logspace` `range` `std` `var` `var_axis` -//! and `std_axis` methods are only available when `std` is enabled. -//! - `serde` -//! - Enables serialization support for serde 1.x -//! - `rayon` -//! - Enables parallel iterators, parallelized methods, the [`parallel`] module and [`par_azip!`]. -//! - Implies std -//! - `approx` -//! - Enables implementations of traits from the [`approx`] crate. -//! - `blas` -//! - Enable transparent BLAS support for matrix multiplication. -//! Uses ``blas-src`` for pluggable backend, which needs to be configured -//! separately (see the README). -//! - `matrixmultiply-threading` -//! - Enable the ``threading`` feature in the matrixmultiply package +//! - `std`: Rust standard library-using functionality (enabled by default) +//! - `serde`: serialization support for serde 1.x +//! - `rayon`: Parallel iterators, parallelized methods, the [`parallel`] module and [`par_azip!`]. +//! - `approx` Implementations of traits from version 0.4 of the [`approx`] crate. +//! - `approx-0_5`: Implementations of traits from version 0.5 of the [`approx`] crate. +//! - `blas`: transparent BLAS support for matrix multiplication, needs configuration. +//! - `matrixmultiply-threading`: Use threading from `matrixmultiply`. //! //! ## Documentation //! -//! * The docs for [`ArrayBase`](struct.ArrayBase.html) provide an overview of +//! * The docs for [`ArrayBase`] provide an overview of //! the *n*-dimensional array type. Other good pages to look at are the -//! documentation for the [`s![]`](macro.s.html) and -//! [`azip!()`](macro.azip.html) macros. +//! documentation for the [`s![]`](s!) and +//! [`azip!()`](azip!) macros. //! //! * If you have experience with NumPy, you may also be interested in -//! [`ndarray_for_numpy_users`](doc/ndarray_for_numpy_users/index.html). +//! [`ndarray_for_numpy_users`](doc::ndarray_for_numpy_users). //! //! ## The ndarray ecosystem //! @@ -142,12 +137,13 @@ pub use crate::dimension::IxDynImpl; pub use crate::dimension::NdIndex; pub use crate::error::{ErrorKind, ShapeError}; pub use crate::indexes::{indices, indices_of}; +pub use crate::order::Order; pub use crate::slice::{ MultiSliceArg, NewAxis, Slice, SliceArg, SliceInfo, SliceInfoElem, SliceNextDim, }; use crate::iterators::Baseiter; -use crate::iterators::{ElementsBase, ElementsBaseMut, Iter, IterMut, Lanes}; +use crate::iterators::{ElementsBase, ElementsBaseMut, Iter, IterMut}; pub use crate::arraytraits::AsArray; #[cfg(feature = "std")] @@ -159,7 +155,7 @@ pub use crate::stacking::{concatenate, stack, stack_new_axis}; pub use crate::math_cell::MathCell; pub use crate::impl_views::IndexLonger; -pub use crate::shape_builder::{Shape, ShapeBuilder, StrideShape}; +pub use crate::shape_builder::{Shape, ShapeBuilder, ShapeArg, StrideShape}; #[macro_use] mod macro_utils; @@ -196,15 +192,21 @@ mod iterators; mod layout; mod linalg_traits; mod linspace; +#[cfg(feature = "std")] +pub use crate::linspace::{Linspace, linspace, range}; mod logspace; +#[cfg(feature = "std")] +pub use crate::logspace::{Logspace, logspace}; mod math_cell; mod numeric_util; +mod order; mod partial; mod shape_builder; #[macro_use] mod slice; mod split_at; mod stacking; +mod low_level_util; #[macro_use] mod zip; @@ -234,9 +236,13 @@ pub type Ixs = isize; /// An *n*-dimensional array. /// -/// The array is a general container of elements. It cannot grow or shrink, but -/// can be sliced into subsets of its data. -/// The array supports arithmetic operations by applying them elementwise. +/// The array is a general container of elements. +/// The array supports arithmetic operations by applying them elementwise, if the +/// elements are numeric, but it supports non-numeric elements too. +/// +/// The arrays rarely grow or shrink, since those operations can be costly. On +/// the other hand there is a rich set of methods and operations for taking views, +/// slices, and making traversals over one or more arrays. /// /// In *n*-dimensional we include for example 1-dimensional rows or columns, /// 2-dimensional matrices, and higher dimensional arrays. If the array has *n* @@ -247,13 +253,7 @@ pub type Ixs = isize; /// /// Type aliases [`Array`], [`ArcArray`], [`CowArray`], [`ArrayView`], and /// [`ArrayViewMut`] refer to `ArrayBase` with different types for the data -/// container. -/// -/// [`Array`]: type.Array.html -/// [`ArcArray`]: type.ArcArray.html -/// [`ArrayView`]: type.ArrayView.html -/// [`ArrayViewMut`]: type.ArrayViewMut.html -/// [`CowArray`]: type.CowArray.html +/// container: arrays with different kinds of ownership or different kinds of array views. /// /// ## Contents /// @@ -277,7 +277,7 @@ pub type Ixs = isize; /// /// ## `Array` /// -/// [`Array`](type.Array.html) is an owned array that owns the underlying array +/// [`Array`] is an owned array that owns the underlying array /// elements directly (just like a `Vec`) and it is the default way to create and /// store n-dimensional data. `Array` has two type parameters: `A` for /// the element type, and `D` for the dimensionality. A particular @@ -296,17 +296,16 @@ pub type Ixs = isize; /// /// ## `ArcArray` /// -/// [`ArcArray`](type.ArcArray.html) is an owned array with reference counted +/// [`ArcArray`] is an owned array with reference counted /// data (shared ownership). /// Sharing requires that it uses copy-on-write for mutable operations. /// Calling a method for mutating elements on `ArcArray`, for example -/// [`view_mut()`](#method.view_mut) or [`get_mut()`](#method.get_mut), +/// [`view_mut()`](Self::view_mut) or [`get_mut()`](Self::get_mut), /// will break sharing and require a clone of the data (if it is not uniquely held). /// /// ## `CowArray` /// -/// [`CowArray`](type.CowArray.html) is analogous to -/// [`std::borrow::Cow`](https://doc.rust-lang.org/std/borrow/enum.Cow.html). +/// [`CowArray`] is analogous to [`std::borrow::Cow`]. /// It can represent either an immutable view or a uniquely owned array. If a /// `CowArray` instance is the immutable view variant, then calling a method /// for mutating elements in the array will cause it to be converted into the @@ -371,15 +370,15 @@ pub type Ixs = isize; /// /// Important traits and types for dimension and indexing: /// -/// - A [`Dim`](struct.Dim.html) value represents a dimensionality or index. -/// - Trait [`Dimension`](trait.Dimension.html) is implemented by all +/// - A [`struct@Dim`] value represents a dimensionality or index. +/// - Trait [`Dimension`] is implemented by all /// dimensionalities. It defines many operations for dimensions and indices. -/// - Trait [`IntoDimension`](trait.IntoDimension.html) is used to convert into a +/// - Trait [`IntoDimension`] is used to convert into a /// `Dim` value. -/// - Trait [`ShapeBuilder`](trait.ShapeBuilder.html) is an extension of +/// - Trait [`ShapeBuilder`] is an extension of /// `IntoDimension` and is used when constructing an array. A shape describes /// not just the extent of each axis but also their strides. -/// - Trait [`NdIndex`](trait.NdIndex.html) is an extension of `Dimension` and is +/// - Trait [`NdIndex`] is an extension of `Dimension` and is /// for values that can be used with indexing syntax. /// /// @@ -394,10 +393,10 @@ pub type Ixs = isize; /// /// ## Loops, Producers and Iterators /// -/// Using [`Zip`](struct.Zip.html) is the most general way to apply a procedure +/// Using [`Zip`] is the most general way to apply a procedure /// across one or several arrays or *producers*. /// -/// [`NdProducer`](trait.NdProducer.html) is like an iterable but for +/// [`NdProducer`] is like an iterable but for /// multidimensional data. All producers have dimensions and axes, like an /// array view, and they can be split and used with parallelization using `Zip`. /// @@ -469,12 +468,12 @@ pub type Ixs = isize; /// [`.columns()`][gc], [`.columns_mut()`][gcm], /// [`.lanes(axis)`][l], [`.lanes_mut(axis)`][lm]. /// -/// [gr]: #method.rows -/// [grm]: #method.rows_mut -/// [gc]: #method.columns -/// [gcm]: #method.columns_mut -/// [l]: #method.lanes -/// [lm]: #method.lanes_mut +/// [gr]: Self::rows +/// [grm]: Self::rows_mut +/// [gc]: Self::columns +/// [gcm]: Self::columns_mut +/// [l]: Self::lanes +/// [lm]: Self::lanes_mut /// /// Yes, for 2D arrays `.rows()` and `.outer_iter()` have about the same /// effect: @@ -488,7 +487,7 @@ pub type Ixs = isize; /// the array. Slicing methods include [`.slice()`], [`.slice_mut()`], /// [`.slice_move()`], and [`.slice_collapse()`]. /// -/// The slicing argument can be passed using the macro [`s![]`](macro.s!.html), +/// The slicing argument can be passed using the macro [`s![]`](s!), /// which will be used in all examples. (The explicit form is an instance of /// [`SliceInfo`] or another type which implements [`SliceArg`]; see their docs /// for more information.) @@ -500,11 +499,10 @@ pub type Ixs = isize; /// [`.slice_collapse()`] panics on `NewAxis` elements and behaves like /// [`.collapse_axis()`] by preserving the number of dimensions. /// -/// [`.slice()`]: #method.slice -/// [`.slice_mut()`]: #method.slice_mut -/// [`.slice_move()`]: #method.slice_move -/// [`.slice_collapse()`]: #method.slice_collapse -/// [`NewAxis`]: struct.NewAxis.html +/// [`.slice()`]: Self::slice +/// [`.slice_mut()`]: Self::slice_mut +/// [`.slice_move()`]: Self::slice_move +/// [`.slice_collapse()`]: Self::slice_collapse /// /// When slicing arrays with generic dimensionality, creating an instance of /// [`SliceInfo`] to pass to the multi-axis slicing methods like [`.slice()`] @@ -513,18 +511,18 @@ pub type Ixs = isize; /// or to create a view and then slice individual axes of the view using /// methods such as [`.slice_axis_inplace()`] and [`.collapse_axis()`]. /// -/// [`.slice_each_axis()`]: #method.slice_each_axis -/// [`.slice_each_axis_mut()`]: #method.slice_each_axis_mut -/// [`.slice_each_axis_inplace()`]: #method.slice_each_axis_inplace -/// [`.slice_axis_inplace()`]: #method.slice_axis_inplace -/// [`.collapse_axis()`]: #method.collapse_axis +/// [`.slice_each_axis()`]: Self::slice_each_axis +/// [`.slice_each_axis_mut()`]: Self::slice_each_axis_mut +/// [`.slice_each_axis_inplace()`]: Self::slice_each_axis_inplace +/// [`.slice_axis_inplace()`]: Self::slice_axis_inplace +/// [`.collapse_axis()`]: Self::collapse_axis /// /// It's possible to take multiple simultaneous *mutable* slices with /// [`.multi_slice_mut()`] or (for [`ArrayViewMut`] only) /// [`.multi_slice_move()`]. /// -/// [`.multi_slice_mut()`]: #method.multi_slice_mut -/// [`.multi_slice_move()`]: type.ArrayViewMut.html#method.multi_slice_move +/// [`.multi_slice_mut()`]: Self::multi_slice_mut +/// [`.multi_slice_move()`]: ArrayViewMut#method.multi_slice_move /// /// ``` /// use ndarray::{arr2, arr3, s, ArrayBase, DataMut, Dimension, NewAxis, Slice}; @@ -622,16 +620,16 @@ pub type Ixs = isize; /// Methods for selecting an individual subview take two arguments: `axis` and /// `index`. /// -/// [`.axis_iter()`]: #method.axis_iter -/// [`.axis_iter_mut()`]: #method.axis_iter_mut -/// [`.fold_axis()`]: #method.fold_axis -/// [`.index_axis()`]: #method.index_axis -/// [`.index_axis_inplace()`]: #method.index_axis_inplace -/// [`.index_axis_mut()`]: #method.index_axis_mut -/// [`.index_axis_move()`]: #method.index_axis_move -/// [`.collapse_axis()`]: #method.collapse_axis -/// [`.outer_iter()`]: #method.outer_iter -/// [`.outer_iter_mut()`]: #method.outer_iter_mut +/// [`.axis_iter()`]: Self::axis_iter +/// [`.axis_iter_mut()`]: Self::axis_iter_mut +/// [`.fold_axis()`]: Self::fold_axis +/// [`.index_axis()`]: Self::index_axis +/// [`.index_axis_inplace()`]: Self::index_axis_inplace +/// [`.index_axis_mut()`]: Self::index_axis_mut +/// [`.index_axis_move()`]: Self::index_axis_move +/// [`.collapse_axis()`]: Self::collapse_axis +/// [`.outer_iter()`]: Self::outer_iter +/// [`.outer_iter_mut()`]: Self::outer_iter_mut /// /// ``` /// @@ -714,10 +712,10 @@ pub type Ixs = isize; /// /// ### Binary Operators with Array and Scalar /// -/// The trait [`ScalarOperand`](trait.ScalarOperand.html) marks types that can be used in arithmetic +/// The trait [`ScalarOperand`] marks types that can be used in arithmetic /// with arrays directly. For a scalar `K` the following combinations of operands /// are supported (scalar can be on either the left or right side, but -/// `ScalarOperand` docs has the detailed condtions). +/// `ScalarOperand` docs has the detailed conditions). /// /// - `&A @ K` or `K @ &A` which produces a new `Array` /// - `B @ K` or `K @ B` which consumes `B`, updates it with the result and returns it @@ -737,7 +735,7 @@ pub type Ixs = isize; /// Arrays support limited *broadcasting*, where arithmetic operations with /// array operands of different sizes can be carried out by repeating the /// elements of the smaller dimension array. See -/// [`.broadcast()`](#method.broadcast) for a more detailed +/// [`.broadcast()`](Self::broadcast) for a more detailed /// description. /// /// ``` @@ -883,12 +881,12 @@ pub type Ixs = isize; /// ///
/// /// /// /// /// @@ -1032,19 +1030,19 @@ pub type Ixs = isize; /// /// Input | Output | Methods /// ------|--------|-------- -/// `Vec` | `ArrayBase` | [`::from_vec()`](#method.from_vec) -/// `Vec` | `ArrayBase` | [`::from_shape_vec()`](#method.from_shape_vec) -/// `&[A]` | `ArrayView1` | [`::from()`](type.ArrayView.html#method.from) -/// `&[A]` | `ArrayView` | [`::from_shape()`](type.ArrayView.html#method.from_shape) -/// `&mut [A]` | `ArrayViewMut1` | [`::from()`](type.ArrayViewMut.html#method.from) -/// `&mut [A]` | `ArrayViewMut` | [`::from_shape()`](type.ArrayViewMut.html#method.from_shape) -/// `&ArrayBase` | `Vec` | [`.to_vec()`](#method.to_vec) -/// `Array` | `Vec` | [`.into_raw_vec()`](type.Array.html#method.into_raw_vec)[1](#into_raw_vec) -/// `&ArrayBase` | `&[A]` | [`.as_slice()`](#method.as_slice)[2](#req_contig_std), [`.as_slice_memory_order()`](#method.as_slice_memory_order)[3](#req_contig) -/// `&mut ArrayBase` | `&mut [A]` | [`.as_slice_mut()`](#method.as_slice_mut)[2](#req_contig_std), [`.as_slice_memory_order_mut()`](#method.as_slice_memory_order_mut)[3](#req_contig) -/// `ArrayView` | `&[A]` | [`.to_slice()`](type.ArrayView.html#method.to_slice)[2](#req_contig_std) -/// `ArrayViewMut` | `&mut [A]` | [`.into_slice()`](type.ArrayViewMut.html#method.into_slice)[2](#req_contig_std) -/// `Array0` | `A` | [`.into_scalar()`](type.Array.html#method.into_scalar) +/// `Vec` | `ArrayBase` | [`::from_vec()`](Self::from_vec) +/// `Vec` | `ArrayBase` | [`::from_shape_vec()`](Self::from_shape_vec) +/// `&[A]` | `ArrayView1` | [`::from()`](ArrayView#method.from) +/// `&[A]` | `ArrayView` | [`::from_shape()`](ArrayView#method.from_shape) +/// `&mut [A]` | `ArrayViewMut1` | [`::from()`](ArrayViewMut#method.from) +/// `&mut [A]` | `ArrayViewMut` | [`::from_shape()`](ArrayViewMut#method.from_shape) +/// `&ArrayBase` | `Vec` | [`.to_vec()`](Self::to_vec) +/// `Array` | `Vec` | [`.into_raw_vec()`](Array#method.into_raw_vec)[1](#into_raw_vec) +/// `&ArrayBase` | `&[A]` | [`.as_slice()`](Self::as_slice)[2](#req_contig_std), [`.as_slice_memory_order()`](Self::as_slice_memory_order)[3](#req_contig) +/// `&mut ArrayBase` | `&mut [A]` | [`.as_slice_mut()`](Self::as_slice_mut)[2](#req_contig_std), [`.as_slice_memory_order_mut()`](Self::as_slice_memory_order_mut)[3](#req_contig) +/// `ArrayView` | `&[A]` | [`.to_slice()`](ArrayView#method.to_slice)[2](#req_contig_std) +/// `ArrayViewMut` | `&mut [A]` | [`.into_slice()`](ArrayViewMut#method.into_slice)[2](#req_contig_std) +/// `Array0` | `A` | [`.into_scalar()`](Array#method.into_scalar) /// /// 1Returns the data in memory order. /// @@ -1057,16 +1055,16 @@ pub type Ixs = isize; /// conversions to/from `Vec`s/slices. See /// [below](#constructor-methods-for-owned-arrays) for more constructors. /// -/// [ArrayView::reborrow()]: type.ArrayView.html#method.reborrow -/// [ArrayViewMut::reborrow()]: type.ArrayViewMut.html#method.reborrow -/// [.into_dimensionality()]: #method.into_dimensionality -/// [.into_dyn()]: #method.into_dyn -/// [.into_owned()]: #method.into_owned -/// [.into_shared()]: #method.into_shared -/// [.to_owned()]: #method.to_owned -/// [.map()]: #method.map -/// [.view()]: #method.view -/// [.view_mut()]: #method.view_mut +/// [ArrayView::reborrow()]: ArrayView#method.reborrow +/// [ArrayViewMut::reborrow()]: ArrayViewMut#method.reborrow +/// [.into_dimensionality()]: Self::into_dimensionality +/// [.into_dyn()]: Self::into_dyn +/// [.into_owned()]: Self::into_owned +/// [.into_shared()]: Self::into_shared +/// [.to_owned()]: Self::to_owned +/// [.map()]: Self::map +/// [.view()]: Self::view +/// [.view_mut()]: Self::view_mut /// /// ### Conversions from Nested `Vec`s/`Array`s /// @@ -1108,7 +1106,7 @@ pub type Ixs = isize; /// If you don't know ahead-of-time the shape of the final array, then the /// cleanest solution is generally to append the data to a flat `Vec`, and then /// convert it to an `Array` at the end with -/// [`::from_shape_vec()`](#method.from_shape_vec). You just have to be careful +/// [`::from_shape_vec()`](Self::from_shape_vec). You just have to be careful /// that the layout of the data (the order of the elements in the flat `Vec`) /// is correct. /// @@ -1131,10 +1129,9 @@ pub type Ixs = isize; /// /// If neither of these options works for you, and you really need to convert /// nested `Vec`/`Array` instances to an `Array`, the cleanest solution is -/// generally to use -/// [`Iterator::flatten()`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.flatten) +/// generally to use [`Iterator::flatten()`] /// to get a flat `Vec`, and then convert the `Vec` to an `Array` with -/// [`::from_shape_vec()`](#method.from_shape_vec), like this: +/// [`::from_shape_vec()`](Self::from_shape_vec), like this: /// /// ```rust /// use ndarray::{array, Array2, Array3}; @@ -1297,20 +1294,20 @@ where /// It can act as both an owner as the data as well as a shared reference (view /// like). /// Calling a method for mutating elements on `ArcArray`, for example -/// [`view_mut()`](struct.ArrayBase.html#method.view_mut) or -/// [`get_mut()`](struct.ArrayBase.html#method.get_mut), will break sharing and +/// [`view_mut()`](ArrayBase::view_mut) or +/// [`get_mut()`](ArrayBase::get_mut), will break sharing and /// require a clone of the data (if it is not uniquely held). /// /// `ArcArray` uses atomic reference counting like `Arc`, so it is `Send` and /// `Sync` (when allowed by the element type of the array too). /// -/// [**`ArrayBase`**](struct.ArrayBase.html) is used to implement both the owned +/// **[`ArrayBase`]** is used to implement both the owned /// arrays and the views; see its docs for an overview of all array features. /// /// See also: /// -/// + [Constructor Methods for Owned Arrays](struct.ArrayBase.html#constructor-methods-for-owned-arrays) -/// + [Methods For All Array Types](struct.ArrayBase.html#methods-for-all-array-types) +/// + [Constructor Methods for Owned Arrays](ArrayBase#constructor-methods-for-owned-arrays) +/// + [Methods For All Array Types](ArrayBase#methods-for-all-array-types) pub type ArcArray = ArrayBase, D>; /// An array that owns its data uniquely. @@ -1321,18 +1318,18 @@ pub type ArcArray = ArrayBase, D>; /// The `Array` is parameterized by `A` for the element type and `D` for /// the dimensionality. /// -/// [**`ArrayBase`**](struct.ArrayBase.html) is used to implement both the owned +/// **[`ArrayBase`]** is used to implement both the owned /// arrays and the views; see its docs for an overview of all array features. /// /// See also: /// -/// + [Constructor Methods for Owned Arrays](struct.ArrayBase.html#constructor-methods-for-owned-arrays) -/// + [Methods For All Array Types](struct.ArrayBase.html#methods-for-all-array-types) +/// + [Constructor Methods for Owned Arrays](ArrayBase#constructor-methods-for-owned-arrays) +/// + [Methods For All Array Types](ArrayBase#methods-for-all-array-types) /// + Dimensionality-specific type alises -/// [`Array1`](type.Array1.html), -/// [`Array2`](type.Array2.html), -/// [`Array3`](type.Array3.html), ..., -/// [`ArrayD`](type.ArrayD.html), +/// [`Array1`], +/// [`Array2`], +/// [`Array3`], ..., +/// [`ArrayD`], /// and so on. pub type Array = ArrayBase, D>; @@ -1341,20 +1338,17 @@ pub type Array = ArrayBase, D>; /// An `CowArray` represents either a uniquely owned array or a view of an /// array. The `'a` corresponds to the lifetime of the view variant. /// -/// This type is analogous to -/// [`std::borrow::Cow`](https://doc.rust-lang.org/std/borrow/enum.Cow.html). +/// This type is analogous to [`std::borrow::Cow`]. /// If a `CowArray` instance is the immutable view variant, then calling a /// method for mutating elements in the array will cause it to be converted /// into the owned variant (by cloning all the elements) before the /// modification is performed. /// -/// Array views have all the methods of an array (see [`ArrayBase`][ab]). +/// Array views have all the methods of an array (see [`ArrayBase`]). /// -/// See also [`ArcArray`](type.ArcArray.html), which also provides +/// See also [`ArcArray`], which also provides /// copy-on-write behavior but has a reference-counted pointer to the data /// instead of either a view or a uniquely owned copy. -/// -/// [ab]: struct.ArrayBase.html pub type CowArray<'a, A, D> = ArrayBase, D>; /// A read-only array view. @@ -1365,11 +1359,9 @@ pub type CowArray<'a, A, D> = ArrayBase, D>; /// The `ArrayView<'a, A, D>` is parameterized by `'a` for the scope of the /// borrow, `A` for the element type and `D` for the dimensionality. /// -/// Array views have all the methods of an array (see [`ArrayBase`][ab]). -/// -/// See also [`ArrayViewMut`](type.ArrayViewMut.html). +/// Array views have all the methods of an array (see [`ArrayBase`]). /// -/// [ab]: struct.ArrayBase.html +/// See also [`ArrayViewMut`]. pub type ArrayView<'a, A, D> = ArrayBase, D>; /// A read-write array view. @@ -1380,11 +1372,9 @@ pub type ArrayView<'a, A, D> = ArrayBase, D>; /// The `ArrayViewMut<'a, A, D>` is parameterized by `'a` for the scope of the /// borrow, `A` for the element type and `D` for the dimensionality. /// -/// Array views have all the methods of an array (see [`ArrayBase`][ab]). -/// -/// See also [`ArrayView`](type.ArrayView.html). +/// Array views have all the methods of an array (see [`ArrayBase`]). /// -/// [ab]: struct.ArrayBase.html +/// See also [`ArrayView`]. pub type ArrayViewMut<'a, A, D> = ArrayBase, D>; /// A read-only array view without a lifetime. @@ -1396,15 +1386,13 @@ pub type ArrayViewMut<'a, A, D> = ArrayBase, D>; /// T` and `&T`, but `RawArrayView` has additional requirements that `*const T` /// does not, such as non-nullness. /// -/// [`ArrayView`]: type.ArrayView.html -/// /// The `RawArrayView` is parameterized by `A` for the element type and /// `D` for the dimensionality. /// /// Raw array views have all the methods of an array (see -/// [`ArrayBase`](struct.ArrayBase.html)). +/// [`ArrayBase`]). /// -/// See also [`RawArrayViewMut`](type.RawArrayViewMut.html). +/// See also [`RawArrayViewMut`]. /// /// # Warning /// @@ -1421,15 +1409,13 @@ pub type RawArrayView = ArrayBase, D>; /// relationship between `*mut T` and `&mut T`, but `RawArrayViewMut` has /// additional requirements that `*mut T` does not, such as non-nullness. /// -/// [`ArrayViewMut`]: type.ArrayViewMut.html -/// /// The `RawArrayViewMut` is parameterized by `A` for the element type /// and `D` for the dimensionality. /// /// Raw array views have all the methods of an array (see -/// [`ArrayBase`](struct.ArrayBase.html)). +/// [`ArrayBase`]). /// -/// See also [`RawArrayView`](type.RawArrayView.html). +/// See also [`RawArrayView`]. /// /// # Warning /// @@ -1442,7 +1428,7 @@ pub use data_repr::OwnedRepr; /// ArcArray's representation. /// /// *Don’t use this type directly—use the type alias -/// [`ArcArray`](type.ArcArray.html) for the array type!* +/// [`ArcArray`] for the array type!* #[derive(Debug)] pub struct OwnedArcRepr(Arc>); @@ -1455,8 +1441,7 @@ impl Clone for OwnedArcRepr { /// Array pointer’s representation. /// /// *Don’t use this type directly—use the type aliases -/// [`RawArrayView`](type.RawArrayView.html) / -/// [`RawArrayViewMut`](type.RawArrayViewMut.html) for the array type!* +/// [`RawArrayView`] / [`RawArrayViewMut`] for the array type!* #[derive(Copy, Clone)] // This is just a marker type, to carry the mutability and element type. pub struct RawViewRepr { @@ -1465,7 +1450,7 @@ pub struct RawViewRepr { impl RawViewRepr { #[inline(always)] - fn new() -> Self { + const fn new() -> Self { RawViewRepr { ptr: PhantomData } } } @@ -1473,8 +1458,7 @@ impl RawViewRepr { /// Array view’s representation. /// /// *Don’t use this type directly—use the type aliases -/// [`ArrayView`](type.ArrayView.html) -/// / [`ArrayViewMut`](type.ArrayViewMut.html) for the array type!* +/// [`ArrayView`] / [`ArrayViewMut`] for the array type!* #[derive(Copy, Clone)] // This is just a marker type, to carry the lifetime parameter. pub struct ViewRepr { @@ -1483,7 +1467,7 @@ pub struct ViewRepr { impl ViewRepr { #[inline(always)] - fn new() -> Self { + const fn new() -> Self { ViewRepr { life: PhantomData } } } @@ -1491,7 +1475,7 @@ impl ViewRepr { /// CowArray's representation. /// /// *Don't use this type directly—use the type alias -/// [`CowArray`](type.CowArray.html) for the array type!* +/// [`CowArray`] for the array type!* pub enum CowRepr<'a, A> { /// Borrowed data. View(ViewRepr<&'a A>), @@ -1575,10 +1559,6 @@ where unsafe { ArrayView::new(ptr, dim, strides) } } - fn raw_strides(&self) -> D { - self.strides.clone() - } - /// Remove array axis `axis` and return the result. fn try_remove_axis(self, axis: Axis) -> ArrayBase { let d = self.dim.try_remove_axis(axis); @@ -1588,12 +1568,6 @@ where self.with_strides_dim(s, d) } } - - /// n-d generalization of rows, just like inner iter - fn inner_rows(&self) -> iterators::Lanes<'_, A, D::Smaller> { - let n = self.ndim(); - Lanes::new(self.view(), Axis(n.saturating_sub(1))) - } } // parallel methods @@ -1613,7 +1587,7 @@ pub mod linalg; mod impl_ops; pub use crate::impl_ops::ScalarOperand; -#[cfg(feature = "approx")] +#[cfg(any(feature = "approx", feature = "approx-0_5"))] mod array_approx; // Array view methods diff --git a/src/linalg/impl_linalg.rs b/src/linalg/impl_linalg.rs index effa3c261..dc8ef3428 100644 --- a/src/linalg/impl_linalg.rs +++ b/src/linalg/impl_linalg.rs @@ -7,19 +7,27 @@ // except according to those terms. use crate::imp_prelude::*; + +#[cfg(feature = "blas")] +use crate::dimension::offset_from_low_addr_ptr_to_logical_ptr; use crate::numeric_util; use crate::{LinalgScalar, Zip}; use std::any::TypeId; +use std::mem::MaybeUninit; +#[cfg(not(feature = "std"))] use alloc::vec::Vec; +use num_complex::Complex; +use num_complex::{Complex32 as c32, Complex64 as c64}; + +#[cfg(feature = "blas")] +use libc::c_int; #[cfg(feature = "blas")] use std::cmp; #[cfg(feature = "blas")] use std::mem::swap; -#[cfg(feature = "blas")] -use libc::c_int; #[cfg(feature = "blas")] use cblas_sys as blas_sys; @@ -380,7 +388,12 @@ fn mat_mul_impl( // size cutoff for using BLAS let cut = GEMM_BLAS_CUTOFF; let ((mut m, a), (_, mut n)) = (lhs.dim(), rhs.dim()); - if !(m > cut || n > cut || a > cut) || !(same_type::() || same_type::()) { + if !(m > cut || n > cut || a > cut) + || !(same_type::() + || same_type::() + || same_type::() + || same_type::()) + { return mat_mul_general(alpha, lhs, rhs, beta, c); } { @@ -410,8 +423,23 @@ fn mat_mul_impl( rhs_trans = CblasTrans; } + macro_rules! gemm_scalar_cast { + (f32, $var:ident) => { + cast_as(&$var) + }; + (f64, $var:ident) => { + cast_as(&$var) + }; + (c32, $var:ident) => { + &$var as *const A as *const _ + }; + (c64, $var:ident) => { + &$var as *const A as *const _ + }; + } + macro_rules! gemm { - ($ty:ty, $gemm:ident) => { + ($ty:tt, $gemm:ident) => { if blas_row_major_2d::<$ty, _>(&lhs_) && blas_row_major_2d::<$ty, _>(&rhs_) && blas_row_major_2d::<$ty, _>(&c_) @@ -431,7 +459,7 @@ fn mat_mul_impl( let lhs_stride = cmp::max(lhs_.strides()[0] as blas_index, k as blas_index); let rhs_stride = cmp::max(rhs_.strides()[0] as blas_index, n as blas_index); let c_stride = cmp::max(c_.strides()[0] as blas_index, n as blas_index); - + // gemm is C ← αA^Op B^Op + βC // Where Op is notrans/trans/conjtrans unsafe { @@ -439,17 +467,17 @@ fn mat_mul_impl( CblasRowMajor, lhs_trans, rhs_trans, - m as blas_index, // m, rows of Op(a) - n as blas_index, // n, cols of Op(b) - k as blas_index, // k, cols of Op(a) - cast_as(&alpha), // alpha - lhs_.ptr.as_ptr() as *const _, // a - lhs_stride, // lda - rhs_.ptr.as_ptr() as *const _, // b - rhs_stride, // ldb - cast_as(&beta), // beta - c_.ptr.as_ptr() as *mut _, // c - c_stride, // ldc + m as blas_index, // m, rows of Op(a) + n as blas_index, // n, cols of Op(b) + k as blas_index, // k, cols of Op(a) + gemm_scalar_cast!($ty, alpha), // alpha + lhs_.ptr.as_ptr() as *const _, // a + lhs_stride, // lda + rhs_.ptr.as_ptr() as *const _, // b + rhs_stride, // ldb + gemm_scalar_cast!($ty, beta), // beta + c_.ptr.as_ptr() as *mut _, // c + c_stride, // ldc ); } return; @@ -458,6 +486,9 @@ fn mat_mul_impl( } gemm!(f32, cblas_sgemm); gemm!(f64, cblas_dgemm); + + gemm!(c32, cblas_cgemm); + gemm!(c64, cblas_zgemm); } mat_mul_general(alpha, lhs, rhs, beta, c) } @@ -481,7 +512,7 @@ fn mat_mul_general( let (rsc, csc) = (c.strides()[0], c.strides()[1]); if same_type::() { unsafe { - ::matrixmultiply::sgemm( + matrixmultiply::sgemm( m, k, n, @@ -500,7 +531,7 @@ fn mat_mul_general( } } else if same_type::() { unsafe { - ::matrixmultiply::dgemm( + matrixmultiply::dgemm( m, k, n, @@ -517,6 +548,48 @@ fn mat_mul_general( csc, ); } + } else if same_type::() { + unsafe { + matrixmultiply::cgemm( + matrixmultiply::CGemmOption::Standard, + matrixmultiply::CGemmOption::Standard, + m, + k, + n, + complex_array(cast_as(&alpha)), + ap as *const _, + lhs.strides()[0], + lhs.strides()[1], + bp as *const _, + rhs.strides()[0], + rhs.strides()[1], + complex_array(cast_as(&beta)), + cp as *mut _, + rsc, + csc, + ); + } + } else if same_type::() { + unsafe { + matrixmultiply::zgemm( + matrixmultiply::CGemmOption::Standard, + matrixmultiply::CGemmOption::Standard, + m, + k, + n, + complex_array(cast_as(&alpha)), + ap as *const _, + lhs.strides()[0], + lhs.strides()[1], + bp as *const _, + rhs.strides()[0], + rhs.strides()[1], + complex_array(cast_as(&beta)), + cp as *mut _, + rsc, + csc, + ); + } } else { // It's a no-op if `c` has zero length. if c.is_empty() { @@ -608,14 +681,12 @@ pub fn general_mat_vec_mul( S3: DataMut, A: LinalgScalar, { - unsafe { - general_mat_vec_mul_impl(alpha, a, x, beta, y.raw_view_mut()) - } + unsafe { general_mat_vec_mul_impl(alpha, a, x, beta, y.raw_view_mut()) } } /// General matrix-vector multiplication /// -/// Use a raw view for the destination vector, so that it can be uninitalized. +/// Use a raw view for the destination vector, so that it can be uninitialized. /// /// ## Safety /// @@ -657,6 +728,12 @@ unsafe fn general_mat_vec_mul_impl( } }; + // Low addr in memory pointers required for x, y + let x_offset = offset_from_low_addr_ptr_to_logical_ptr(&x.dim, &x.strides); + let x_ptr = x.ptr.as_ptr().sub(x_offset); + let y_offset = offset_from_low_addr_ptr_to_logical_ptr(&y.dim, &y.strides); + let y_ptr = y.ptr.as_ptr().sub(y_offset); + let x_stride = x.strides()[0] as blas_index; let y_stride = y.strides()[0] as blas_index; @@ -668,10 +745,10 @@ unsafe fn general_mat_vec_mul_impl( cast_as(&alpha), // alpha a.ptr.as_ptr() as *const _, // a a_stride, // lda - x.ptr.as_ptr() as *const _, // x + x_ptr as *const _, // x x_stride, - cast_as(&beta), // beta - y.ptr.as_ptr() as *mut _, // x + cast_as(&beta), // beta + y_ptr as *mut _, // y y_stride, ); return; @@ -699,6 +776,39 @@ unsafe fn general_mat_vec_mul_impl( } } + +/// Kronecker product of 2D matrices. +/// +/// The kronecker product of a LxN matrix A and a MxR matrix B is a (L*M)x(N*R) +/// matrix K formed by the block multiplication A_ij * B. +pub fn kron(a: &ArrayBase, b: &ArrayBase) -> Array +where + S1: Data, + S2: Data, + A: LinalgScalar, +{ + let dimar = a.shape()[0]; + let dimac = a.shape()[1]; + let dimbr = b.shape()[0]; + let dimbc = b.shape()[1]; + let mut out: Array2> = Array2::uninit(( + dimar + .checked_mul(dimbr) + .expect("Dimensions of kronecker product output array overflows usize."), + dimac + .checked_mul(dimbc) + .expect("Dimensions of kronecker product output array overflows usize."), + )); + Zip::from(out.exact_chunks_mut((dimbr, dimbc))) + .and(a) + .for_each(|out, &a| { + Zip::from(out).and(b).for_each(|out, &b| { + *out = MaybeUninit::new(a * b); + }) + }); + unsafe { out.assume_init() } +} + #[inline(always)] /// Return `true` if `A` and `B` are the same type fn same_type() -> bool { @@ -709,10 +819,17 @@ fn same_type() -> bool { // // **Panics** if `A` and `B` are not the same type fn cast_as(a: &A) -> B { - assert!(same_type::()); + assert!(same_type::(), "expect type {} and {} to match", + std::any::type_name::(), std::any::type_name::()); unsafe { ::std::ptr::read(a as *const _ as *const B) } } +/// Return the complex in the form of an array [re, im] +#[inline] +fn complex_array(z: Complex) -> [A; 2] { + [z.re, z.im] +} + #[cfg(feature = "blas")] fn blas_compat_1d(a: &ArrayBase) -> bool where @@ -727,7 +844,10 @@ where return false; } let stride = a.strides()[0]; - if stride > blas_index::max_value() as isize || stride < blas_index::min_value() as isize { + if stride == 0 + || stride > blas_index::max_value() as isize + || stride < blas_index::min_value() as isize + { return false; } true diff --git a/src/linalg/mod.rs b/src/linalg/mod.rs index 8575905cd..abd7b2b9d 100644 --- a/src/linalg/mod.rs +++ b/src/linalg/mod.rs @@ -11,5 +11,6 @@ pub use self::impl_linalg::general_mat_mul; pub use self::impl_linalg::general_mat_vec_mul; pub use self::impl_linalg::Dot; +pub use self::impl_linalg::kron; mod impl_linalg; diff --git a/src/linspace.rs b/src/linspace.rs index 6e9b1203c..513044e00 100644 --- a/src/linspace.rs +++ b/src/linspace.rs @@ -66,8 +66,8 @@ impl ExactSizeIterator for Linspace where Linspace: Iterator {} /// /// The `Linspace` has `n` elements from `a` to `b` (inclusive). /// -/// The iterator element type is `F`, where `F` must implement `Float`, e.g. -/// `f32` or `f64`. +/// The iterator element type is `F`, where `F` must implement [`Float`], e.g. +/// [`f32`] or [`f64`]. /// /// **Panics** if converting `n - 1` to type `F` fails. #[inline] @@ -89,13 +89,13 @@ where } } -/// Return an iterator of floats from `start` to `end` (exclusive), +/// Return an iterator of floats from `a` to `b` (exclusive), /// incrementing by `step`. /// /// Numerical reasons can result in `b` being included in the result. /// -/// The iterator element type is `F`, where `F` must implement `Float`, e.g. -/// `f32` or `f64`. +/// The iterator element type is `F`, where `F` must implement [`Float`], e.g. +/// [`f32`] or [`f64`]. /// /// **Panics** if converting `((b - a) / step).ceil()` to type `F` fails. #[inline] diff --git a/src/logspace.rs b/src/logspace.rs index 4dc6e1f32..53be034b5 100644 --- a/src/logspace.rs +++ b/src/logspace.rs @@ -68,12 +68,12 @@ impl ExactSizeIterator for Logspace where Logspace: Iterator {} /// An iterator of a sequence of logarithmically spaced numbers. /// -/// The `Logspace` has `n` elements, where the first element is `base.powf(a)` +/// The [`Logspace`] has `n` elements, where the first element is `base.powf(a)` /// and the last element is `base.powf(b)`. If `base` is negative, this /// iterator will return all negative values. /// -/// The iterator element type is `F`, where `F` must implement `Float`, e.g. -/// `f32` or `f64`. +/// The iterator element type is `F`, where `F` must implement [`Float`], e.g. +/// [`f32`] or [`f64`]. /// /// **Panics** if converting `n - 1` to type `F` fails. #[inline] diff --git a/src/low_level_util.rs b/src/low_level_util.rs new file mode 100644 index 000000000..b61b06f0d --- /dev/null +++ b/src/low_level_util.rs @@ -0,0 +1,40 @@ +// Copyright 2021 bluss and ndarray developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + + +/// Guard value that will abort if it is dropped. +/// To defuse, this value must be forgotten before the end of the scope. +/// +/// The string value is added to the message printed if aborting. +#[must_use] +pub(crate) struct AbortIfPanic(pub(crate) &'static &'static str); + +impl AbortIfPanic { + /// Defuse the AbortIfPanic guard. This *must* be done when finished. + #[inline] + pub(crate) fn defuse(self) { + std::mem::forget(self); + } +} + +impl Drop for AbortIfPanic { + // The compiler should be able to remove this, if it can see through that there + // is no panic in the code section. + fn drop(&mut self) { + #[cfg(feature="std")] + { + eprintln!("ndarray: panic in no-panic section, aborting: {}", self.0); + std::process::abort() + } + #[cfg(not(feature="std"))] + { + // no-std uses panic-in-panic (should abort) + panic!("ndarray: panic in no-panic section, bailing out: {}", self.0); + } + } +} diff --git a/src/numeric/impl_float_maths.rs b/src/numeric/impl_float_maths.rs new file mode 100644 index 000000000..4b3208800 --- /dev/null +++ b/src/numeric/impl_float_maths.rs @@ -0,0 +1,168 @@ +// Element-wise methods for ndarray + +#[cfg(feature = "std")] +use num_traits::Float; + +use crate::imp_prelude::*; + +#[cfg(feature = "std")] +macro_rules! boolean_ops { + ($(#[$meta1:meta])* fn $func:ident + $(#[$meta2:meta])* fn $all:ident + $(#[$meta3:meta])* fn $any:ident) => { + $(#[$meta1])* + #[must_use = "method returns a new array and does not mutate the original value"] + pub fn $func(&self) -> Array { + self.mapv(A::$func) + } + $(#[$meta2])* + #[must_use = "method returns a new boolean value and does not mutate the original value"] + pub fn $all(&self) -> bool { + $crate::Zip::from(self).all(|&elt| !elt.$func()) + } + $(#[$meta3])* + #[must_use = "method returns a new boolean value and does not mutate the original value"] + pub fn $any(&self) -> bool { + !self.$all() + } + }; +} + +#[cfg(feature = "std")] +macro_rules! unary_ops { + ($($(#[$meta:meta])* fn $id:ident)+) => { + $($(#[$meta])* + #[must_use = "method returns a new array and does not mutate the original value"] + pub fn $id(&self) -> Array { + self.mapv(A::$id) + })+ + }; +} + +#[cfg(feature = "std")] +macro_rules! binary_ops { + ($($(#[$meta:meta])* fn $id:ident($ty:ty))+) => { + $($(#[$meta])* + #[must_use = "method returns a new array and does not mutate the original value"] + pub fn $id(&self, rhs: $ty) -> Array { + self.mapv(|v| A::$id(v, rhs)) + })+ + }; +} + +/// # Element-wise methods for float arrays +/// +/// Element-wise math functions for any array type that contains float number. +#[cfg(feature = "std")] +impl ArrayBase +where + A: 'static + Float, + S: Data, + D: Dimension, +{ + boolean_ops! { + /// If the number is `NaN` (not a number), then `true` is returned for each element. + fn is_nan + /// Return `true` if all elements are `NaN` (not a number). + fn is_all_nan + /// Return `true` if any element is `NaN` (not a number). + fn is_any_nan + } + boolean_ops! { + /// If the number is infinity, then `true` is returned for each element. + fn is_infinite + /// Return `true` if all elements are infinity. + fn is_all_infinite + /// Return `true` if any element is infinity. + fn is_any_infinite + } + unary_ops! { + /// The largest integer less than or equal to each element. + fn floor + /// The smallest integer less than or equal to each element. + fn ceil + /// The nearest integer of each element. + fn round + /// The integer part of each element. + fn trunc + /// The fractional part of each element. + fn fract + /// Absolute of each element. + fn abs + /// Sign number of each element. + /// + /// + `1.0` for all positive numbers. + /// + `-1.0` for all negative numbers. + /// + `NaN` for all `NaN` (not a number). + fn signum + /// The reciprocal (inverse) of each element, `1/x`. + fn recip + /// Square root of each element. + fn sqrt + /// `e^x` of each element (exponential function). + fn exp + /// `2^x` of each element. + fn exp2 + /// Natural logarithm of each element. + fn ln + /// Base 2 logarithm of each element. + fn log2 + /// Base 10 logarithm of each element. + fn log10 + /// Cubic root of each element. + fn cbrt + /// Sine of each element (in radians). + fn sin + /// Cosine of each element (in radians). + fn cos + /// Tangent of each element (in radians). + fn tan + /// Converts radians to degrees for each element. + fn to_degrees + /// Converts degrees to radians for each element. + fn to_radians + } + binary_ops! { + /// Integer power of each element. + /// + /// This function is generally faster than using float power. + fn powi(i32) + /// Float power of each element. + fn powf(A) + /// Logarithm of each element with respect to an arbitrary base. + fn log(A) + /// The positive difference between given number and each element. + fn abs_sub(A) + } + + /// Square (two powers) of each element. + #[must_use = "method returns a new array and does not mutate the original value"] + pub fn pow2(&self) -> Array { + self.mapv(|v: A| v * v) + } +} + +impl ArrayBase +where + A: 'static + PartialOrd + Clone, + S: Data, + D: Dimension, +{ + /// Limit the values for each element, similar to NumPy's `clip` function. + /// + /// ``` + /// use ndarray::array; + /// + /// let a = array![0., 1., 2., 3., 4., 5., 6., 7., 8., 9.]; + /// assert_eq!(a.clamp(1., 8.), array![1., 1., 2., 3., 4., 5., 6., 7., 8., 8.]); + /// assert_eq!(a.clamp(3., 6.), array![3., 3., 3., 3., 4., 5., 6., 6., 6., 6.]); + /// ``` + /// + /// # Panics + /// + /// Panics if `!(min <= max)`. + pub fn clamp(&self, min: A, max: A) -> Array { + assert!(min <= max, "min must be less than or equal to max"); + self.mapv(|a| num_traits::clamp(a, min.clone(), max.clone())) + } +} diff --git a/src/numeric/impl_numeric.rs b/src/numeric/impl_numeric.rs index a8730d9e0..55cd0cdfe 100644 --- a/src/numeric/impl_numeric.rs +++ b/src/numeric/impl_numeric.rs @@ -8,11 +8,10 @@ #[cfg(feature = "std")] use num_traits::Float; -use num_traits::{self, FromPrimitive, Zero}; +use num_traits::{FromPrimitive, Zero}; use std::ops::{Add, Div, Mul}; use crate::imp_prelude::*; -use crate::itertools::enumerate; use crate::numeric_util; /// # Numerical Methods for Arrays @@ -38,7 +37,7 @@ where return numeric_util::unrolled_fold(slc, A::zero, A::add); } let mut sum = A::zero(); - for row in self.inner_rows() { + for row in self.rows() { if let Some(slc) = row.as_slice() { sum = sum + numeric_util::unrolled_fold(slc, A::zero, A::add); } else { @@ -103,7 +102,7 @@ where return numeric_util::unrolled_fold(slc, A::one, A::mul); } let mut sum = A::one(); - for row in self.inner_rows() { + for row in self.rows() { if let Some(slc) = row.as_slice() { sum = sum * numeric_util::unrolled_fold(slc, A::one, A::mul); } else { @@ -249,22 +248,16 @@ where A: Clone + Zero + Add, D: RemoveAxis, { - let n = self.len_of(axis); - let mut res = Array::zeros(self.raw_dim().remove_axis(axis)); - let stride = self.strides()[axis.index()]; - if self.ndim() == 2 && stride == 1 { - // contiguous along the axis we are summing - let ax = axis.index(); - for (i, elt) in enumerate(&mut res) { - *elt = self.index_axis(Axis(1 - ax), i).sum(); - } + let min_stride_axis = self.dim.min_stride_axis(&self.strides); + if axis == min_stride_axis { + crate::Zip::from(self.lanes(axis)).map_collect(|lane| lane.sum()) } else { - for i in 0..n { - let view = self.index_axis(axis, i); - res = res + &view; + let mut res = Array::zeros(self.raw_dim().remove_axis(axis)); + for subview in self.axis_iter(axis) { + res = res + &subview; } + res } - res } /// Return mean along `axis`. diff --git a/src/numeric/mod.rs b/src/numeric/mod.rs index b3da06746..c0a7228c5 100644 --- a/src/numeric/mod.rs +++ b/src/numeric/mod.rs @@ -1 +1,3 @@ mod impl_numeric; + +mod impl_float_maths; diff --git a/src/order.rs b/src/order.rs new file mode 100644 index 000000000..e8d9c8db1 --- /dev/null +++ b/src/order.rs @@ -0,0 +1,83 @@ + +/// Array order +/// +/// Order refers to indexing order, or how a linear sequence is translated +/// into a two-dimensional or multi-dimensional array. +/// +/// - `RowMajor` means that the index along the row is the most rapidly changing +/// - `ColumnMajor` means that the index along the column is the most rapidly changing +/// +/// Given a sequence like: 1, 2, 3, 4, 5, 6 +/// +/// If it is laid it out in a 2 x 3 matrix using row major ordering, it results in: +/// +/// ```text +/// 1 2 3 +/// 4 5 6 +/// ``` +/// +/// If it is laid using column major ordering, it results in: +/// +/// ```text +/// 1 3 5 +/// 2 4 6 +/// ``` +/// +/// It can be seen as filling in "rows first" or "columns first". +/// +/// `Order` can be used both to refer to logical ordering as well as memory ordering or memory +/// layout. The orderings have common short names, also seen in other environments, where +/// row major is called "C" order (after the C programming language) and column major is called "F" +/// or "Fortran" order. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum Order { + /// Row major or "C" order + RowMajor, + /// Column major or "F" order + ColumnMajor, +} + +impl Order { + /// "C" is an alias for row major ordering + pub const C: Order = Order::RowMajor; + + /// "F" (for Fortran) is an alias for column major ordering + pub const F: Order = Order::ColumnMajor; + + /// Return true if input is Order::RowMajor, false otherwise + #[inline] + pub fn is_row_major(self) -> bool { + match self { + Order::RowMajor => true, + Order::ColumnMajor => false, + } + } + + /// Return true if input is Order::ColumnMajor, false otherwise + #[inline] + pub fn is_column_major(self) -> bool { + !self.is_row_major() + } + + /// Return Order::RowMajor if the input is true, Order::ColumnMajor otherwise + #[inline] + pub fn row_major(row_major: bool) -> Order { + if row_major { Order::RowMajor } else { Order::ColumnMajor } + } + + /// Return Order::ColumnMajor if the input is true, Order::RowMajor otherwise + #[inline] + pub fn column_major(column_major: bool) -> Order { + Self::row_major(!column_major) + } + + /// Return the transpose: row major becomes column major and vice versa. + #[inline] + pub fn transpose(self) -> Order { + match self { + Order::RowMajor => Order::ColumnMajor, + Order::ColumnMajor => Order::RowMajor, + } + } +} diff --git a/src/parallel/impl_par_methods.rs b/src/parallel/impl_par_methods.rs index 33d393345..ed0dcad7a 100644 --- a/src/parallel/impl_par_methods.rs +++ b/src/parallel/impl_par_methods.rs @@ -94,7 +94,7 @@ macro_rules! zip_impl { -> Array where R: Send { - let mut output = self.uninitalized_for_current_layout::(); + let mut output = self.uninitialized_for_current_layout::(); let total_len = output.len(); // Create a parallel iterator that produces chunks of the zip with the output @@ -177,6 +177,56 @@ macro_rules! zip_impl { self.par_map_assign_into(into, f) } + /// Parallel version of `fold`. + /// + /// Splits the producer in multiple tasks which each accumulate a single value + /// using the `fold` closure. Those tasks are executed in parallel and their results + /// are then combined to a single value using the `reduce` closure. + /// + /// The `identity` closure provides the initial values for each of the tasks and + /// for the final reduction. + /// + /// This is a shorthand for calling `self.into_par_iter().fold(...).reduce(...)`. + /// + /// Note that it is often more efficient to parallelize not per-element but rather + /// based on larger chunks of an array like generalized rows and operating on each chunk + /// using a sequential variant of the accumulation. + /// For example, sum each row sequentially and in parallel, taking advantage of locality + /// and vectorization within each task, and then reduce their sums to the sum of the matrix. + /// + /// Also note that the splitting of the producer into multiple tasks is _not_ deterministic + /// which needs to be considered when the accuracy of such an operation is analyzed. + /// + /// ## Examples + /// + /// ```rust + /// use ndarray::{Array, Zip}; + /// + /// let a = Array::::ones((128, 1024)); + /// let b = Array::::ones(128); + /// + /// let weighted_sum = Zip::from(a.rows()).and(&b).par_fold( + /// || 0, + /// |sum, row, factor| sum + row.sum() * factor, + /// |sum, other_sum| sum + other_sum, + /// ); + /// + /// assert_eq!(weighted_sum, a.len()); + /// ``` + pub fn par_fold(self, identity: ID, fold: F, reduce: R) -> T + where + ID: Fn() -> T + Send + Sync + Clone, + F: Fn(T, $($p::Item),*) -> T + Send + Sync, + R: Fn(T, T) -> T + Send + Sync, + T: Send + { + self.into_par_iter() + .fold(identity.clone(), move |accumulator, ($($p,)*)| { + fold(accumulator, $($p),*) + }) + .reduce(identity, reduce) + } + ); } )+ diff --git a/src/parallel/mod.rs b/src/parallel/mod.rs index dd3b89341..552515f11 100644 --- a/src/parallel/mod.rs +++ b/src/parallel/mod.rs @@ -32,6 +32,10 @@ //! “unindexed”. Use ndarray’s [Zip] for lock step parallel iteration of //! multiple arrays or producers at a time. //! +//! For the unindexed parallel iterators, an inherent method [`with_min_len`](Parallel::with_min_len) +//! is provided to limit the number of elements each parallel task processes in way that is +//! similar to Rayon's [`IndexedParallelIterator::with_min_len`](rayon::prelude::IndexedParallelIterator::with_min_len). +//! //! # Examples //! //! ## Arrays and array views @@ -42,16 +46,14 @@ //! use ndarray::Array2; //! use ndarray::parallel::prelude::*; //! -//! fn main() { -//! let mut a = Array2::::zeros((128, 128)); +//! let mut a = Array2::::zeros((128, 128)); //! -//! // Parallel versions of regular array methods -//! a.par_map_inplace(|x| *x = x.exp()); -//! a.par_mapv_inplace(f64::exp); +//! // Parallel versions of regular array methods +//! a.par_map_inplace(|x| *x = x.exp()); +//! a.par_mapv_inplace(f64::exp); //! -//! // You can also use the parallel iterator directly -//! a.par_iter_mut().for_each(|x| *x = x.exp()); -//! } +//! // You can also use the parallel iterator directly +//! a.par_iter_mut().for_each(|x| *x = x.exp()); //! ``` //! //! ## Axis iterators @@ -63,16 +65,14 @@ //! use ndarray::Axis; //! use ndarray::parallel::prelude::*; //! -//! fn main() { -//! let a = Array::linspace(0., 63., 64).into_shape((4, 16)).unwrap(); -//! let mut sums = Vec::new(); -//! a.axis_iter(Axis(0)) -//! .into_par_iter() -//! .map(|row| row.sum()) -//! .collect_into_vec(&mut sums); +//! let a = Array::linspace(0., 63., 64).into_shape_with_order((4, 16)).unwrap(); +//! let mut sums = Vec::new(); +//! a.axis_iter(Axis(0)) +//! .into_par_iter() +//! .map(|row| row.sum()) +//! .collect_into_vec(&mut sums); //! -//! assert_eq!(sums, [120., 376., 632., 888.]); -//! } +//! assert_eq!(sums, [120., 376., 632., 888.]); //! ``` //! //! ## Axis chunks iterators @@ -84,16 +84,14 @@ //! use ndarray::Axis; //! use ndarray::parallel::prelude::*; //! -//! fn main() { -//! let a = Array::linspace(0., 63., 64).into_shape((4, 16)).unwrap(); -//! let mut shapes = Vec::new(); -//! a.axis_chunks_iter(Axis(0), 3) -//! .into_par_iter() -//! .map(|chunk| chunk.shape().to_owned()) -//! .collect_into_vec(&mut shapes); +//! let a = Array::linspace(0., 63., 64).into_shape_with_order((4, 16)).unwrap(); +//! let mut shapes = Vec::new(); +//! a.axis_chunks_iter(Axis(0), 3) +//! .into_par_iter() +//! .map(|chunk| chunk.shape().to_owned()) +//! .collect_into_vec(&mut shapes); //! -//! assert_eq!(shapes, [vec![3, 16], vec![1, 16]]); -//! } +//! assert_eq!(shapes, [vec![3, 16], vec![1, 16]]); //! ``` //! //! ## Zip @@ -106,19 +104,17 @@ //! //! type Array3f64 = Array3; //! -//! fn main() { -//! const N: usize = 128; -//! let a = Array3f64::from_elem((N, N, N), 1.); -//! let b = Array3f64::from_elem(a.dim(), 2.); -//! let mut c = Array3f64::zeros(a.dim()); -//! -//! Zip::from(&mut c) -//! .and(&a) -//! .and(&b) -//! .par_for_each(|c, &a, &b| { -//! *c += a - b; -//! }); -//! } +//! const N: usize = 128; +//! let a = Array3f64::from_elem((N, N, N), 1.); +//! let b = Array3f64::from_elem(a.dim(), 2.); +//! let mut c = Array3f64::zeros(a.dim()); +//! +//! Zip::from(&mut c) +//! .and(&a) +//! .and(&b) +//! .par_for_each(|c, &a, &b| { +//! *c += a - b; +//! }); //! ``` #[allow(unused_imports)] // used by rustdoc links diff --git a/src/parallel/par.rs b/src/parallel/par.rs index d9d592af6..cc905b5cf 100644 --- a/src/parallel/par.rs +++ b/src/parallel/par.rs @@ -21,11 +21,14 @@ use crate::split_at::SplitPreference; #[derive(Copy, Clone, Debug)] pub struct Parallel { iter: I, + min_len: usize, } +const DEFAULT_MIN_LEN: usize = 1; + /// Parallel producer wrapper. #[derive(Copy, Clone, Debug)] -struct ParallelProducer(I); +struct ParallelProducer(I, usize); macro_rules! par_iter_wrapper { // thread_bounds are either Sync or Send + Sync @@ -40,6 +43,7 @@ macro_rules! par_iter_wrapper { fn into_par_iter(self) -> Self::Iter { Parallel { iter: self, + min_len: DEFAULT_MIN_LEN, } } } @@ -67,7 +71,7 @@ macro_rules! par_iter_wrapper { fn with_producer(self, callback: Cb) -> Cb::Output where Cb: ProducerCallback { - callback.callback(ParallelProducer(self.iter)) + callback.callback(ParallelProducer(self.iter, self.min_len)) } fn len(&self) -> usize { @@ -106,7 +110,7 @@ macro_rules! par_iter_wrapper { fn split_at(self, i: usize) -> (Self, Self) { let (a, b) = self.0.split_at(i); - (ParallelProducer(a), ParallelProducer(b)) + (ParallelProducer(a, self.1), ParallelProducer(b, self.1)) } } @@ -131,11 +135,11 @@ macro_rules! par_iter_view_wrapper { fn into_par_iter(self) -> Self::Iter { Parallel { iter: self, + min_len: DEFAULT_MIN_LEN, } } } - impl<'a, A, D> ParallelIterator for Parallel<$view_name<'a, A, D>> where D: Dimension, A: $($thread_bounds)*, @@ -144,7 +148,7 @@ macro_rules! par_iter_view_wrapper { fn drive_unindexed(self, consumer: C) -> C::Result where C: UnindexedConsumer { - bridge_unindexed(ParallelProducer(self.iter), consumer) + bridge_unindexed(ParallelProducer(self.iter, self.min_len), consumer) } fn opt_len(&self) -> Option { @@ -152,20 +156,39 @@ macro_rules! par_iter_view_wrapper { } } + impl<'a, A, D> Parallel<$view_name<'a, A, D>> + where D: Dimension, + A: $($thread_bounds)*, + { + /// Sets the minimum number of elements desired to process in each job. This will not be + /// split any smaller than this length, but of course a producer could already be smaller + /// to begin with. + /// + /// ***Panics*** if `min_len` is zero. + pub fn with_min_len(self, min_len: usize) -> Self { + assert_ne!(min_len, 0, "Minimum number of elements must at least be one to avoid splitting off empty tasks."); + + Self { + min_len, + ..self + } + } + } + impl<'a, A, D> UnindexedProducer for ParallelProducer<$view_name<'a, A, D>> where D: Dimension, A: $($thread_bounds)*, { type Item = <$view_name<'a, A, D> as IntoIterator>::Item; fn split(self) -> (Self, Option) { - if self.0.len() <= 1 { + if self.0.len() <= self.1 { return (self, None) } let array = self.0; let max_axis = array.max_stride_axis(); let mid = array.len_of(max_axis) / 2; let (a, b) = array.split_at(max_axis, mid); - (ParallelProducer(a), Some(ParallelProducer(b))) + (ParallelProducer(a, self.1), Some(ParallelProducer(b, self.1))) } fn fold_with(self, folder: F) -> F @@ -217,6 +240,7 @@ macro_rules! zip_impl { fn into_par_iter(self) -> Self::Iter { Parallel { iter: self, + min_len: DEFAULT_MIN_LEN, } } } @@ -233,7 +257,7 @@ macro_rules! zip_impl { fn drive_unindexed(self, consumer: Cons) -> Cons::Result where Cons: UnindexedConsumer { - bridge_unindexed(ParallelProducer(self.iter), consumer) + bridge_unindexed(ParallelProducer(self.iter, self.min_len), consumer) } fn opt_len(&self) -> Option { @@ -251,11 +275,11 @@ macro_rules! zip_impl { type Item = ($($p::Item ,)*); fn split(self) -> (Self, Option) { - if !self.0.can_split() { + if self.0.size() <= self.1 { return (self, None) } let (a, b) = self.0.split(); - (ParallelProducer(a), Some(ParallelProducer(b))) + (ParallelProducer(a, self.1), Some(ParallelProducer(b, self.1))) } fn fold_with(self, folder: Fold) -> Fold @@ -284,6 +308,25 @@ zip_impl! { [P1 P2 P3 P4 P5 P6], } +impl Parallel> +where + D: Dimension, +{ + /// Sets the minimum number of elements desired to process in each job. This will not be + /// split any smaller than this length, but of course a producer could already be smaller + /// to begin with. + /// + /// ***Panics*** if `min_len` is zero. + pub fn with_min_len(self, min_len: usize) -> Self { + assert_ne!(min_len, 0, "Minimum number of elements must at least be one to avoid splitting off empty tasks."); + + Self { + min_len, + ..self + } + } +} + /// A parallel iterator (unindexed) that produces the splits of the array /// or producer `P`. pub(crate) struct ParallelSplits

{ diff --git a/src/parallel/zipmacro.rs b/src/parallel/zipmacro.rs index 95111c933..28188542f 100644 --- a/src/parallel/zipmacro.rs +++ b/src/parallel/zipmacro.rs @@ -39,18 +39,16 @@ /// /// type M = Array2; /// -/// fn main() { -/// let mut a = M::zeros((16, 16)); -/// let b = M::from_elem(a.dim(), 1.); -/// let c = M::from_elem(a.dim(), 2.); +/// let mut a = M::zeros((16, 16)); +/// let b = M::from_elem(a.dim(), 1.); +/// let c = M::from_elem(a.dim(), 2.); /// -/// // Compute a simple ternary operation: -/// // elementwise addition of b and c, stored in a +/// // Compute a simple ternary operation: +/// // elementwise addition of b and c, stored in a /// -/// par_azip!((a in &mut a, &b in &b, &c in &c) *a = b + c); +/// par_azip!((a in &mut a, &b in &b, &c in &c) *a = b + c); /// -/// assert_eq!(a, &b + &c); -/// } +/// assert_eq!(a, &b + &c); /// ``` macro_rules! par_azip { ($($t:tt)*) => { diff --git a/src/partial.rs b/src/partial.rs index a8146aff0..a835081c4 100644 --- a/src/partial.rs +++ b/src/partial.rs @@ -38,7 +38,7 @@ impl Partial { #[cfg(feature = "rayon")] pub(crate) fn stub() -> Self { - Self { len: 0, ptr: 0 as *mut _ } + Self { len: 0, ptr: ptr::null_mut() } } #[cfg(feature = "rayon")] diff --git a/src/prelude.rs b/src/prelude.rs index ea6dfb08f..a25fc8780 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -12,8 +12,9 @@ //! and macros that you can import easily as a group. //! //! ``` -//! //! use ndarray::prelude::*; +//! +//! # let _ = arr0(1); // use the import //! ``` #[doc(no_inline)] diff --git a/src/private.rs b/src/private.rs index 552b31497..1fa779ff0 100644 --- a/src/private.rs +++ b/src/private.rs @@ -11,13 +11,13 @@ macro_rules! private_decl { () => { /// This trait is private to implement; this method exists to make it /// impossible to implement outside the crate. + #[doc(hidden)] fn __private__(&self) -> crate::private::PrivateMarker; } } macro_rules! private_impl { () => { - #[doc(hidden)] fn __private__(&self) -> crate::private::PrivateMarker { crate::private::PrivateMarker } diff --git a/src/shape_builder.rs b/src/shape_builder.rs index 6fc99d0b2..877102b5c 100644 --- a/src/shape_builder.rs +++ b/src/shape_builder.rs @@ -1,5 +1,6 @@ use crate::dimension::IntoDimension; use crate::Dimension; +use crate::order::Order; /// A contiguous array shape of n dimensions. /// @@ -13,7 +14,7 @@ pub struct Shape { } #[derive(Copy, Clone, Debug)] -pub(crate) enum Contiguous { } +pub(crate) enum Contiguous {} impl Shape { pub(crate) fn is_c(&self) -> bool { @@ -21,7 +22,6 @@ impl Shape { } } - /// An array shape of n dimensions in c-order, f-order or custom strides. #[derive(Copy, Clone, Debug)] pub struct StrideShape { @@ -29,6 +29,20 @@ pub struct StrideShape { pub(crate) strides: Strides, } +impl StrideShape +where + D: Dimension, +{ + /// Return a reference to the dimension + pub fn raw_dim(&self) -> &D { + &self.dim + } + /// Return the size of the shape in number of elements + pub fn size(&self) -> usize { + self.dim.size() + } +} + /// Stride description #[derive(Copy, Clone, Debug)] pub(crate) enum Strides { @@ -37,21 +51,26 @@ pub(crate) enum Strides { /// Column-major ("F"-order) F, /// Custom strides - Custom(D) + Custom(D), } impl Strides { /// Return strides for `dim` (computed from dimension if c/f, else return the custom stride) pub(crate) fn strides_for_dim(self, dim: &D) -> D - where D: Dimension + where + D: Dimension, { match self { Strides::C => dim.default_strides(), Strides::F => dim.fortran_strides(), Strides::Custom(c) => { - debug_assert_eq!(c.ndim(), dim.ndim(), + debug_assert_eq!( + c.ndim(), + dim.ndim(), "Custom strides given with {} dimensions, expected {}", - c.ndim(), dim.ndim()); + c.ndim(), + dim.ndim() + ); c } } @@ -71,7 +90,7 @@ pub trait ShapeBuilder { type Dim: Dimension; type Strides; - fn into_shape(self) -> Shape; + fn into_shape_with_order(self) -> Shape; fn f(self) -> Shape; fn set_f(self, is_f: bool) -> Shape; fn strides(self, strides: Self::Strides) -> StrideShape; @@ -83,7 +102,7 @@ where { /// Create a `Shape` from `dimension`, using the default memory layout. fn from(dimension: D) -> Shape { - dimension.into_shape() + dimension.into_shape_with_order() } } @@ -93,12 +112,8 @@ where T: ShapeBuilder, { fn from(value: T) -> Self { - let shape = value.into_shape(); - let st = if shape.is_c() { - Strides::C - } else { - Strides::F - }; + let shape = value.into_shape_with_order(); + let st = if shape.is_c() { Strides::C } else { Strides::F }; StrideShape { strides: st, dim: shape.dim, @@ -112,7 +127,7 @@ where { type Dim = T::Dim; type Strides = T; - fn into_shape(self) -> Shape { + fn into_shape_with_order(self) -> Shape { Shape { dim: self.into_dimension(), strides: Strides::C, @@ -122,10 +137,10 @@ where self.set_f(true) } fn set_f(self, is_f: bool) -> Shape { - self.into_shape().set_f(is_f) + self.into_shape_with_order().set_f(is_f) } fn strides(self, st: T) -> StrideShape { - self.into_shape().strides(st.into_dimension()) + self.into_shape_with_order().strides(st.into_dimension()) } } @@ -136,7 +151,7 @@ where type Dim = D; type Strides = D; - fn into_shape(self) -> Shape { + fn into_shape_with_order(self) -> Shape { self } @@ -161,10 +176,42 @@ impl Shape where D: Dimension, { - // Return a reference to the dimension - //pub fn dimension(&self) -> &D { &self.dim } + /// Return a reference to the dimension + pub fn raw_dim(&self) -> &D { + &self.dim + } /// Return the size of the shape in number of elements pub fn size(&self) -> usize { self.dim.size() } } + + +/// Array shape argument with optional order parameter +/// +/// Shape or array dimension argument, with optional [`Order`] parameter. +/// +/// This is an argument conversion trait that is used to accept an array shape and +/// (optionally) an ordering argument. +/// +/// See for example [`.to_shape()`](crate::ArrayBase::to_shape). +pub trait ShapeArg { + type Dim: Dimension; + fn into_shape_and_order(self) -> (Self::Dim, Option); +} + +impl ShapeArg for T where T: IntoDimension { + type Dim = T::Dim; + + fn into_shape_and_order(self) -> (Self::Dim, Option) { + (self.into_dimension(), None) + } +} + +impl ShapeArg for (T, Order) where T: IntoDimension { + type Dim = T::Dim; + + fn into_shape_and_order(self) -> (Self::Dim, Option) { + (self.0.into_dimension(), Some(self.1)) + } +} diff --git a/src/slice.rs b/src/slice.rs index 3c554a5ca..14ab0dd67 100644 --- a/src/slice.rs +++ b/src/slice.rs @@ -8,6 +8,7 @@ use crate::dimension::slices_intersect; use crate::error::{ErrorKind, ShapeError}; use crate::{ArrayViewMut, DimAdd, Dimension, Ix0, Ix1, Ix2, Ix3, Ix4, Ix5, Ix6, IxDyn}; +#[cfg(not(feature = "std"))] use alloc::vec::Vec; use std::convert::TryFrom; use std::fmt; @@ -16,11 +17,11 @@ use std::ops::{Deref, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, Rang /// A slice (range with step size). /// -/// `end` is an exclusive index. Negative `begin` or `end` indexes are counted +/// `end` is an exclusive index. Negative `start` or `end` indexes are counted /// from the back of the axis. If `end` is `None`, the slice extends to the end /// of the axis. /// -/// See also the [`s![]`](macro.s.html) macro. +/// See also the [`s![]`](s!) macro. /// /// ## Examples /// @@ -36,8 +37,12 @@ use std::ops::{Deref, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, Rang /// The Python equivalent is `[a::-1]`. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct Slice { + /// start index; negative are counted from the back of the axis pub start: isize, + /// end index; negative are counted from the back of the axis; when not present + /// the default is the full length of the axis. pub end: Option, + /// step size in elements; the default is 1, for every element. pub step: isize, } @@ -71,13 +76,13 @@ impl Slice { /// Token to represent a new axis in a slice description. /// -/// See also the [`s![]`](macro.s!.html) macro. +/// See also the [`s![]`](s!) macro. #[derive(Clone, Copy, Debug)] pub struct NewAxis; /// A slice (range with step), an index, or a new axis token. /// -/// See also the [`s![]`](macro.s!.html) macro for a convenient way to create a +/// See also the [`s![]`](s!) macro for a convenient way to create a /// `SliceInfo<[SliceInfoElem; n], Din, Dout>`. /// /// ## Examples @@ -105,12 +110,16 @@ pub struct NewAxis; /// `[np.newaxis]`. The macro equivalent is `s![NewAxis]`. #[derive(Debug, PartialEq, Eq, Hash)] pub enum SliceInfoElem { - /// A range with step size. `end` is an exclusive index. Negative `begin` + /// A range with step size. `end` is an exclusive index. Negative `start` /// or `end` indexes are counted from the back of the axis. If `end` is /// `None`, the slice extends to the end of the axis. Slice { + /// start index; negative are counted from the back of the axis start: isize, + /// end index; negative are counted from the back of the axis; when not present + /// the default is the full length of the axis. end: Option, + /// step size in elements; the default is 1, for every element. step: isize, }, /// A single index. @@ -287,6 +296,7 @@ impl From for SliceInfoElem { /// that `D`, `Self::OutDim`, `self.in_dim()`, and `self.out_ndim()` are /// consistent with the `&[SliceInfoElem]` returned by `self.as_ref()` and that /// `self.as_ref()` always returns the same value when called multiple times. +#[allow(clippy::missing_safety_doc)] // not implementable downstream pub unsafe trait SliceArg: AsRef<[SliceInfoElem]> { /// Dimensionality of the output array. type OutDim: Dimension; @@ -389,7 +399,7 @@ unsafe impl SliceArg for [SliceInfoElem] { /// `SliceInfo` instance can still be used to slice an array with dimension /// `IxDyn` as long as the number of axes matches. /// -/// [`.slice()`]: struct.ArrayBase.html#method.slice +/// [`.slice()`]: crate::ArrayBase::slice #[derive(Debug)] pub struct SliceInfo { in_dim: PhantomData, @@ -478,7 +488,7 @@ where } /// Returns the number of dimensions of the input array for - /// [`.slice()`](struct.ArrayBase.html#method.slice). + /// [`.slice()`](crate::ArrayBase::slice). /// /// If `Din` is a fixed-size dimension type, then this is equivalent to /// `Din::NDIM.unwrap()`. Otherwise, the value is calculated by iterating @@ -492,7 +502,7 @@ where } /// Returns the number of dimensions after calling - /// [`.slice()`](struct.ArrayBase.html#method.slice) (including taking + /// [`.slice()`](crate::ArrayBase::slice) (including taking /// subviews). /// /// If `Dout` is a fixed-size dimension type, then this is equivalent to @@ -695,9 +705,6 @@ impl_slicenextdim!((), NewAxis, Ix0, Ix1); /// * *new-axis*: a [`NewAxis`] instance that represents the creation of a new axis. /// (Except for [`.slice_collapse()`], which panics on [`NewAxis`] elements.) /// -/// [`Slice`]: struct.Slice.html -/// [`NewAxis`]: struct.NewAxis.html -/// /// The number of *elem*, not including *new-axis*, must match the /// number of axes in the array. *index*, *range*, *slice*, *step*, and /// *new-axis* can be expressions. *index* must be of type `isize`, `usize`, or @@ -714,12 +721,12 @@ impl_slicenextdim!((), NewAxis, Ix0, Ix1); /// panic. Without the `NewAxis`, i.e. `s![0..4;2, 6, 1..5]`, /// [`.slice_collapse()`] would result in an array of shape `[2, 1, 4]`. /// -/// [`.slice()`]: struct.ArrayBase.html#method.slice -/// [`.slice_mut()`]: struct.ArrayBase.html#method.slice_mut -/// [`.slice_move()`]: struct.ArrayBase.html#method.slice_move -/// [`.slice_collapse()`]: struct.ArrayBase.html#method.slice_collapse +/// [`.slice()`]: crate::ArrayBase::slice +/// [`.slice_mut()`]: crate::ArrayBase::slice_mut +/// [`.slice_move()`]: crate::ArrayBase::slice_move +/// [`.slice_collapse()`]: crate::ArrayBase::slice_collapse /// -/// See also [*Slicing*](struct.ArrayBase.html#slicing). +/// See also [*Slicing*](crate::ArrayBase#slicing). /// /// # Example /// @@ -733,7 +740,7 @@ impl_slicenextdim!((), NewAxis, Ix0, Ix1); /// + v.slice(s![1..-1, 2.. ]) /// + v.slice(s![2.. , 1..-1]) /// } -/// # fn main() { } +/// # fn main() { let _ = laplacian; } /// ``` /// /// # Negative *step* @@ -773,14 +780,11 @@ macro_rules! s( r => { let in_dim = $crate::SliceNextDim::next_in_dim(&r, $in_dim); let out_dim = $crate::SliceNextDim::next_out_dim(&r, $out_dim); - #[allow(unsafe_code)] - unsafe { - $crate::SliceInfo::new_unchecked( - [$($stack)* $crate::s!(@convert r, $s)], - in_dim, - out_dim, - ) - } + ( + [$($stack)* $crate::s!(@convert r, $s)], + in_dim, + out_dim, + ) } } }; @@ -790,14 +794,11 @@ macro_rules! s( r => { let in_dim = $crate::SliceNextDim::next_in_dim(&r, $in_dim); let out_dim = $crate::SliceNextDim::next_out_dim(&r, $out_dim); - #[allow(unsafe_code)] - unsafe { - $crate::SliceInfo::new_unchecked( - [$($stack)* $crate::s!(@convert r)], - in_dim, - out_dim, - ) - } + ( + [$($stack)* $crate::s!(@convert r)], + in_dim, + out_dim, + ) } } }; @@ -836,37 +837,40 @@ macro_rules! s( } }; // empty call, i.e. `s![]` - (@parse ::std::marker::PhantomData::<$crate::Ix0>, ::std::marker::PhantomData::<$crate::Ix0>, []) => { - { - #[allow(unsafe_code)] - unsafe { - $crate::SliceInfo::new_unchecked( - [], - ::std::marker::PhantomData::<$crate::Ix0>, - ::std::marker::PhantomData::<$crate::Ix0>, - ) - } - } + (@parse ::core::marker::PhantomData::<$crate::Ix0>, ::core::marker::PhantomData::<$crate::Ix0>, []) => { + ( + [], + ::core::marker::PhantomData::<$crate::Ix0>, + ::core::marker::PhantomData::<$crate::Ix0>, + ) }; // Catch-all clause for syntax errors (@parse $($t:tt)*) => { compile_error!("Invalid syntax in s![] call.") }; // convert range/index/new-axis into SliceInfoElem (@convert $r:expr) => { - <$crate::SliceInfoElem as ::std::convert::From<_>>::from($r) + <$crate::SliceInfoElem as ::core::convert::From<_>>::from($r) }; // convert range/index/new-axis and step into SliceInfoElem (@convert $r:expr, $s:expr) => { - <$crate::SliceInfoElem as ::std::convert::From<_>>::from( - <$crate::Slice as ::std::convert::From<_>>::from($r).step_by($s as isize) + <$crate::SliceInfoElem as ::core::convert::From<_>>::from( + <$crate::Slice as ::core::convert::From<_>>::from($r).step_by($s as isize) ) }; ($($t:tt)*) => { - $crate::s![@parse - ::std::marker::PhantomData::<$crate::Ix0>, - ::std::marker::PhantomData::<$crate::Ix0>, - [] - $($t)* - ] + { + let (indices, in_dim, out_dim) = $crate::s![@parse + ::core::marker::PhantomData::<$crate::Ix0>, + ::core::marker::PhantomData::<$crate::Ix0>, + [] + $($t)* + ]; + // Safety: The `s![@parse ...]` above always constructs the correct + // values to meet the constraints of `SliceInfo::new_unchecked`. + #[allow(unsafe_code)] + unsafe { + $crate::SliceInfo::new_unchecked(indices, in_dim, out_dim) + } + } }; ); diff --git a/src/split_at.rs b/src/split_at.rs index b05e58346..50466afdf 100644 --- a/src/split_at.rs +++ b/src/split_at.rs @@ -7,6 +7,7 @@ pub(crate) trait SplitAt { } pub(crate) trait SplitPreference : SplitAt { + #[allow(dead_code)] // used only when Rayon support is enabled fn can_split(&self) -> bool; fn split_preference(&self) -> (Axis, usize); fn split(self) -> (Self, Self) where Self: Sized { diff --git a/src/stacking.rs b/src/stacking.rs index 500ded6af..16058f39d 100644 --- a/src/stacking.rs +++ b/src/stacking.rs @@ -6,6 +6,10 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; + +use crate::dimension; use crate::error::{from_kind, ErrorKind, ShapeError}; use crate::imp_prelude::*; @@ -38,7 +42,7 @@ pub fn stack( arrays: &[ArrayView], ) -> Result, ShapeError> where - A: Copy, + A: Clone, D: Dimension, D::Larger: RemoveAxis, { @@ -68,7 +72,7 @@ where /// ``` pub fn concatenate(axis: Axis, arrays: &[ArrayView]) -> Result, ShapeError> where - A: Copy, + A: Clone, D: RemoveAxis, { if arrays.is_empty() { @@ -88,24 +92,21 @@ where let stacked_dim = arrays.iter().fold(0, |acc, a| acc + a.len_of(axis)); res_dim.set_axis(axis, stacked_dim); + let new_len = dimension::size_of_shape_checked(&res_dim)?; - // we can safely use uninitialized values here because we will - // overwrite every one of them. - let mut res = Array::uninit(res_dim); + // start with empty array with precomputed capacity + // append's handling of empty arrays makes sure `axis` is ok for appending + res_dim.set_axis(axis, 0); + let mut res = unsafe { + // Safety: dimension is size 0 and vec is empty + Array::from_shape_vec_unchecked(res_dim, Vec::with_capacity(new_len)) + }; - { - let mut assign_view = res.view_mut(); - for array in arrays { - let len = array.len_of(axis); - let (front, rest) = assign_view.split_at(axis, len); - array.assign_to(front); - assign_view = rest; - } - debug_assert_eq!(assign_view.len(), 0); - } - unsafe { - Ok(res.assume_init()) + for array in arrays { + res.append(axis, array.clone())?; } + debug_assert_eq!(res.len_of(axis), stacked_dim); + Ok(res) } #[deprecated(note="Use under the name stack instead.", since="0.15.0")] @@ -138,7 +139,7 @@ pub fn stack_new_axis( arrays: &[ArrayView], ) -> Result, ShapeError> where - A: Copy, + A: Clone, D: Dimension, D::Larger: RemoveAxis, { @@ -158,33 +159,29 @@ where res_dim.set_axis(axis, arrays.len()); - // we can safely use uninitialized values here because we will - // overwrite every one of them. - let mut res = Array::uninit(res_dim); + let new_len = dimension::size_of_shape_checked(&res_dim)?; - res.axis_iter_mut(axis) - .zip(arrays.iter()) - .for_each(|(assign_view, array)| { - // assign_view is D::Larger::Smaller which is usually == D - // (but if D is Ix6, we have IxD != Ix6 here; differing types - // but same number of axes). - let assign_view = assign_view.into_dimensionality::() - .expect("same-dimensionality cast"); - array.assign_to(assign_view); - }); + // start with empty array with precomputed capacity + // append's handling of empty arrays makes sure `axis` is ok for appending + res_dim.set_axis(axis, 0); + let mut res = unsafe { + // Safety: dimension is size 0 and vec is empty + Array::from_shape_vec_unchecked(res_dim, Vec::with_capacity(new_len)) + }; - unsafe { - Ok(res.assume_init()) + for array in arrays { + res.append(axis, array.clone().insert_axis(axis))?; } + + debug_assert_eq!(res.len_of(axis), arrays.len()); + Ok(res) } /// Stack arrays along the new axis. /// -/// Uses the [`stack`][1] function, calling `ArrayView::from(&a)` on each +/// Uses the [`stack()`] function, calling `ArrayView::from(&a)` on each /// argument `a`. /// -/// [1]: fn.stack.html -/// /// ***Panics*** if the `stack` function would return an error. /// /// ``` @@ -194,31 +191,46 @@ where /// /// # fn main() { /// -/// let a = arr2(&[[2., 2.], -/// [3., 3.]]); -/// assert!( -/// stack![Axis(0), a, a] -/// == arr3(&[[[2., 2.], -/// [3., 3.]], -/// [[2., 2.], -/// [3., 3.]]]) +/// let a = arr2(&[[1., 2.], +/// [3., 4.]]); +/// assert_eq!( +/// stack![Axis(0), a, a], +/// arr3(&[[[1., 2.], +/// [3., 4.]], +/// [[1., 2.], +/// [3., 4.]]]), +/// ); +/// assert_eq!( +/// stack![Axis(1), a, a,], +/// arr3(&[[[1., 2.], +/// [1., 2.]], +/// [[3., 4.], +/// [3., 4.]]]), +/// ); +/// assert_eq!( +/// stack![Axis(2), a, a], +/// arr3(&[[[1., 1.], +/// [2., 2.]], +/// [[3., 3.], +/// [4., 4.]]]), /// ); /// # } /// ``` #[macro_export] macro_rules! stack { + ($axis:expr, $( $array:expr ),+ ,) => { + $crate::stack!($axis, $($array),+) + }; ($axis:expr, $( $array:expr ),+ ) => { $crate::stack($axis, &[ $($crate::ArrayView::from(&$array) ),* ]).unwrap() - } + }; } /// Concatenate arrays along the given axis. /// -/// Uses the [`concatenate`][1] function, calling `ArrayView::from(&a)` on each +/// Uses the [`concatenate()`] function, calling `ArrayView::from(&a)` on each /// argument `a`. /// -/// [1]: fn.concatenate.html -/// /// ***Panics*** if the `concatenate` function would return an error. /// /// ``` @@ -228,31 +240,37 @@ macro_rules! stack { /// /// # fn main() { /// -/// let a = arr2(&[[2., 2.], -/// [3., 3.]]); -/// assert!( -/// concatenate![Axis(0), a, a] -/// == arr2(&[[2., 2.], -/// [3., 3.], -/// [2., 2.], -/// [3., 3.]]) +/// let a = arr2(&[[1., 2.], +/// [3., 4.]]); +/// assert_eq!( +/// concatenate![Axis(0), a, a], +/// arr2(&[[1., 2.], +/// [3., 4.], +/// [1., 2.], +/// [3., 4.]]), +/// ); +/// assert_eq!( +/// concatenate![Axis(1), a, a,], +/// arr2(&[[1., 2., 1., 2.], +/// [3., 4., 3., 4.]]), /// ); /// # } /// ``` #[macro_export] macro_rules! concatenate { + ($axis:expr, $( $array:expr ),+ ,) => { + $crate::concatenate!($axis, $($array),+) + }; ($axis:expr, $( $array:expr ),+ ) => { $crate::concatenate($axis, &[ $($crate::ArrayView::from(&$array) ),* ]).unwrap() - } + }; } /// Stack arrays along the new axis. /// -/// Uses the [`stack_new_axis`][1] function, calling `ArrayView::from(&a)` on each +/// Uses the [`stack_new_axis()`] function, calling `ArrayView::from(&a)` on each /// argument `a`. /// -/// [1]: fn.stack_new_axis.html -/// /// ***Panics*** if the `stack` function would return an error. /// /// ``` diff --git a/src/zip/mod.rs b/src/zip/mod.rs index 553b173b1..a94f74518 100644 --- a/src/zip/mod.rs +++ b/src/zip/mod.rs @@ -20,8 +20,8 @@ use crate::Layout; use crate::partial::Partial; use crate::indexes::{indices, Indices}; -use crate::layout::{CORDER, FORDER}; use crate::split_at::{SplitPreference, SplitAt}; +use crate::dimension; pub use self::ndproducer::{NdProducer, IntoNdProducer, Offset}; @@ -37,9 +37,7 @@ macro_rules! fold_while { /// Broadcast an array so that it acts like a larger size and/or shape array. /// -/// See [broadcasting][1] for more information. -/// -/// [1]: struct.ArrayBase.html#broadcasting +/// See [broadcasting](ArrayBase#broadcasting) for more information. trait Broadcast where E: IntoDimension, @@ -53,33 +51,38 @@ where private_decl! {} } +/// Compute `Layout` hints for array shape dim, strides +fn array_layout(dim: &D, strides: &D) -> Layout { + let n = dim.ndim(); + if dimension::is_layout_c(dim, strides) { + // effectively one-dimensional => C and F layout compatible + if n <= 1 || dim.slice().iter().filter(|&&len| len > 1).count() <= 1 { + Layout::one_dimensional() + } else { + Layout::c() + } + } else if n > 1 && dimension::is_layout_f(dim, strides) { + Layout::f() + } else if n > 1 { + if dim[0] > 1 && strides[0] == 1 { + Layout::fpref() + } else if dim[n - 1] > 1 && strides[n - 1] == 1 { + Layout::cpref() + } else { + Layout::none() + } + } else { + Layout::none() + } +} + impl ArrayBase where S: RawData, D: Dimension, { pub(crate) fn layout_impl(&self) -> Layout { - let n = self.ndim(); - if self.is_standard_layout() { - // effectively one-dimensional => C and F layout compatible - if n <= 1 || self.shape().iter().filter(|&&len| len > 1).count() <= 1 { - Layout::one_dimensional() - } else { - Layout::c() - } - } else if n > 1 && self.raw_view().reversed_axes().is_standard_layout() { - Layout::f() - } else if n > 1 { - if self.stride_of(Axis(0)) == 1 { - Layout::fpref() - } else if self.stride_of(Axis(n - 1)) == 1 { - Layout::cpref() - } else { - Layout::none() - } - } else { - Layout::none() - } + array_layout(&self.dim, &self.strides) } } @@ -90,6 +93,7 @@ where { type Output = ArrayView<'a, A, E::Dim>; fn broadcast_unwrap(self, shape: E) -> Self::Output { + #[allow(clippy::needless_borrow)] let res: ArrayView<'_, A, E::Dim> = (&self).broadcast_unwrap(shape.into_dimension()); unsafe { ArrayView::new(res.ptr, res.dim, res.strides) } } @@ -116,7 +120,7 @@ trait ZippableTuple: Sized { /// a time). /// /// In general, the zip uses a tuple of producers -/// ([`NdProducer`](trait.NdProducer.html) trait) that all have to be of the +/// ([`NdProducer`] trait) that all have to be of the /// same shape. The NdProducer implementation defines what its item type is /// (for example if it's a shared reference, mutable reference or an array /// view etc). @@ -131,11 +135,9 @@ trait ZippableTuple: Sized { /// `fold_while`. The zip object can be split, which allows parallelization. /// A read-only zip object (no mutable producers) can be cloned. /// -/// See also the [`azip!()` macro][az] which offers a convenient shorthand +/// See also the [`azip!()`] which offers a convenient shorthand /// to common ways to use `Zip`. /// -/// [az]: macro.azip.html -/// /// ``` /// use ndarray::Zip; /// use ndarray::Array2; @@ -180,12 +182,13 @@ trait ZippableTuple: Sized { /// /// // Example 3: Recreate Example 2 using map_collect to make a new array /// -/// let mut totals2 = Zip::from(a.rows()).map_collect(|row| row.sum()); +/// let totals2 = Zip::from(a.rows()).map_collect(|row| row.sum()); /// /// // Check the result against the previous example. /// assert_eq!(totals, totals2); /// ``` #[derive(Debug, Clone)] +#[must_use = "zipping producers is lazy and does nothing unless consumed"] pub struct Zip { parts: Parts, dimension: D, @@ -241,22 +244,25 @@ where } } -impl Zip +#[inline] +fn zip_dimension_check(dimension: &D, part: &P) where D: Dimension, + P: NdProducer, { - fn check

(&self, part: &P) - where - P: NdProducer, - { - ndassert!( - part.equal_dim(&self.dimension), - "Zip: Producer dimension mismatch, expected: {:?}, got: {:?}", - self.dimension, - part.raw_dim() - ); - } + ndassert!( + part.equal_dim(dimension), + "Zip: Producer dimension mismatch, expected: {:?}, got: {:?}", + dimension, + part.raw_dim() + ); +} + +impl Zip +where + D: Dimension, +{ /// Return a the number of element tuples in the Zip pub fn size(&self) -> usize { self.dimension.size() @@ -271,7 +277,8 @@ where } fn prefer_f(&self) -> bool { - !self.layout.is(CORDER) && (self.layout.is(FORDER) || self.layout_tendency < 0) + !self.layout.is(Layout::CORDER) && + (self.layout.is(Layout::FORDER) || self.layout_tendency < 0) } /// Return an *approximation* to the max stride axis; if @@ -309,7 +316,7 @@ where { if self.dimension.ndim() == 0 { function(acc, unsafe { self.parts.as_ref(self.parts.as_ptr()) }) - } else if self.layout.is(CORDER | FORDER) { + } else if self.layout.is(Layout::CORDER | Layout::FORDER) { self.for_each_core_contiguous(acc, function) } else { self.for_each_core_strided(acc, function) @@ -321,7 +328,7 @@ where F: FnMut(Acc, P::Item) -> FoldWhile, P: ZippableTuple, { - debug_assert!(self.layout.is(CORDER | FORDER)); + debug_assert!(self.layout.is(Layout::CORDER | Layout::FORDER)); let size = self.dimension.size(); let ptrs = self.parts.as_ptr(); let inner_strides = self.parts.contiguous_stride(); @@ -422,13 +429,33 @@ where } #[cfg(feature = "rayon")] - pub(crate) fn uninitalized_for_current_layout(&self) -> Array, D> + pub(crate) fn uninitialized_for_current_layout(&self) -> Array, D> { let is_f = self.prefer_f(); Array::uninit(self.dimension.clone().set_f(is_f)) } } +impl Zip<(P1, P2), D> +where + D: Dimension, + P1: NdProducer, + P1: NdProducer, +{ + /// Debug assert traversal order is like c (including 1D case) + // Method placement: only used for binary Zip at the moment. + #[inline] + pub(crate) fn debug_assert_c_order(self) -> Self { + debug_assert!(self.layout.is(Layout::CORDER) || self.layout_tendency >= 0 || + self.dimension.slice().iter().filter(|&&d| d > 1).count() <= 1, + "Assertion failed: traversal is not c-order or 1D for \ + layout {:?}, tendency {}, dimension {:?}", + self.layout, self.layout_tendency, self.dimension); + self + } +} + + /* trait Offset : Copy { unsafe fn offset(self, off: isize) -> Self; @@ -645,6 +672,33 @@ macro_rules! map_impl { }).is_done() } + /// Tests if at least one element of the iterator matches a predicate. + /// + /// Returns `true` if `predicate` evaluates to `true` for at least one element. + /// Returns `false` if the input arrays are empty. + /// + /// Example: + /// + /// ``` + /// use ndarray::{array, Zip}; + /// let a = array![1, 2, 3]; + /// let b = array![1, 4, 9]; + /// assert!(Zip::from(&a).and(&b).any(|&a, &b| a == b)); + /// assert!(!Zip::from(&a).and(&b).any(|&a, &b| a - 1 == b)); + /// ``` + pub fn any(mut self, mut predicate: F) -> bool + where F: FnMut($($p::Item),*) -> bool + { + self.for_each_core((), move |_, args| { + let ($($p,)*) = args; + if predicate($($p),*) { + FoldWhile::Done(()) + } else { + FoldWhile::Continue(()) + } + }).is_done() + } + expand_if!(@bool [$notlast] /// Include the producer `p` in the Zip. @@ -655,10 +709,30 @@ macro_rules! map_impl { where P: IntoNdProducer, { let part = p.into_producer(); - self.check(&part); + zip_dimension_check(&self.dimension, &part); self.build_and(part) } + /// Include the producer `p` in the Zip. + /// + /// ## Safety + /// + /// The caller must ensure that the producer's shape is equal to the Zip's shape. + /// Uses assertions when debug assertions are enabled. + #[allow(unused)] + pub(crate) unsafe fn and_unchecked

(self, p: P) -> Zip<($($p,)* P::Output, ), D> + where P: IntoNdProducer, + { + #[cfg(debug_assertions)] + { + self.and(p) + } + #[cfg(not(debug_assertions))] + { + self.build_and(p.into_producer()) + } + } + /// Include the producer `p` in the Zip, broadcasting if needed. /// /// If their shapes disagree, `rhs` is broadcast to the shape of `self`. @@ -706,7 +780,7 @@ macro_rules! map_impl { // Use partial to count the number of filled elements, and can drop the right // number of elements on unwinding (if it happens during apply/collect). unsafe { - let output_view = output.cast::(); + let output_view = output.into_raw_view_mut().cast::(); self.and(output_view) .collect_with_partial(f) .release_ownership(); @@ -800,7 +874,7 @@ macro_rules! map_impl { // debug assert that the output is contiguous in the memory layout we need if cfg!(debug_assertions) { let out_layout = output.layout(); - assert!(out_layout.is(CORDER | FORDER)); + assert!(out_layout.is(Layout::CORDER | Layout::FORDER)); assert!( (self.layout_tendency <= 0 && out_layout.tendency() <= 0) || (self.layout_tendency >= 0 && out_layout.tendency() >= 0), diff --git a/src/zip/ndproducer.rs b/src/zip/ndproducer.rs index 7c201741c..4eb986d37 100644 --- a/src/zip/ndproducer.rs +++ b/src/zip/ndproducer.rs @@ -1,8 +1,7 @@ - use crate::imp_prelude::*; use crate::Layout; use crate::NdIndex; -#[cfg(not(features = "std"))] +#[cfg(not(feature = "std"))] use alloc::vec::Vec; /// Argument conversion into a producer. @@ -36,22 +35,22 @@ where /// for example an array view, mutable array view or an iterator /// that yields chunks. /// -/// Producers are used as a arguments to [`Zip`](struct.Zip.html) and -/// [`azip!()`](macro.azip.html). +/// Producers are used as a arguments to [`Zip`](crate::Zip) and +/// [`azip!()`]. /// /// # Comparison to `IntoIterator` /// /// Most `NdProducers` are *iterable* (implement `IntoIterator`) but not directly /// iterators. This separation is needed because the producer represents /// a multidimensional set of items, it can be split along a particular axis for -/// parallelization, and it has no fixed correspondance to a sequence. +/// parallelization, and it has no fixed correspondence to a sequence. /// /// The natural exception is one dimensional producers, like `AxisIter`, which /// implement `Iterator` directly /// (`AxisIter` traverses a one dimensional sequence, along an axis, while /// *producing* multidimensional items). /// -/// See also [`IntoNdProducer`](trait.IntoNdProducer.html) +/// See also [`IntoNdProducer`] pub trait NdProducer { /// The element produced per iteration. type Item; @@ -168,6 +167,26 @@ impl<'a, A: 'a> IntoNdProducer for &'a mut [A] { } } +/// A one-dimensional array is a one-dimensional producer +impl<'a, A: 'a, const N: usize> IntoNdProducer for &'a [A; N] { + type Item = ::Item; + type Dim = Ix1; + type Output = ArrayView1<'a, A>; + fn into_producer(self) -> Self::Output { + <_>::from(self) + } +} + +/// A mutable one-dimensional array is a mutable one-dimensional producer +impl<'a, A: 'a, const N: usize> IntoNdProducer for &'a mut [A; N] { + type Item = ::Item; + type Dim = Ix1; + type Output = ArrayViewMut1<'a, A>; + fn into_producer(self) -> Self::Output { + <_>::from(self) + } +} + /// A Vec is a one-dimensional producer impl<'a, A: 'a> IntoNdProducer for &'a Vec { type Item = ::Item; @@ -195,37 +214,31 @@ impl<'a, A, D: Dimension> NdProducer for ArrayView<'a, A, D> { type Stride = isize; private_impl! {} - #[doc(hidden)] + fn raw_dim(&self) -> Self::Dim { self.raw_dim() } - #[doc(hidden)] fn equal_dim(&self, dim: &Self::Dim) -> bool { self.dim.equal(dim) } - #[doc(hidden)] fn as_ptr(&self) -> *mut A { self.as_ptr() as _ } - #[doc(hidden)] fn layout(&self) -> Layout { self.layout_impl() } - #[doc(hidden)] unsafe fn as_ref(&self, ptr: *mut A) -> Self::Item { &*ptr } - #[doc(hidden)] unsafe fn uget_ptr(&self, i: &Self::Dim) -> *mut A { self.ptr.as_ptr().offset(i.index_unchecked(&self.strides)) } - #[doc(hidden)] fn stride_of(&self, axis: Axis) -> isize { self.stride_of(axis) } @@ -235,7 +248,6 @@ impl<'a, A, D: Dimension> NdProducer for ArrayView<'a, A, D> { 1 } - #[doc(hidden)] fn split_at(self, axis: Axis, index: usize) -> (Self, Self) { self.split_at(axis, index) } @@ -248,37 +260,31 @@ impl<'a, A, D: Dimension> NdProducer for ArrayViewMut<'a, A, D> { type Stride = isize; private_impl! {} - #[doc(hidden)] + fn raw_dim(&self) -> Self::Dim { self.raw_dim() } - #[doc(hidden)] fn equal_dim(&self, dim: &Self::Dim) -> bool { self.dim.equal(dim) } - #[doc(hidden)] fn as_ptr(&self) -> *mut A { self.as_ptr() as _ } - #[doc(hidden)] fn layout(&self) -> Layout { self.layout_impl() } - #[doc(hidden)] unsafe fn as_ref(&self, ptr: *mut A) -> Self::Item { &mut *ptr } - #[doc(hidden)] unsafe fn uget_ptr(&self, i: &Self::Dim) -> *mut A { self.ptr.as_ptr().offset(i.index_unchecked(&self.strides)) } - #[doc(hidden)] fn stride_of(&self, axis: Axis) -> isize { self.stride_of(axis) } @@ -288,7 +294,6 @@ impl<'a, A, D: Dimension> NdProducer for ArrayViewMut<'a, A, D> { 1 } - #[doc(hidden)] fn split_at(self, axis: Axis, index: usize) -> (Self, Self) { self.split_at(axis, index) } @@ -301,37 +306,31 @@ impl NdProducer for RawArrayView { type Stride = isize; private_impl! {} - #[doc(hidden)] + fn raw_dim(&self) -> Self::Dim { self.raw_dim() } - #[doc(hidden)] fn equal_dim(&self, dim: &Self::Dim) -> bool { self.dim.equal(dim) } - #[doc(hidden)] fn as_ptr(&self) -> *const A { self.as_ptr() } - #[doc(hidden)] fn layout(&self) -> Layout { self.layout_impl() } - #[doc(hidden)] unsafe fn as_ref(&self, ptr: *const A) -> *const A { ptr } - #[doc(hidden)] unsafe fn uget_ptr(&self, i: &Self::Dim) -> *const A { self.ptr.as_ptr().offset(i.index_unchecked(&self.strides)) } - #[doc(hidden)] fn stride_of(&self, axis: Axis) -> isize { self.stride_of(axis) } @@ -341,7 +340,6 @@ impl NdProducer for RawArrayView { 1 } - #[doc(hidden)] fn split_at(self, axis: Axis, index: usize) -> (Self, Self) { self.split_at(axis, index) } @@ -354,37 +352,31 @@ impl NdProducer for RawArrayViewMut { type Stride = isize; private_impl! {} - #[doc(hidden)] + fn raw_dim(&self) -> Self::Dim { self.raw_dim() } - #[doc(hidden)] fn equal_dim(&self, dim: &Self::Dim) -> bool { self.dim.equal(dim) } - #[doc(hidden)] fn as_ptr(&self) -> *mut A { self.as_ptr() as _ } - #[doc(hidden)] fn layout(&self) -> Layout { self.layout_impl() } - #[doc(hidden)] unsafe fn as_ref(&self, ptr: *mut A) -> *mut A { ptr } - #[doc(hidden)] unsafe fn uget_ptr(&self, i: &Self::Dim) -> *mut A { self.ptr.as_ptr().offset(i.index_unchecked(&self.strides)) } - #[doc(hidden)] fn stride_of(&self, axis: Axis) -> isize { self.stride_of(axis) } @@ -394,9 +386,7 @@ impl NdProducer for RawArrayViewMut { 1 } - #[doc(hidden)] fn split_at(self, axis: Axis, index: usize) -> (Self, Self) { self.split_at(axis, index) } } - diff --git a/src/zip/zipmacro.rs b/src/zip/zipmacro.rs index f25e33d31..0bbe956b3 100644 --- a/src/zip/zipmacro.rs +++ b/src/zip/zipmacro.rs @@ -1,7 +1,7 @@ /// Array zip macro: lock step function application across several arrays and /// producers. /// -/// This is a shorthand for [`Zip`](struct.Zip.html). +/// This is a shorthand for [`Zip`](crate::Zip). /// /// This example: /// @@ -42,60 +42,57 @@ /// /// type M = Array2; /// -/// fn main() { -/// // Setup example arrays -/// let mut a = M::zeros((16, 16)); -/// let mut b = M::zeros(a.dim()); -/// let mut c = M::zeros(a.dim()); +/// // Setup example arrays +/// let mut a = M::zeros((16, 16)); +/// let mut b = M::zeros(a.dim()); +/// let mut c = M::zeros(a.dim()); /// -/// // assign values -/// b.fill(1.); -/// for ((i, j), elt) in c.indexed_iter_mut() { -/// *elt = (i + 10 * j) as f32; -/// } -/// -/// // Example 1: Compute a simple ternary operation: -/// // elementwise addition of b and c, stored in a -/// azip!((a in &mut a, &b in &b, &c in &c) *a = b + c); +/// // assign values +/// b.fill(1.); +/// for ((i, j), elt) in c.indexed_iter_mut() { +/// *elt = (i + 10 * j) as f32; +/// } /// -/// assert_eq!(a, &b + &c); +/// // Example 1: Compute a simple ternary operation: +/// // elementwise addition of b and c, stored in a +/// azip!((a in &mut a, &b in &b, &c in &c) *a = b + c); /// -/// // Example 2: azip!() with index -/// azip!((index (i, j), &b in &b, &c in &c) { -/// a[[i, j]] = b - c; -/// }); +/// assert_eq!(a, &b + &c); /// -/// assert_eq!(a, &b - &c); +/// // Example 2: azip!() with index +/// azip!((index (i, j), &b in &b, &c in &c) { +/// a[[i, j]] = b - c; +/// }); /// +/// assert_eq!(a, &b - &c); /// -/// // Example 3: azip!() on references -/// // See the definition of the function below -/// borrow_multiply(&mut a, &b, &c); /// -/// assert_eq!(a, &b * &c); +/// // Example 3: azip!() on references +/// // See the definition of the function below +/// borrow_multiply(&mut a, &b, &c); /// +/// assert_eq!(a, &b * &c); /// -/// // Since this function borrows its inputs, the `IntoNdProducer` -/// // expressions don't need to explicitly include `&mut` or `&`. -/// fn borrow_multiply(a: &mut M, b: &M, c: &M) { -/// azip!((a in a, &b in b, &c in c) *a = b * c); -/// } /// +/// // Since this function borrows its inputs, the `IntoNdProducer` +/// // expressions don't need to explicitly include `&mut` or `&`. +/// fn borrow_multiply(a: &mut M, b: &M, c: &M) { +/// azip!((a in a, &b in b, &c in c) *a = b * c); +/// } /// -/// // Example 4: using azip!() without dereference in pattern. -/// // -/// // Create a new array `totals` with one entry per row of `a`. -/// // Use azip to traverse the rows of `a` and assign to the corresponding -/// // entry in `totals` with the sum across each row. -/// // -/// // The row is an array view; it doesn't need to be dereferenced. -/// let mut totals = Array1::zeros(a.nrows()); -/// azip!((totals in &mut totals, row in a.rows()) *totals = row.sum()); /// -/// // Check the result against the built in `.sum_axis()` along axis 1. -/// assert_eq!(totals, a.sum_axis(Axis(1))); -/// } +/// // Example 4: using azip!() without dereference in pattern. +/// // +/// // Create a new array `totals` with one entry per row of `a`. +/// // Use azip to traverse the rows of `a` and assign to the corresponding +/// // entry in `totals` with the sum across each row. +/// // +/// // The row is an array view; it doesn't need to be dereferenced. +/// let mut totals = Array1::zeros(a.nrows()); +/// azip!((totals in &mut totals, row in a.rows()) *totals = row.sum()); /// +/// // Check the result against the built in `.sum_axis()` along axis 1. +/// assert_eq!(totals, a.sum_axis(Axis(1))); /// ``` #[macro_export] macro_rules! azip { diff --git a/tests/append.rs b/tests/append.rs new file mode 100644 index 000000000..cbb10d853 --- /dev/null +++ b/tests/append.rs @@ -0,0 +1,403 @@ + +use ndarray::prelude::*; +use ndarray::{ShapeError, ErrorKind}; + +#[test] +fn push_row() { + let mut a = Array::zeros((0, 4)); + a.push_row(aview1(&[0., 1., 2., 3.])).unwrap(); + a.push_row(aview1(&[4., 5., 6., 7.])).unwrap(); + assert_eq!(a.shape(), &[2, 4]); + + assert_eq!(a, + array![[0., 1., 2., 3.], + [4., 5., 6., 7.]]); + + assert_eq!(a.push_row(aview1(&[1.])), + Err(ShapeError::from_kind(ErrorKind::IncompatibleShape))); + assert_eq!(a.push_column(aview1(&[1.])), + Err(ShapeError::from_kind(ErrorKind::IncompatibleShape))); + assert_eq!(a.push_column(aview1(&[1., 2.])), + Ok(())); + assert_eq!(a, + array![[0., 1., 2., 3., 1.], + [4., 5., 6., 7., 2.]]); +} + +#[test] +fn push_row_wrong_layout() { + let mut a = Array::zeros((0, 4)); + a.push_row(aview1(&[0., 1., 2., 3.])).unwrap(); + a.push_row(aview1(&[4., 5., 6., 7.])).unwrap(); + assert_eq!(a.shape(), &[2, 4]); + + assert_eq!(a, + array![[0., 1., 2., 3.], + [4., 5., 6., 7.]]); + assert_eq!(a.strides(), &[4, 1]); + + // Changing the memory layout to fit the next append + let mut a2 = a.clone(); + a2.push_column(aview1(&[1., 2.])).unwrap(); + assert_eq!(a2, + array![[0., 1., 2., 3., 1.], + [4., 5., 6., 7., 2.]]); + assert_eq!(a2.strides(), &[1, 2]); + + + // Clone the array + + let mut dim = a.raw_dim(); + dim[1] = 0; + let mut b = Array::zeros(dim); + b.append(Axis(1), a.view()).unwrap(); + assert_eq!(b.push_column(aview1(&[1., 2.])), Ok(())); + assert_eq!(b, + array![[0., 1., 2., 3., 1.], + [4., 5., 6., 7., 2.]]); +} + +#[test] +fn push_row_neg_stride_1() { + let mut a = Array::zeros((0, 4)); + a.push_row(aview1(&[0., 1., 2., 3.])).unwrap(); + a.push_row(aview1(&[4., 5., 6., 7.])).unwrap(); + assert_eq!(a.shape(), &[2, 4]); + + assert_eq!(a, + array![[0., 1., 2., 3.], + [4., 5., 6., 7.]]); + assert_eq!(a.strides(), &[4, 1]); + + a.invert_axis(Axis(0)); + + // Changing the memory layout to fit the next append + let mut a2 = a.clone(); + println!("a = {:?}", a); + println!("a2 = {:?}", a2); + a2.push_column(aview1(&[1., 2.])).unwrap(); + assert_eq!(a2, + array![[4., 5., 6., 7., 1.], + [0., 1., 2., 3., 2.]]); + assert_eq!(a2.strides(), &[1, 2]); + + a.invert_axis(Axis(1)); + let mut a3 = a.clone(); + a3.push_row(aview1(&[4., 5., 6., 7.])).unwrap(); + assert_eq!(a3, + array![[7., 6., 5., 4.], + [3., 2., 1., 0.], + [4., 5., 6., 7.]]); + assert_eq!(a3.strides(), &[4, 1]); + + a.invert_axis(Axis(0)); + let mut a4 = a.clone(); + a4.push_row(aview1(&[4., 5., 6., 7.])).unwrap(); + assert_eq!(a4, + array![[3., 2., 1., 0.], + [7., 6., 5., 4.], + [4., 5., 6., 7.]]); + assert_eq!(a4.strides(), &[4, -1]); +} + +#[test] +fn push_row_neg_stride_2() { + let mut a = Array::zeros((0, 4)); + a.push_row(aview1(&[0., 1., 2., 3.])).unwrap(); + a.push_row(aview1(&[4., 5., 6., 7.])).unwrap(); + assert_eq!(a.shape(), &[2, 4]); + + assert_eq!(a, + array![[0., 1., 2., 3.], + [4., 5., 6., 7.]]); + assert_eq!(a.strides(), &[4, 1]); + + a.invert_axis(Axis(1)); + + // Changing the memory layout to fit the next append + let mut a2 = a.clone(); + println!("a = {:?}", a); + println!("a2 = {:?}", a2); + a2.push_column(aview1(&[1., 2.])).unwrap(); + assert_eq!(a2, + array![[3., 2., 1., 0., 1.], + [7., 6., 5., 4., 2.]]); + assert_eq!(a2.strides(), &[1, 2]); + + a.invert_axis(Axis(0)); + let mut a3 = a.clone(); + a3.push_row(aview1(&[4., 5., 6., 7.])).unwrap(); + assert_eq!(a3, + array![[7., 6., 5., 4.], + [3., 2., 1., 0.], + [4., 5., 6., 7.]]); + assert_eq!(a3.strides(), &[4, 1]); + + a.invert_axis(Axis(1)); + let mut a4 = a.clone(); + a4.push_row(aview1(&[4., 5., 6., 7.])).unwrap(); + assert_eq!(a4, + array![[4., 5., 6., 7.], + [0., 1., 2., 3.], + [4., 5., 6., 7.]]); + assert_eq!(a4.strides(), &[4, 1]); +} + +#[test] +fn push_row_error() { + let mut a = Array::zeros((3, 4)); + + assert_eq!(a.push_row(aview1(&[1.])), + Err(ShapeError::from_kind(ErrorKind::IncompatibleShape))); + assert_eq!(a.push_column(aview1(&[1.])), + Err(ShapeError::from_kind(ErrorKind::IncompatibleShape))); + assert_eq!(a.push_column(aview1(&[1., 2., 3.])), + Ok(())); + assert_eq!(a.t(), + array![[0., 0., 0.], + [0., 0., 0.], + [0., 0., 0.], + [0., 0., 0.], + [1., 2., 3.]]); +} + +#[test] +fn push_row_existing() { + let mut a = Array::zeros((1, 4)); + a.push_row(aview1(&[0., 1., 2., 3.])).unwrap(); + a.push_row(aview1(&[4., 5., 6., 7.])).unwrap(); + assert_eq!(a.shape(), &[3, 4]); + + assert_eq!(a, + array![[0., 0., 0., 0.], + [0., 1., 2., 3.], + [4., 5., 6., 7.]]); + + assert_eq!(a.push_row(aview1(&[1.])), + Err(ShapeError::from_kind(ErrorKind::IncompatibleShape))); + assert_eq!(a.push_column(aview1(&[1.])), + Err(ShapeError::from_kind(ErrorKind::IncompatibleShape))); + assert_eq!(a.push_column(aview1(&[1., 2., 3.])), + Ok(())); + assert_eq!(a, + array![[0., 0., 0., 0., 1.], + [0., 1., 2., 3., 2.], + [4., 5., 6., 7., 3.]]); +} + +#[test] +fn push_row_col_len_1() { + // Test appending 1 row and then cols from shape 1 x 1 + let mut a = Array::zeros((1, 1)); + a.push_row(aview1(&[1.])).unwrap(); // shape 2 x 1 + a.push_column(aview1(&[2., 3.])).unwrap(); // shape 2 x 2 + assert_eq!(a.push_row(aview1(&[1.])), + Err(ShapeError::from_kind(ErrorKind::IncompatibleShape))); + //assert_eq!(a.push_row(aview1(&[1., 2.])), Err(ShapeError::from_kind(ErrorKind::IncompatibleLayout))); + a.push_column(aview1(&[4., 5.])).unwrap(); // shape 2 x 3 + assert_eq!(a.shape(), &[2, 3]); + + assert_eq!(a, + array![[0., 2., 4.], + [1., 3., 5.]]); +} + +#[test] +fn push_column() { + let mut a = Array::zeros((4, 0)); + a.push_column(aview1(&[0., 1., 2., 3.])).unwrap(); + a.push_column(aview1(&[4., 5., 6., 7.])).unwrap(); + assert_eq!(a.shape(), &[4, 2]); + + assert_eq!(a.t(), + array![[0., 1., 2., 3.], + [4., 5., 6., 7.]]); +} + +#[test] +fn append_array1() { + let mut a = Array::zeros((0, 4)); + a.append(Axis(0), aview2(&[[0., 1., 2., 3.]])).unwrap(); + println!("{:?}", a); + a.append(Axis(0), aview2(&[[4., 5., 6., 7.]])).unwrap(); + println!("{:?}", a); + //a.push_column(aview1(&[4., 5., 6., 7.])).unwrap(); + //assert_eq!(a.shape(), &[4, 2]); + + assert_eq!(a, + array![[0., 1., 2., 3.], + [4., 5., 6., 7.]]); + + a.append(Axis(0), aview2(&[[5., 5., 4., 4.], [3., 3., 2., 2.]])).unwrap(); + println!("{:?}", a); + assert_eq!(a, + array![[0., 1., 2., 3.], + [4., 5., 6., 7.], + [5., 5., 4., 4.], + [3., 3., 2., 2.]]); +} + +#[test] +fn append_array_3d() { + let mut a = Array::zeros((0, 2, 2)); + a.append(Axis(0), array![[[0, 1], [2, 3]]].view()).unwrap(); + println!("{:?}", a); + + let aa = array![[[51, 52], [53, 54]], [[55, 56], [57, 58]]]; + let av = aa.view(); + println!("Send {:?} to append", av); + a.append(Axis(0), av.clone()).unwrap(); + + a.swap_axes(0, 1); + let aa = array![[[71, 72], [73, 74]], [[75, 76], [77, 78]]]; + let mut av = aa.view(); + av.swap_axes(0, 1); + println!("Send {:?} to append", av); + a.append(Axis(1), av.clone()).unwrap(); + println!("{:?}", a); + let aa = array![[[81, 82], [83, 84]], [[85, 86], [87, 88]]]; + let mut av = aa.view(); + av.swap_axes(0, 1); + println!("Send {:?} to append", av); + a.append(Axis(1), av).unwrap(); + println!("{:?}", a); + assert_eq!(a, + array![[[0, 1], + [51, 52], + [55, 56], + [71, 72], + [75, 76], + [81, 82], + [85, 86]], + [[2, 3], + [53, 54], + [57, 58], + [73, 74], + [77, 78], + [83, 84], + [87, 88]]]); +} + +#[test] +fn test_append_2d() { + // create an empty array and append + let mut a = Array::zeros((0, 4)); + let ones = ArrayView::from(&[1.; 12]).into_shape_with_order((3, 4)).unwrap(); + let zeros = ArrayView::from(&[0.; 8]).into_shape_with_order((2, 4)).unwrap(); + a.append(Axis(0), ones).unwrap(); + a.append(Axis(0), zeros).unwrap(); + a.append(Axis(0), ones).unwrap(); + println!("{:?}", a); + assert_eq!(a.shape(), &[8, 4]); + for (i, row) in a.rows().into_iter().enumerate() { + let ones = i < 3 || i >= 5; + assert!(row.iter().all(|&x| x == ones as i32 as f64), "failed on lane {}", i); + } + + let mut a = Array::zeros((0, 4)); + a = a.reversed_axes(); + let ones = ones.reversed_axes(); + let zeros = zeros.reversed_axes(); + a.append(Axis(1), ones).unwrap(); + a.append(Axis(1), zeros).unwrap(); + a.append(Axis(1), ones).unwrap(); + println!("{:?}", a); + assert_eq!(a.shape(), &[4, 8]); + + for (i, row) in a.columns().into_iter().enumerate() { + let ones = i < 3 || i >= 5; + assert!(row.iter().all(|&x| x == ones as i32 as f64), "failed on lane {}", i); + } +} + +#[test] +fn test_append_middle_axis() { + // ensure we can append to Axis(1) by letting it become outermost + let mut a = Array::::zeros((3, 0, 2)); + a.append(Axis(1), Array::from_iter(0..12).into_shape_with_order((3, 2, 2)).unwrap().view()).unwrap(); + println!("{:?}", a); + a.append(Axis(1), Array::from_iter(12..24).into_shape_with_order((3, 2, 2)).unwrap().view()).unwrap(); + println!("{:?}", a); + + // ensure we can append to Axis(1) by letting it become outermost + let mut a = Array::::zeros((3, 1, 2)); + a.append(Axis(1), Array::from_iter(0..12).into_shape_with_order((3, 2, 2)).unwrap().view()).unwrap(); + println!("{:?}", a); + a.append(Axis(1), Array::from_iter(12..24).into_shape_with_order((3, 2, 2)).unwrap().view()).unwrap(); + println!("{:?}", a); +} + +#[test] +fn test_append_zero_size() { + { + let mut a = Array::::zeros((0, 0)); + a.append(Axis(0), aview2(&[[]])).unwrap(); + a.append(Axis(0), aview2(&[[]])).unwrap(); + assert_eq!(a.len(), 0); + assert_eq!(a.shape(), &[2, 0]); + } + + { + let mut a = Array::::zeros((0, 0)); + a.append(Axis(1), ArrayView::from(&[]).into_shape_with_order((0, 1)).unwrap()).unwrap(); + a.append(Axis(1), ArrayView::from(&[]).into_shape_with_order((0, 1)).unwrap()).unwrap(); + assert_eq!(a.len(), 0); + assert_eq!(a.shape(), &[0, 2]); + } +} + +#[test] +fn push_row_neg_stride_3() { + let mut a = Array::zeros((0, 4)); + a.push_row(aview1(&[0., 1., 2., 3.])).unwrap(); + a.invert_axis(Axis(1)); + a.push_row(aview1(&[4., 5., 6., 7.])).unwrap(); + assert_eq!(a.shape(), &[2, 4]); + assert_eq!(a, array![[3., 2., 1., 0.], [4., 5., 6., 7.]]); + assert_eq!(a.strides(), &[4, -1]); +} + +#[test] +fn push_row_ignore_strides_length_one_axes() { + let strides = &[0, 1, 10, 20]; + for invert in &[vec![], vec![0], vec![1], vec![0, 1]] { + for &stride0 in strides { + for &stride1 in strides { + let mut a = + Array::from_shape_vec([1, 1].strides([stride0, stride1]), vec![0.]).unwrap(); + for &ax in invert { + a.invert_axis(Axis(ax)); + } + a.push_row(aview1(&[1.])).unwrap(); + assert_eq!(a.shape(), &[2, 1]); + assert_eq!(a, array![[0.], [1.]]); + assert_eq!(a.stride_of(Axis(0)), 1); + } + } + } +} + +#[test] +#[should_panic(expected = "IncompatibleShape")] +fn zero_dimensional_error1() { + let mut a = Array::zeros(()).into_dyn(); + a.append(Axis(0), arr0(0).into_dyn().view()).unwrap(); +} + +#[test] +#[should_panic(expected = "IncompatibleShape")] +fn zero_dimensional_error2() { + let mut a = Array::zeros(()).into_dyn(); + a.push(Axis(0), arr0(0).into_dyn().view()).unwrap(); +} + +#[test] +fn zero_dimensional_ok() { + let mut a = Array::zeros(0); + let one = aview0(&1); + let two = aview0(&2); + a.push(Axis(0), two).unwrap(); + a.push(Axis(0), one).unwrap(); + a.push(Axis(0), one).unwrap(); + assert_eq!(a, array![2, 1, 1]); +} diff --git a/tests/array-construct.rs b/tests/array-construct.rs index 2b72ab039..a3949fcab 100644 --- a/tests/array-construct.rs +++ b/tests/array-construct.rs @@ -7,6 +7,7 @@ use defmac::defmac; use ndarray::prelude::*; +use ndarray::arr3; use ndarray::Zip; #[test] @@ -59,7 +60,7 @@ fn test_uninit() { assert_eq!(a.dim(), (3, 4)); assert_eq!(a.strides(), &[1, 3]); let b = Array::::linspace(0., 25., a.len()) - .into_shape(a.dim()) + .into_shape_with_order(a.dim()) .unwrap(); a.assign(&b); assert_eq!(&a, &b); @@ -164,6 +165,43 @@ fn test_ones() { assert_eq!(a, b); } +#[test] +fn test_from_shape_empty_with_neg_stride() { + // Issue #998, negative strides for an axis where it doesn't matter. + let s = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; + let v = s[..12].to_vec(); + let v_ptr = v.as_ptr(); + let a = Array::from_shape_vec((2, 0, 2).strides((1, -4isize as usize, 2)), v).unwrap(); + assert_eq!(a, arr3(&[[[0; 2]; 0]; 2])); + assert_eq!(a.as_ptr(), v_ptr); +} + +#[test] +fn test_from_shape_with_neg_stride() { + // Issue #998, negative strides for an axis where it doesn't matter. + let s = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; + let v = s[..12].to_vec(); + let v_ptr = v.as_ptr(); + let a = Array::from_shape_vec((2, 1, 2).strides((1, -4isize as usize, 2)), v).unwrap(); + assert_eq!(a, arr3(&[[[0, 2]], + [[1, 3]]])); + assert_eq!(a.as_ptr(), v_ptr); +} + +#[test] +fn test_from_shape_2_2_2_with_neg_stride() { + // Issue #998, negative strides for an axis where it doesn't matter. + let s = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; + let v = s[..12].to_vec(); + let v_ptr = v.as_ptr(); + let a = Array::from_shape_vec((2, 2, 2).strides((1, -4isize as usize, 2)), v).unwrap(); + assert_eq!(a, arr3(&[[[4, 6], + [0, 2]], + [[5, 7], + [1, 3]]])); + assert_eq!(a.as_ptr(), v_ptr.wrapping_add(4)); +} + #[should_panic] #[test] fn deny_wraparound_zeros() { @@ -176,7 +214,7 @@ fn deny_wraparound_zeros() { fn deny_wraparound_reshape() { //2^64 + 5 = 18446744073709551621 = 3×7×29×36760123×823996703 (5 distinct prime factors) let five = Array::::zeros(5); - let _five_large = five.into_shape((3, 7, 29, 36760123, 823996703)).unwrap(); + let _five_large = five.into_shape_with_order((3, 7, 29, 36760123, 823996703)).unwrap(); } #[should_panic] @@ -206,6 +244,29 @@ fn deny_wraparound_uninit() { let _five_large = Array::::uninit((3, 7, 29, 36760123, 823996703)); } +#[should_panic] +#[test] +fn deny_slice_with_too_many_rows_to_arrayview2() { + let _view = ArrayView2::from(&[[0u8; 0]; usize::MAX][..]); +} + +#[should_panic] +#[test] +fn deny_slice_with_too_many_zero_sized_elems_to_arrayview2() { + let _view = ArrayView2::from(&[[(); isize::MAX as usize]; isize::MAX as usize][..]); +} + +#[should_panic] +#[test] +fn deny_slice_with_too_many_rows_to_arrayviewmut2() { + let _view = ArrayViewMut2::from(&mut [[0u8; 0]; usize::MAX][..]); +} + +#[should_panic] +#[test] +fn deny_slice_with_too_many_zero_sized_elems_to_arrayviewmut2() { + let _view = ArrayViewMut2::from(&mut [[(); isize::MAX as usize]; isize::MAX as usize][..]); +} #[test] fn maybe_uninit_1() { diff --git a/tests/array.rs b/tests/array.rs index 9e45d161f..561284869 100644 --- a/tests/array.rs +++ b/tests/array.rs @@ -7,12 +7,15 @@ clippy::float_cmp )] +use approx::assert_relative_eq; use defmac::defmac; -use itertools::{enumerate, zip, Itertools}; +#[allow(deprecated)] +use itertools::{zip, Itertools}; use ndarray::prelude::*; use ndarray::{arr3, rcarr2}; use ndarray::indices; use ndarray::{Slice, SliceInfo, SliceInfoElem}; +use num_complex::Complex; use std::convert::TryFrom; macro_rules! assert_panics { @@ -71,7 +74,7 @@ fn arrayviewmut_shrink_lifetime<'a, 'b: 'a>( fn test_mat_mul() { // smoke test, a big matrix multiplication of uneven size let (n, m) = (45, 33); - let a = ArcArray::linspace(0., ((n * m) - 1) as f32, n as usize * m as usize).reshape((n, m)); + let a = ArcArray::linspace(0., ((n * m) - 1) as f32, n as usize * m as usize).into_shape_with_order((n, m)).unwrap(); let b = ArcArray::eye(m); assert_eq!(a.dot(&b), a); let c = ArcArray::eye(n); @@ -146,7 +149,7 @@ fn test_slice_with_many_dim() { [A[&[0, 0, 0, 0, 0, 1, 0][..]], A[&[0, 0, 2, 0, 0, 1, 0][..]]], [A[&[1, 0, 0, 0, 0, 1, 0][..]], A[&[1, 0, 2, 0, 0, 1, 0][..]]] ] - .into_shape(new_shape) + .into_shape_with_order(new_shape) .unwrap(); assert_eq!(vi, correct); @@ -401,7 +404,7 @@ fn test_multislice() { }; } - let mut arr = Array1::from_iter(0..48).into_shape((8, 6)).unwrap(); + let mut arr = Array1::from_iter(0..48).into_shape_with_order((8, 6)).unwrap(); assert_eq!( (arr.clone().view_mut(),), @@ -497,12 +500,14 @@ fn test_index() { *elt = i; } + #[allow(deprecated)] for ((i, j), a) in zip(indices((2, 3)), &A) { assert_eq!(*a, A[[i, j]]); } let vi = A.slice(s![1.., ..;2]); let mut it = vi.iter(); + #[allow(deprecated)] for ((i, j), x) in zip(indices((1, 2)), &mut it) { assert_eq!(*x, vi[[i, j]]); } @@ -513,9 +518,9 @@ fn test_index() { fn test_index_arrays() { let a = Array1::from_iter(0..12); assert_eq!(a[1], a[[1]]); - let v = a.view().into_shape((3, 4)).unwrap(); + let v = a.view().into_shape_with_order((3, 4)).unwrap(); assert_eq!(a[1], v[[0, 1]]); - let w = v.into_shape((2, 2, 3)).unwrap(); + let w = v.into_shape_with_order((2, 2, 3)).unwrap(); assert_eq!(a[1], w[[0, 0, 1]]); } @@ -537,7 +542,7 @@ fn test_add() { #[test] fn test_multidim() { - let mut mat = ArcArray::zeros(2 * 3 * 4 * 5 * 6).reshape((2, 3, 4, 5, 6)); + let mut mat = ArcArray::zeros(2 * 3 * 4 * 5 * 6).into_shape_with_order((2, 3, 4, 5, 6)).unwrap(); mat[(0, 0, 0, 0, 0)] = 22u8; { for (i, elt) in mat.iter_mut().enumerate() { @@ -599,7 +604,7 @@ fn test_cow() { assert_eq!(n[[0, 0]], 1); assert_eq!(n[[0, 1]], 0); assert_eq!(n.get((0, 1)), Some(&0)); - let mut rev = mat.reshape(4); + let mut rev = mat.into_shape_with_order(4).unwrap(); rev.slice_collapse(s![..;-1]); assert_eq!(rev[0], 4); assert_eq!(rev[1], 3); @@ -638,7 +643,7 @@ fn test_cow_shrink() { assert_eq!(n[[0, 1]], 0); assert_eq!(n.get((0, 1)), Some(&0)); // small has non-C strides this way - let mut small = mat.reshape(6); + let mut small = mat.into_shape_with_order(6).unwrap(); small.slice_collapse(s![4..;-1]); assert_eq!(small[0], 6); assert_eq!(small[1], 5); @@ -655,14 +660,14 @@ fn test_cow_shrink() { #[test] #[cfg(feature = "std")] fn test_sub() { - let mat = ArcArray::linspace(0., 15., 16).reshape((2, 4, 2)); + let mat = ArcArray::linspace(0., 15., 16).into_shape_with_order((2, 4, 2)).unwrap(); let s1 = mat.index_axis(Axis(0), 0); let s2 = mat.index_axis(Axis(0), 1); assert_eq!(s1.shape(), &[4, 2]); assert_eq!(s2.shape(), &[4, 2]); - let n = ArcArray::linspace(8., 15., 8).reshape((4, 2)); + let n = ArcArray::linspace(8., 15., 8).into_shape_with_order((4, 2)).unwrap(); assert_eq!(n, s2); - let m = ArcArray::from(vec![2., 3., 10., 11.]).reshape((2, 2)); + let m = ArcArray::from(vec![2., 3., 10., 11.]).into_shape_with_order((2, 2)).unwrap(); assert_eq!(m, mat.index_axis(Axis(1), 1)); } @@ -670,7 +675,7 @@ fn test_sub() { #[test] #[cfg(feature = "std")] fn test_sub_oob_1() { - let mat = ArcArray::linspace(0., 15., 16).reshape((2, 4, 2)); + let mat = ArcArray::linspace(0., 15., 16).into_shape_with_order((2, 4, 2)).unwrap(); mat.index_axis(Axis(0), 2); } @@ -709,6 +714,19 @@ fn test_select() { assert_abs_diff_eq!(c, c_target); } +#[test] +fn test_select_1d() { + let x = arr1(&[0, 1, 2, 3, 4, 5, 6]); + let r1 = x.select(Axis(0), &[1, 3, 4, 2, 2, 5]); + assert_eq!(r1, arr1(&[1, 3, 4, 2, 2, 5])); + // select nothing + let r2 = x.select(Axis(0), &[]); + assert_eq!(r2, arr1(&[])); + // select nothing from empty + let r3 = r2.select(Axis(0), &[]); + assert_eq!(r3, arr1(&[])); +} + #[test] fn diag() { let d = arr2(&[[1., 2., 3.0f32]]).into_diag(); @@ -716,7 +734,7 @@ fn diag() { let a = arr2(&[[1., 2., 3.0f32], [0., 0., 0.]]); let d = a.view().into_diag(); assert_eq!(d.dim(), 2); - let d = arr2::(&[[]]).into_diag(); + let d = arr2::(&[[]]).into_diag(); assert_eq!(d.dim(), 0); let d = ArcArray::::zeros(()).into_diag(); assert_eq!(d.dim(), 1); @@ -837,7 +855,7 @@ fn permuted_axes() { let permuted = a.view().permuted_axes([0]); assert_eq!(a, permuted); - let a = Array::from_iter(0..24).into_shape((2, 3, 4)).unwrap(); + let a = Array::from_iter(0..24).into_shape_with_order((2, 3, 4)).unwrap(); let permuted = a.view().permuted_axes([2, 1, 0]); for ((i0, i1, i2), elem) in a.indexed_iter() { assert_eq!(*elem, permuted[(i2, i1, i0)]); @@ -847,7 +865,7 @@ fn permuted_axes() { assert_eq!(*elem, permuted[&[i0, i2, i1][..]]); } - let a = Array::from_iter(0..120).into_shape((2, 3, 4, 5)).unwrap(); + let a = Array::from_iter(0..120).into_shape_with_order((2, 3, 4, 5)).unwrap(); let permuted = a.view().permuted_axes([1, 0, 3, 2]); for ((i0, i1, i2, i3), elem) in a.indexed_iter() { assert_eq!(*elem, permuted[(i1, i0, i3, i2)]); @@ -861,7 +879,7 @@ fn permuted_axes() { #[should_panic] #[test] fn permuted_axes_repeated_axis() { - let a = Array::from_iter(0..24).into_shape((2, 3, 4)).unwrap(); + let a = Array::from_iter(0..24).into_shape_with_order((2, 3, 4)).unwrap(); a.view().permuted_axes([1, 0, 1]); } @@ -869,7 +887,7 @@ fn permuted_axes_repeated_axis() { #[test] fn permuted_axes_missing_axis() { let a = Array::from_iter(0..24) - .into_shape((2, 3, 4)) + .into_shape_with_order((2, 3, 4)) .unwrap() .into_dyn(); a.view().permuted_axes(&[2, 0][..]); @@ -878,7 +896,7 @@ fn permuted_axes_missing_axis() { #[should_panic] #[test] fn permuted_axes_oob() { - let a = Array::from_iter(0..24).into_shape((2, 3, 4)).unwrap(); + let a = Array::from_iter(0..24).into_shape_with_order((2, 3, 4)).unwrap(); a.view().permuted_axes([1, 0, 3]); } @@ -945,7 +963,7 @@ fn zero_axes() { a.map_inplace(|_| panic!()); a.for_each(|_| panic!()); println!("{:?}", a); - let b = arr2::(&[[], [], [], []]); + let b = arr2::(&[[], [], [], []]); println!("{:?}\n{:?}", b.shape(), b); // we can even get a subarray of b @@ -977,8 +995,22 @@ fn map1() { } #[test] -fn as_slice_memory_order() { - // test that mutation breaks sharing +fn mapv_into_any_same_type() { + let a: Array = array![[1., 2., 3.], [4., 5., 6.]]; + let a_plus_one: Array = array![[2., 3., 4.], [5., 6., 7.]]; + assert_eq!(a.mapv_into_any(|a| a + 1.), a_plus_one); +} + +#[test] +fn mapv_into_any_diff_types() { + let a: Array = array![[1., 2., 3.], [4., 5., 6.]]; + let a_even: Array = array![[false, true, false], [true, false, true]]; + assert_eq!(a.mapv_into_any(|a| a.round() as i32 % 2 == 0), a_even); +} + +#[test] +fn as_slice_memory_order_mut_arcarray() { + // Test that mutation breaks sharing for `ArcArray`. let a = rcarr2(&[[1., 2.], [3., 4.0f32]]); let mut b = a.clone(); for elt in b.as_slice_memory_order_mut().unwrap() { @@ -987,6 +1019,74 @@ fn as_slice_memory_order() { assert!(a != b, "{:?} != {:?}", a, b); } +#[test] +fn as_slice_memory_order_mut_cowarray() { + // Test that mutation breaks sharing for `CowArray`. + let a = arr2(&[[1., 2.], [3., 4.0f32]]); + let mut b = CowArray::from(a.view()); + for elt in b.as_slice_memory_order_mut().unwrap() { + *elt = 0.; + } + assert!(a != b, "{:?} != {:?}", a, b); +} + +#[test] +fn as_slice_memory_order_mut_contiguous_arcarray() { + // Test that unsharing preserves the strides in the contiguous case for `ArcArray`. + let a = rcarr2(&[[0, 5], [1, 6], [2, 7], [3, 8], [4, 9]]).reversed_axes(); + let mut b = a.clone().slice_move(s![.., ..2]); + assert_eq!(b.strides(), &[1, 2]); + b.as_slice_memory_order_mut().unwrap(); + assert_eq!(b.strides(), &[1, 2]); +} + +#[test] +fn as_slice_memory_order_mut_contiguous_cowarray() { + // Test that unsharing preserves the strides in the contiguous case for `CowArray`. + let a = arr2(&[[0, 5], [1, 6], [2, 7], [3, 8], [4, 9]]).reversed_axes(); + let mut b = CowArray::from(a.slice(s![.., ..2])); + assert!(b.is_view()); + assert_eq!(b.strides(), &[1, 2]); + b.as_slice_memory_order_mut().unwrap(); + assert_eq!(b.strides(), &[1, 2]); +} + +#[test] +fn to_slice_memory_order() { + for shape in vec![[2, 0, 3, 5], [2, 1, 3, 5], [2, 4, 3, 5]] { + let data: Vec = (0..shape.iter().product()).collect(); + let mut orig = Array1::from(data.clone()).into_shape_with_order(shape).unwrap(); + for perm in vec![[0, 1, 2, 3], [0, 2, 1, 3], [2, 0, 1, 3]] { + let mut a = orig.view_mut().permuted_axes(perm); + assert_eq!(a.as_slice_memory_order().unwrap(), &data); + assert_eq!(a.as_slice_memory_order_mut().unwrap(), &data); + assert_eq!(a.view().to_slice_memory_order().unwrap(), &data); + assert_eq!(a.view_mut().into_slice_memory_order().unwrap(), &data); + } + } +} + +#[test] +fn to_slice_memory_order_discontiguous() { + let mut orig = Array3::::zeros([3, 2, 4]); + assert!(orig + .slice(s![.., 1.., ..]) + .as_slice_memory_order() + .is_none()); + assert!(orig + .slice_mut(s![.., 1.., ..]) + .as_slice_memory_order_mut() + .is_none()); + assert!(orig + .slice(s![.., 1.., ..]) + .to_slice_memory_order() + .is_none()); + assert!(orig + .slice_mut(s![.., 1.., ..]) + .into_slice_memory_order() + .is_none()); +} + #[test] fn array0_into_scalar() { // With this kind of setup, the `Array`'s pointer is not the same as the @@ -1237,7 +1337,7 @@ fn from_vec_dim_stride_2d_rejects() { #[test] fn views() { - let a = ArcArray::from(vec![1, 2, 3, 4]).reshape((2, 2)); + let a = ArcArray::from(vec![1, 2, 3, 4]).into_shape_with_order((2, 2)).unwrap(); let b = a.view(); assert_eq!(a, b); assert_eq!(a.shape(), b.shape()); @@ -1254,7 +1354,7 @@ fn views() { #[test] fn view_mut() { - let mut a = ArcArray::from(vec![1, 2, 3, 4]).reshape((2, 2)); + let mut a = ArcArray::from(vec![1, 2, 3, 4]).into_shape_with_order((2, 2)).unwrap(); for elt in &mut a.view_mut() { *elt = 0; } @@ -1273,7 +1373,7 @@ fn view_mut() { #[test] fn slice_mut() { - let mut a = ArcArray::from(vec![1, 2, 3, 4]).reshape((2, 2)); + let mut a = ArcArray::from(vec![1, 2, 3, 4]).into_shape_with_order((2, 2)).unwrap(); for elt in a.slice_mut(s![.., ..]) { *elt = 0; } @@ -1324,7 +1424,7 @@ fn aview() { fn aview_mut() { let mut data = [0; 16]; { - let mut a = aview_mut1(&mut data).into_shape((4, 4)).unwrap(); + let mut a = aview_mut1(&mut data).into_shape_with_order((4, 4)).unwrap(); { let mut slc = a.slice_mut(s![..2, ..;2]); slc += 1; @@ -1357,64 +1457,6 @@ fn transpose_view_mut() { assert_eq!(at, arr2(&[[1, 4], [2, 5], [3, 7]])); } -#[test] -fn reshape() { - let data = [1, 2, 3, 4, 5, 6, 7, 8]; - let v = aview1(&data); - let u = v.into_shape((3, 3)); - assert!(u.is_err()); - let u = v.into_shape((2, 2, 2)); - assert!(u.is_ok()); - let u = u.unwrap(); - assert_eq!(u.shape(), &[2, 2, 2]); - let s = u.into_shape((4, 2)).unwrap(); - assert_eq!(s.shape(), &[4, 2]); - assert_eq!(s, aview2(&[[1, 2], [3, 4], [5, 6], [7, 8]])); -} - -#[test] -#[should_panic(expected = "IncompatibleShape")] -fn reshape_error1() { - let data = [1, 2, 3, 4, 5, 6, 7, 8]; - let v = aview1(&data); - let _u = v.into_shape((2, 5)).unwrap(); -} - -#[test] -#[should_panic(expected = "IncompatibleLayout")] -fn reshape_error2() { - let data = [1, 2, 3, 4, 5, 6, 7, 8]; - let v = aview1(&data); - let mut u = v.into_shape((2, 2, 2)).unwrap(); - u.swap_axes(0, 1); - let _s = u.into_shape((2, 4)).unwrap(); -} - -#[test] -fn reshape_f() { - let mut u = Array::zeros((3, 4).f()); - for (i, elt) in enumerate(u.as_slice_memory_order_mut().unwrap()) { - *elt = i as i32; - } - let v = u.view(); - println!("{:?}", v); - - // noop ok - let v2 = v.into_shape((3, 4)); - assert!(v2.is_ok()); - assert_eq!(v, v2.unwrap()); - - let u = v.into_shape((3, 2, 2)); - assert!(u.is_ok()); - let u = u.unwrap(); - println!("{:?}", u); - assert_eq!(u.shape(), &[3, 2, 2]); - let s = u.into_shape((4, 3)).unwrap(); - println!("{:?}", s); - assert_eq!(s.shape(), &[4, 3]); - assert_eq!(s, aview2(&[[0, 4, 8], [1, 5, 9], [2, 6, 10], [3, 7, 11]])); -} - #[test] #[allow(clippy::cognitive_complexity)] fn insert_axis() { @@ -1638,7 +1680,7 @@ fn arithmetic_broadcast() { #[test] fn char_array() { // test compilation & basics of non-numerical array - let cc = ArcArray::from_iter("alphabet".chars()).reshape((4, 2)); + let cc = ArcArray::from_iter("alphabet".chars()).into_shape_with_order((4, 2)).unwrap(); assert!(cc.index_axis(Axis(1), 0) == ArcArray::from_iter("apae".chars())); } @@ -1698,7 +1740,7 @@ fn split_at() { } assert_eq!(a, arr2(&[[1., 5.], [8., 4.]])); - let b = ArcArray::linspace(0., 59., 60).reshape((3, 4, 5)); + let b = ArcArray::linspace(0., 59., 60).into_shape_with_order((3, 4, 5)).unwrap(); let (left, right) = b.view().split_at(Axis(2), 2); assert_eq!(left.shape(), [3, 4, 2]); @@ -1833,11 +1875,38 @@ fn map_memory_order() { assert_eq!(amap.strides(), v.strides()); } +#[test] +fn map_mut_with_unsharing() { + // Fortran-layout `ArcArray`. + let a = rcarr2(&[[0, 5], [1, 6], [2, 7], [3, 8], [4, 9]]).reversed_axes(); + assert_eq!(a.shape(), &[2, 5]); + assert_eq!(a.strides(), &[1, 2]); + assert_eq!( + a.as_slice_memory_order(), + Some(&[0, 5, 1, 6, 2, 7, 3, 8, 4, 9][..]) + ); + + // Shared reference of a portion of `a`. + let mut b = a.clone().slice_move(s![.., ..2]); + assert_eq!(b.shape(), &[2, 2]); + assert_eq!(b.strides(), &[1, 2]); + assert_eq!(b.as_slice_memory_order(), Some(&[0, 5, 1, 6][..])); + assert_eq!(b, array![[0, 1], [5, 6]]); + + // `.map_mut()` unshares the data. Earlier versions of `ndarray` failed + // this assertion. See #1018. + assert_eq!(b.map_mut(|&mut x| x + 10), array![[10, 11], [15, 16]]); + + // The strides should be preserved. + assert_eq!(b.shape(), &[2, 2]); + assert_eq!(b.strides(), &[1, 2]); +} + #[test] fn test_view_from_shape() { let s = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; let a = ArrayView::from_shape((2, 3, 2), &s).unwrap(); - let mut answer = Array::from(s.to_vec()).into_shape((2, 3, 2)).unwrap(); + let mut answer = Array::from(s.to_vec()).into_shape_with_order((2, 3, 2)).unwrap(); assert_eq!(a, answer); // custom strides (row major) @@ -1888,6 +1957,22 @@ fn test_contiguous() { assert!(b.as_slice_memory_order().is_some()); } +#[test] +fn test_contiguous_single_element() +{ + assert_matches!(array![1].as_slice_memory_order(), Some(&[1])); + + let arr1 = array![1, 2, 3]; + assert_matches!(arr1.slice(s![0..1]).as_slice_memory_order(), Some(&[1])); + assert_matches!(arr1.slice(s![1..2]).as_slice_memory_order(), Some(&[2])); + assert_matches!(arr1.slice(s![2..3]).as_slice_memory_order(), Some(&[3])); + assert_matches!(arr1.slice(s![0..0]).as_slice_memory_order(), Some(&[])); + + let arr2 = array![[1, 2, 3], [4, 5, 6]]; + assert_matches!(arr2.slice(s![.., 2..3]).as_slice_memory_order(), None); + assert_matches!(arr2.slice(s![1, 2..3]).as_slice_memory_order(), Some(&[6])); +} + #[test] fn test_contiguous_neg_strides() { let s = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]; @@ -2155,7 +2240,7 @@ fn test_array_clone_unalias() { #[test] fn test_array_clone_same_view() { - let mut a = Array::from_iter(0..9).into_shape((3, 3)).unwrap(); + let mut a = Array::from_iter(0..9).into_shape_with_order((3, 3)).unwrap(); a.slice_collapse(s![..;-1, ..;-1]); let b = a.clone(); assert_eq!(a, b); @@ -2397,3 +2482,147 @@ mod array_cow_tests { }); } } + +#[test] +fn test_remove_index() { + let mut a = arr2(&[[1, 2, 3], + [4, 5, 6], + [7, 8, 9], + [10,11,12]]); + a.remove_index(Axis(0), 1); + a.remove_index(Axis(1), 2); + assert_eq!(a.shape(), &[3, 2]); + assert_eq!(a, + array![[1, 2], + [7, 8], + [10,11]]); + + let mut a = arr2(&[[1, 2, 3], + [4, 5, 6], + [7, 8, 9], + [10,11,12]]); + a.invert_axis(Axis(0)); + a.remove_index(Axis(0), 1); + a.remove_index(Axis(1), 2); + assert_eq!(a.shape(), &[3, 2]); + assert_eq!(a, + array![[10,11], + [4, 5], + [1, 2]]); + + a.remove_index(Axis(1), 1); + + assert_eq!(a.shape(), &[3, 1]); + assert_eq!(a, + array![[10], + [4], + [1]]); + a.remove_index(Axis(1), 0); + assert_eq!(a.shape(), &[3, 0]); + assert_eq!(a, + array![[], + [], + []]); +} + +#[should_panic(expected="must be less")] +#[test] +fn test_remove_index_oob1() { + let mut a = arr2(&[[1, 2, 3], + [4, 5, 6], + [7, 8, 9], + [10,11,12]]); + a.remove_index(Axis(0), 4); +} + +#[should_panic(expected="must be less")] +#[test] +fn test_remove_index_oob2() { + let mut a = array![[10], [4], [1]]; + a.remove_index(Axis(1), 0); + assert_eq!(a.shape(), &[3, 0]); + assert_eq!(a, + array![[], + [], + []]); + a.remove_index(Axis(0), 1); // ok + assert_eq!(a, + array![[], + []]); + a.remove_index(Axis(1), 0); // oob +} + +#[should_panic(expected="index out of bounds")] +#[test] +fn test_remove_index_oob3() { + let mut a = array![[10], [4], [1]]; + a.remove_index(Axis(2), 0); +} + +#[test] +fn test_split_complex_view() { + let a = Array3::from_shape_fn((3, 4, 5), |(i, j, k)| { + Complex::::new(i as f32 * j as f32, k as f32) + }); + let Complex { re, im } = a.view().split_complex(); + assert_relative_eq!(re.sum(), 90.); + assert_relative_eq!(im.sum(), 120.); +} + +#[test] +fn test_split_complex_view_roundtrip() { + let a_re = Array3::from_shape_fn((3,1,5), |(i, j, _k)| { + i * j + }); + let a_im = Array3::from_shape_fn((3,1,5), |(_i, _j, k)| { + k + }); + let a = Array3::from_shape_fn((3,1,5), |(i,j,k)| { + Complex::new(a_re[[i,j,k]], a_im[[i,j,k]]) + }); + let Complex { re, im } = a.view().split_complex(); + assert_eq!(a_re, re); + assert_eq!(a_im, im); +} + +#[test] +fn test_split_complex_view_mut() { + let eye_scalar = Array2::::eye(4); + let eye_complex = Array2::>::eye(4); + let mut a = Array2::>::zeros((4, 4)); + let Complex { mut re, im } = a.view_mut().split_complex(); + re.assign(&eye_scalar); + assert_eq!(im.sum(), 0); + assert_eq!(a, eye_complex); +} + +#[test] +fn test_split_complex_zerod() { + let mut a = Array0::from_elem((), Complex::new(42, 32)); + let Complex { re, im } = a.view().split_complex(); + assert_eq!(re.get(()), Some(&42)); + assert_eq!(im.get(()), Some(&32)); + let cmplx = a.view_mut().split_complex(); + cmplx.re.assign_to(cmplx.im); + assert_eq!(a.get(()).unwrap().im, 42); +} + +#[test] +fn test_split_complex_permuted() { + let a = Array3::from_shape_fn((3, 4, 5), |(i, j, k)| { + Complex::new(i * k + j, k) + }); + let permuted = a.view().permuted_axes([1,0,2]); + let Complex { re, im } = permuted.split_complex(); + assert_eq!(re.get((3,2,4)).unwrap(), &11); + assert_eq!(im.get((3,2,4)).unwrap(), &4); +} + +#[test] +fn test_split_complex_invert_axis() { + let mut a = Array::from_shape_fn((2, 3, 2), |(i, j, k)| Complex::new(i as f64 + j as f64, i as f64 + k as f64)); + a.invert_axis(Axis(1)); + let cmplx = a.view().split_complex(); + assert_eq!(cmplx.re, a.mapv(|z| z.re)); + assert_eq!(cmplx.im, a.mapv(|z| z.im)); +} diff --git a/tests/assign.rs b/tests/assign.rs new file mode 100644 index 000000000..5c300943d --- /dev/null +++ b/tests/assign.rs @@ -0,0 +1,266 @@ +use ndarray::prelude::*; + +use std::sync::atomic::{AtomicUsize, Ordering}; + +#[test] +fn assign() { + let mut a = arr2(&[[1., 2.], [3., 4.]]); + let b = arr2(&[[1., 3.], [2., 4.]]); + a.assign(&b); + assert_eq!(a, b); + + /* Test broadcasting */ + a.assign(&ArcArray::zeros(1)); + assert_eq!(a, ArcArray::zeros((2, 2))); + + /* Test other type */ + a.assign(&Array::from_elem((2, 2), 3.)); + assert_eq!(a, ArcArray::from_elem((2, 2), 3.)); + + /* Test mut view */ + let mut a = arr2(&[[1, 2], [3, 4]]); + { + let mut v = a.view_mut(); + v.slice_collapse(s![..1, ..]); + v.fill(0); + } + assert_eq!(a, arr2(&[[0, 0], [3, 4]])); +} + + +#[test] +fn assign_to() { + let mut a = arr2(&[[1., 2.], [3., 4.]]); + let b = arr2(&[[0., 3.], [2., 0.]]); + b.assign_to(&mut a); + assert_eq!(a, b); +} + +#[test] +fn move_into_copy() { + let a = arr2(&[[1., 2.], [3., 4.]]); + let acopy = a.clone(); + let mut b = Array::uninit(a.dim()); + a.move_into_uninit(b.view_mut()); + let b = unsafe { b.assume_init() }; + assert_eq!(acopy, b); + + let a = arr2(&[[1., 2.], [3., 4.]]).reversed_axes(); + let acopy = a.clone(); + let mut b = Array::uninit(a.dim()); + a.move_into_uninit(b.view_mut()); + let b = unsafe { b.assume_init() }; + assert_eq!(acopy, b); +} + +#[test] +fn move_into_owned() { + // Test various memory layouts and holes while moving String elements. + for &use_f_order in &[false, true] { + for &invert_axis in &[0b00, 0b01, 0b10, 0b11] { // bitmask for axis to invert + for &slice in &[false, true] { + let mut a = Array::from_shape_fn((5, 4).set_f(use_f_order), + |idx| format!("{:?}", idx)); + if slice { + a.slice_collapse(s![1..-1, ..;2]); + } + + if invert_axis & 0b01 != 0 { + a.invert_axis(Axis(0)); + } + if invert_axis & 0b10 != 0 { + a.invert_axis(Axis(1)); + } + + let acopy = a.clone(); + let mut b = Array::uninit(a.dim()); + a.move_into_uninit(b.view_mut()); + let b = unsafe { b.assume_init() }; + + assert_eq!(acopy, b); + } + } + } +} + +#[test] +fn move_into_slicing() { + // Count correct number of drops when using move_into_uninit and discontiguous arrays (with holes). + for &use_f_order in &[false, true] { + for &invert_axis in &[0b00, 0b01, 0b10, 0b11] { // bitmask for axis to invert + let counter = DropCounter::default(); + { + let (m, n) = (5, 4); + + let mut a = Array::from_shape_fn((m, n).set_f(use_f_order), |_idx| counter.element()); + a.slice_collapse(s![1..-1, ..;2]); + if invert_axis & 0b01 != 0 { + a.invert_axis(Axis(0)); + } + if invert_axis & 0b10 != 0 { + a.invert_axis(Axis(1)); + } + + let mut b = Array::uninit(a.dim()); + a.move_into_uninit(b.view_mut()); + let b = unsafe { b.assume_init() }; + + let total = m * n; + let dropped_1 = total - (m - 2) * (n - 2); + assert_eq!(counter.created(), total); + assert_eq!(counter.dropped(), dropped_1); + drop(b); + } + counter.assert_drop_count(); + } + } +} + +#[test] +fn move_into_diag() { + // Count correct number of drops when using move_into_uninit and discontiguous arrays (with holes). + for &use_f_order in &[false, true] { + let counter = DropCounter::default(); + { + let (m, n) = (5, 4); + + let a = Array::from_shape_fn((m, n).set_f(use_f_order), |_idx| counter.element()); + let a = a.into_diag(); + + let mut b = Array::uninit(a.dim()); + a.move_into_uninit(b.view_mut()); + let b = unsafe { b.assume_init() }; + + let total = m * n; + let dropped_1 = total - Ord::min(m, n); + assert_eq!(counter.created(), total); + assert_eq!(counter.dropped(), dropped_1); + drop(b); + } + counter.assert_drop_count(); + } +} + +#[test] +fn move_into_0dim() { + // Count correct number of drops when using move_into_uninit and discontiguous arrays (with holes). + for &use_f_order in &[false, true] { + let counter = DropCounter::default(); + { + let (m, n) = (5, 4); + + // slice into a 0-dim array + let a = Array::from_shape_fn((m, n).set_f(use_f_order), |_idx| counter.element()); + let a = a.slice_move(s![2, 2]); + + assert_eq!(a.ndim(), 0); + let mut b = Array::uninit(a.dim()); + a.move_into_uninit(b.view_mut()); + let b = unsafe { b.assume_init() }; + + let total = m * n; + let dropped_1 = total - 1; + assert_eq!(counter.created(), total); + assert_eq!(counter.dropped(), dropped_1); + drop(b); + } + counter.assert_drop_count(); + } +} + +#[test] +fn move_into_empty() { + // Count correct number of drops when using move_into_uninit and discontiguous arrays (with holes). + for &use_f_order in &[false, true] { + let counter = DropCounter::default(); + { + let (m, n) = (5, 4); + + // slice into an empty array; + let a = Array::from_shape_fn((m, n).set_f(use_f_order), |_idx| counter.element()); + let a = a.slice_move(s![..0, 1..1]); + assert!(a.is_empty()); + let mut b = Array::uninit(a.dim()); + a.move_into_uninit(b.view_mut()); + let b = unsafe { b.assume_init() }; + + let total = m * n; + let dropped_1 = total; + assert_eq!(counter.created(), total); + assert_eq!(counter.dropped(), dropped_1); + drop(b); + } + counter.assert_drop_count(); + } +} + +#[test] +fn move_into() { + // Test various memory layouts and holes while moving String elements with move_into + for &use_f_order in &[false, true] { + for &invert_axis in &[0b00, 0b01, 0b10, 0b11] { // bitmask for axis to invert + for &slice in &[false, true] { + let mut a = Array::from_shape_fn((5, 4).set_f(use_f_order), + |idx| format!("{:?}", idx)); + if slice { + a.slice_collapse(s![1..-1, ..;2]); + } + + if invert_axis & 0b01 != 0 { + a.invert_axis(Axis(0)); + } + if invert_axis & 0b10 != 0 { + a.invert_axis(Axis(1)); + } + + let acopy = a.clone(); + let mut b = Array::default(a.dim().set_f(!use_f_order ^ !slice)); + a.move_into(&mut b); + + assert_eq!(acopy, b); + } + } + } +} + + +/// This counter can create elements, and then count and verify +/// the number of which have actually been dropped again. +#[derive(Default)] +struct DropCounter { + created: AtomicUsize, + dropped: AtomicUsize, +} + +struct Element<'a>(&'a AtomicUsize); + +impl DropCounter { + fn created(&self) -> usize { + self.created.load(Ordering::Relaxed) + } + + fn dropped(&self) -> usize { + self.dropped.load(Ordering::Relaxed) + } + + fn element(&self) -> Element<'_> { + self.created.fetch_add(1, Ordering::Relaxed); + Element(&self.dropped) + } + + fn assert_drop_count(&self) { + assert_eq!( + self.created(), + self.dropped(), + "Expected {} dropped elements, but found {}", + self.created(), + self.dropped() + ); + } +} + +impl<'a> Drop for Element<'a> { + fn drop(&mut self) { + self.0.fetch_add(1, Ordering::Relaxed); + } +} diff --git a/tests/azip.rs b/tests/azip.rs index 6f0327f5d..d41c019dd 100644 --- a/tests/azip.rs +++ b/tests/azip.rs @@ -263,7 +263,7 @@ fn test_zip_dim_mismatch_1() { // where A is F-contiguous and B contiguous but neither F nor C contiguous. #[test] fn test_contiguous_but_not_c_or_f() { - let a = Array::from_iter(0..27).into_shape((3, 3, 3)).unwrap(); + let a = Array::from_iter(0..27).into_shape_with_order((3, 3, 3)).unwrap(); // both F order let a = a.reversed_axes(); @@ -287,7 +287,7 @@ fn test_contiguous_but_not_c_or_f() { #[test] fn test_clone() { - let a = Array::from_iter(0..27).into_shape((3, 3, 3)).unwrap(); + let a = Array::from_iter(0..27).into_shape_with_order((3, 3, 3)).unwrap(); let z = Zip::from(&a).and(a.exact_chunks((1, 1, 1))); let w = z.clone(); diff --git a/tests/broadcast.rs b/tests/broadcast.rs index 5416e9017..6dee901e2 100644 --- a/tests/broadcast.rs +++ b/tests/broadcast.rs @@ -5,12 +5,12 @@ use ndarray::prelude::*; fn broadcast_1() { let a_dim = Dim([2, 4, 2, 2]); let b_dim = Dim([2, 1, 2, 1]); - let a = ArcArray::linspace(0., 1., a_dim.size()).reshape(a_dim); - let b = ArcArray::linspace(0., 1., b_dim.size()).reshape(b_dim); + let a = ArcArray::linspace(0., 1., a_dim.size()).into_shape_with_order(a_dim).unwrap(); + let b = ArcArray::linspace(0., 1., b_dim.size()).into_shape_with_order(b_dim).unwrap(); assert!(b.broadcast(a.dim()).is_some()); let c_dim = Dim([2, 1]); - let c = ArcArray::linspace(0., 1., c_dim.size()).reshape(c_dim); + let c = ArcArray::linspace(0., 1., c_dim.size()).into_shape_with_order(c_dim).unwrap(); assert!(c.broadcast(1).is_none()); assert!(c.broadcast(()).is_none()); assert!(c.broadcast((2, 1)).is_some()); @@ -31,8 +31,8 @@ fn broadcast_1() { fn test_add() { let a_dim = Dim([2, 4, 2, 2]); let b_dim = Dim([2, 1, 2, 1]); - let mut a = ArcArray::linspace(0.0, 1., a_dim.size()).reshape(a_dim); - let b = ArcArray::linspace(0.0, 1., b_dim.size()).reshape(b_dim); + let mut a = ArcArray::linspace(0.0, 1., a_dim.size()).into_shape_with_order(a_dim).unwrap(); + let b = ArcArray::linspace(0.0, 1., b_dim.size()).into_shape_with_order(b_dim).unwrap(); a += &b; let t = ArcArray::from_elem((), 1.0f32); a += &t; @@ -43,7 +43,7 @@ fn test_add() { #[cfg(feature = "std")] fn test_add_incompat() { let a_dim = Dim([2, 4, 2, 2]); - let mut a = ArcArray::linspace(0.0, 1., a_dim.size()).reshape(a_dim); + let mut a = ArcArray::linspace(0.0, 1., a_dim.size()).into_shape_with_order(a_dim).unwrap(); let incompat = ArcArray::from_elem(3, 1.0f32); a += &incompat; } diff --git a/tests/dimension.rs b/tests/dimension.rs index 939b4f0e3..5dae5b5a3 100644 --- a/tests/dimension.rs +++ b/tests/dimension.rs @@ -55,14 +55,14 @@ fn remove_axis() { let a = ArcArray::::zeros(vec![4, 5, 6]); let _b = a .index_axis_move(Axis(1), 0) - .reshape((4, 6)) - .reshape(vec![2, 3, 4]); + .to_shape((4, 6)).unwrap() + .to_shape(vec![2, 3, 4]).unwrap(); } #[test] #[allow(clippy::eq_op)] fn dyn_dimension() { - let a = arr2(&[[1., 2.], [3., 4.0]]).into_shape(vec![2, 2]).unwrap(); + let a = arr2(&[[1., 2.], [3., 4.0]]).into_shape_with_order(vec![2, 2]).unwrap(); assert_eq!(&a - &a, Array::zeros(vec![2, 2])); assert_eq!(a[&[0, 0][..]], 1.); assert_eq!(a[[0, 0]], 1.); diff --git a/tests/format.rs b/tests/format.rs index 422fa2957..4b21fe39d 100644 --- a/tests/format.rs +++ b/tests/format.rs @@ -6,7 +6,7 @@ fn formatting() { let a = rcarr1::(&[1., 2., 3., 4.]); assert_eq!(format!("{}", a), "[1, 2, 3, 4]"); assert_eq!(format!("{:4}", a), "[ 1, 2, 3, 4]"); - let a = a.reshape((4, 1, 1)); + let a = a.into_shape_clone((4, 1, 1)).unwrap(); assert_eq!( format!("{}", a), "\ @@ -30,7 +30,7 @@ fn formatting() { [[ 4]]]", ); - let a = a.reshape((2, 2)); + let a = a.into_shape_clone((2, 2)).unwrap(); assert_eq!( format!("{}", a), "\ diff --git a/tests/higher_order_f.rs b/tests/higher_order_f.rs index 1238cc4d8..c567eb3e0 100644 --- a/tests/higher_order_f.rs +++ b/tests/higher_order_f.rs @@ -6,37 +6,3 @@ fn test_fold_axis_oob() { let a = arr2(&[[1., 2.], [3., 4.]]); a.fold_axis(Axis(2), 0., |x, y| x + y); } - -#[test] -fn assign() { - let mut a = arr2(&[[1., 2.], [3., 4.]]); - let b = arr2(&[[1., 3.], [2., 4.]]); - a.assign(&b); - assert_eq!(a, b); - - /* Test broadcasting */ - a.assign(&ArcArray::zeros(1)); - assert_eq!(a, ArcArray::zeros((2, 2))); - - /* Test other type */ - a.assign(&Array::from_elem((2, 2), 3.)); - assert_eq!(a, ArcArray::from_elem((2, 2), 3.)); - - /* Test mut view */ - let mut a = arr2(&[[1, 2], [3, 4]]); - { - let mut v = a.view_mut(); - v.slice_collapse(s![..1, ..]); - v.fill(0); - } - assert_eq!(a, arr2(&[[0, 0], [3, 4]])); -} - - -#[test] -fn assign_to() { - let mut a = arr2(&[[1., 2.], [3., 4.]]); - let b = arr2(&[[0., 3.], [2., 0.]]); - b.assign_to(&mut a); - assert_eq!(a, b); -} diff --git a/tests/indices.rs b/tests/indices.rs index 3e2c0796c..ca6ca9887 100644 --- a/tests/indices.rs +++ b/tests/indices.rs @@ -1,15 +1,16 @@ use ndarray::indices_of; use ndarray::prelude::*; +use ndarray::Order; #[test] fn test_ixdyn_index_iterate() { - for &rev in &[false, true] { - let mut a = Array::zeros((2, 3, 4).set_f(rev)); + for &order in &[Order::C, Order::F] { + let mut a = Array::zeros((2, 3, 4).set_f(order.is_column_major())); let dim = a.shape().to_vec(); for ((i, j, k), elt) in a.indexed_iter_mut() { *elt = i + 10 * j + 100 * k; } - let a = a.into_shape(dim).unwrap(); + let a = a.into_shape_with_order((dim, order)).unwrap(); println!("{:?}", a.dim()); let mut c = 0; for i in indices_of(&a) { diff --git a/tests/iterator_chunks.rs b/tests/iterator_chunks.rs index 67ddd1e38..bf3af2d56 100644 --- a/tests/iterator_chunks.rs +++ b/tests/iterator_chunks.rs @@ -13,7 +13,7 @@ use ndarray::prelude::*; fn chunks() { use ndarray::NdProducer; let a = >::linspace(1., 100., 10 * 10) - .into_shape((10, 10)) + .into_shape_with_order((10, 10)) .unwrap(); let (m, n) = a.dim(); diff --git a/tests/iterators.rs b/tests/iterators.rs index 4e4bbc666..fd0716036 100644 --- a/tests/iterators.rs +++ b/tests/iterators.rs @@ -6,9 +6,11 @@ )] use ndarray::prelude::*; -use ndarray::{arr3, aview1, indices, s, Axis, Slice, Zip}; +use ndarray::{arr3, indices, s, Slice, Zip}; -use itertools::{assert_equal, enumerate}; +use itertools::assert_equal; +use itertools::enumerate; +use std::cell::Cell; macro_rules! assert_panics { ($body:expr) => { @@ -40,7 +42,7 @@ fn double_ended() { #[test] fn iter_size_hint() { // Check that the size hint is correctly computed - let a = ArcArray::from_iter(0..24).reshape((2, 3, 4)); + let a = ArcArray::from_iter(0..24).into_shape_with_order((2, 3, 4)).unwrap(); let mut data = [0; 24]; for (i, elt) in enumerate(&mut data) { *elt = i as i32; @@ -62,7 +64,7 @@ fn indexed() { for (i, elt) in a.indexed_iter() { assert_eq!(i, *elt as usize); } - let a = a.reshape((2, 4, 1)); + let a = a.into_shape_with_order((2, 4, 1)).unwrap(); let (mut i, mut j, k) = (0, 0, 0); for (idx, elt) in a.indexed_iter() { assert_eq!(idx, (i, j, k)); @@ -94,11 +96,11 @@ fn as_slice() { } let a = ArcArray::linspace(0., 7., 8); - let a = a.reshape((2, 4, 1)); + let a = a.into_shape_with_order((2, 4, 1)).unwrap(); assert_slice_correct(&a); - let a = a.reshape((2, 4)); + let a = a.into_shape_with_order((2, 4)).unwrap(); assert_slice_correct(&a); assert!(a.view().index_axis(Axis(1), 0).as_slice().is_none()); @@ -121,7 +123,7 @@ fn as_slice() { assert!(u.as_slice().is_some()); assert_slice_correct(&u); - let a = a.reshape((8, 1)); + let a = a.into_shape_with_order((8, 1)).unwrap(); assert_slice_correct(&a); let u = a.slice(s![..;2, ..]); println!( @@ -136,7 +138,7 @@ fn as_slice() { #[test] fn inner_iter() { let a = ArcArray::from_iter(0..12); - let a = a.reshape((2, 3, 2)); + let a = a.into_shape_with_order((2, 3, 2)).unwrap(); // [[[0, 1], // [2, 3], // [4, 5]], @@ -185,7 +187,7 @@ fn inner_iter_corner_cases() { #[test] fn inner_iter_size_hint() { // Check that the size hint is correctly computed - let a = ArcArray::from_iter(0..24).reshape((2, 3, 4)); + let a = ArcArray::from_iter(0..24).into_shape_with_order((2, 3, 4)).unwrap(); let mut len = 6; let mut it = a.rows().into_iter(); assert_eq!(it.len(), len); @@ -200,7 +202,7 @@ fn inner_iter_size_hint() { #[test] fn outer_iter() { let a = ArcArray::from_iter(0..12); - let a = a.reshape((2, 3, 2)); + let a = a.into_shape_with_order((2, 3, 2)).unwrap(); // [[[0, 1], // [2, 3], // [4, 5]], @@ -259,7 +261,7 @@ fn outer_iter() { #[test] fn axis_iter() { let a = ArcArray::from_iter(0..12); - let a = a.reshape((2, 3, 2)); + let a = a.into_shape_with_order((2, 3, 2)).unwrap(); // [[[0, 1], // [2, 3], // [4, 5]], @@ -351,7 +353,7 @@ fn outer_iter_corner_cases() { #[test] fn outer_iter_mut() { let a = ArcArray::from_iter(0..12); - let a = a.reshape((2, 3, 2)); + let a = a.into_shape_with_order((2, 3, 2)).unwrap(); // [[[0, 1], // [2, 3], // [4, 5]], @@ -378,7 +380,7 @@ fn outer_iter_mut() { #[test] fn axis_iter_mut() { let a = ArcArray::from_iter(0..12); - let a = a.reshape((2, 3, 2)); + let a = a.into_shape_with_order((2, 3, 2)).unwrap(); // [[[0, 1], // [2, 3], // [4, 5]], @@ -398,7 +400,7 @@ fn axis_iter_mut() { #[test] fn axis_chunks_iter() { let a = ArcArray::from_iter(0..24); - let a = a.reshape((2, 6, 2)); + let a = a.into_shape_with_order((2, 6, 2)).unwrap(); let it = a.axis_chunks_iter(Axis(1), 2); assert_equal( @@ -411,7 +413,7 @@ fn axis_chunks_iter() { ); let a = ArcArray::from_iter(0..28); - let a = a.reshape((2, 7, 2)); + let a = a.into_shape_with_order((2, 7, 2)).unwrap(); let it = a.axis_chunks_iter(Axis(1), 2); assert_equal( @@ -530,10 +532,10 @@ fn axis_iter_mut_zip_partially_consumed_discontiguous() { fn axis_chunks_iter_corner_cases() { // examples provided by @bluss in PR #65 // these tests highlight corner cases of the axis_chunks_iter implementation - // and enable checking if no pointer offseting is out of bounds. However - // checking the absence of of out of bounds offseting cannot (?) be + // and enable checking if no pointer offsetting is out of bounds. However + // checking the absence of of out of bounds offsetting cannot (?) be // done automatically, so one has to launch this test in a debugger. - let a = ArcArray::::linspace(0., 7., 8).reshape((8, 1)); + let a = ArcArray::::linspace(0., 7., 8).into_shape_with_order((8, 1)).unwrap(); let it = a.axis_chunks_iter(Axis(0), 4); assert_equal(it, vec![a.slice(s![..4, ..]), a.slice(s![4.., ..])]); let a = a.slice(s![..;-1,..]); @@ -562,7 +564,7 @@ fn axis_chunks_iter_corner_cases() { fn axis_chunks_iter_zero_stride() { { // stride 0 case - let b = Array::from(vec![0f32; 0]).into_shape((5, 0, 3)).unwrap(); + let b = Array::from(vec![0f32; 0]).into_shape_with_order((5, 0, 3)).unwrap(); let shapes: Vec<_> = b .axis_chunks_iter(Axis(0), 2) .map(|v| v.raw_dim()) @@ -572,7 +574,7 @@ fn axis_chunks_iter_zero_stride() { { // stride 0 case reverse - let b = Array::from(vec![0f32; 0]).into_shape((5, 0, 3)).unwrap(); + let b = Array::from(vec![0f32; 0]).into_shape_with_order((5, 0, 3)).unwrap(); let shapes: Vec<_> = b .axis_chunks_iter(Axis(0), 2) .rev() @@ -632,7 +634,7 @@ fn axis_chunks_iter_split_at() { #[test] fn axis_chunks_iter_mut() { let a = ArcArray::from_iter(0..24); - let mut a = a.reshape((2, 6, 2)); + let mut a = a.into_shape_with_order((2, 6, 2)).unwrap(); let mut it = a.axis_chunks_iter_mut(Axis(1), 2); let mut col0 = it.next().unwrap(); @@ -656,7 +658,7 @@ fn axis_chunks_iter_mut_zero_axis_len() { #[test] fn outer_iter_size_hint() { // Check that the size hint is correctly computed - let a = ArcArray::from_iter(0..24).reshape((4, 3, 2)); + let a = ArcArray::from_iter(0..24).into_shape_with_order((4, 3, 2)).unwrap(); let mut len = 4; let mut it = a.outer_iter(); assert_eq!(it.len(), len); @@ -688,7 +690,7 @@ fn outer_iter_size_hint() { #[test] fn outer_iter_split_at() { - let a = ArcArray::from_iter(0..30).reshape((5, 3, 2)); + let a = ArcArray::from_iter(0..30).into_shape_with_order((5, 3, 2)).unwrap(); let it = a.outer_iter(); let (mut itl, mut itr) = it.clone().split_at(2); @@ -710,7 +712,7 @@ fn outer_iter_split_at() { #[test] #[should_panic] fn outer_iter_split_at_panics() { - let a = ArcArray::from_iter(0..30).reshape((5, 3, 2)); + let a = ArcArray::from_iter(0..30).into_shape_with_order((5, 3, 2)).unwrap(); let it = a.outer_iter(); it.split_at(6); @@ -718,7 +720,7 @@ fn outer_iter_split_at_panics() { #[test] fn outer_iter_mut_split_at() { - let mut a = ArcArray::from_iter(0..30).reshape((5, 3, 2)); + let mut a = ArcArray::from_iter(0..30).into_shape_with_order((5, 3, 2)).unwrap(); { let it = a.outer_iter_mut(); @@ -742,7 +744,7 @@ fn iterators_are_send_sync() { // are too. fn _send_sync(_: &T) {} - let mut a = ArcArray::from_iter(0..30).into_shape((5, 3, 2)).unwrap(); + let mut a = ArcArray::from_iter(0..30).into_shape_with_order((5, 3, 2)).unwrap(); _send_sync(&a.view()); _send_sync(&a.view_mut()); @@ -892,3 +894,91 @@ fn test_rfold() { ); } } + +#[test] +fn test_into_iter() { + let a = Array1::from(vec![1, 2, 3, 4]); + let v = a.into_iter().collect::>(); + assert_eq!(v, [1, 2, 3, 4]); +} + +#[test] +fn test_into_iter_2d() { + let a = Array1::from(vec![1, 2, 3, 4]).into_shape_with_order((2, 2)).unwrap(); + let v = a.into_iter().collect::>(); + assert_eq!(v, [1, 2, 3, 4]); + + let a = Array1::from(vec![1, 2, 3, 4]).into_shape_with_order((2, 2)).unwrap().reversed_axes(); + let v = a.into_iter().collect::>(); + assert_eq!(v, [1, 3, 2, 4]); +} + +#[test] +fn test_into_iter_sliced() { + let (m, n) = (4, 5); + let drops = Cell::new(0); + + for i in 0..m - 1 { + for j in 0..n - 1 { + for i2 in i + 1 .. m { + for j2 in j + 1 .. n { + for invert in 0..3 { + drops.set(0); + let i = i as isize; + let j = j as isize; + let i2 = i2 as isize; + let j2 = j2 as isize; + let mut a = Array1::from_iter(0..(m * n) as i32) + .mapv(|v| DropCount::new(v, &drops)) + .into_shape_with_order((m, n)).unwrap(); + a.slice_collapse(s![i..i2, j..j2]); + if invert < a.ndim() { + a.invert_axis(Axis(invert)); + } + + println!("{:?}, {:?}", i..i2, j..j2); + println!("{:?}", a); + let answer = a.iter().cloned().collect::>(); + let v = a.into_iter().collect::>(); + assert_eq!(v, answer); + + assert_eq!(drops.get(), m * n - v.len()); + drop(v); + assert_eq!(drops.get(), m * n); + } + } + } + } + } +} + +/// Helper struct that counts its drops Asserts that it's not dropped twice. Also global number of +/// drops is counted in the cell. +/// +/// Compares equal by its "represented value". +#[derive(Clone, Debug)] +struct DropCount<'a> { + value: i32, + my_drops: usize, + drops: &'a Cell +} + +impl PartialEq for DropCount<'_> { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} + +impl<'a> DropCount<'a> { + fn new(value: i32, drops: &'a Cell) -> Self { + DropCount { value, my_drops: 0, drops } + } +} + +impl Drop for DropCount<'_> { + fn drop(&mut self) { + assert_eq!(self.my_drops, 0); + self.my_drops += 1; + self.drops.set(self.drops.get() + 1); + } +} diff --git a/tests/ixdyn.rs b/tests/ixdyn.rs index 11af2c97a..5b7ef9327 100644 --- a/tests/ixdyn.rs +++ b/tests/ixdyn.rs @@ -10,6 +10,7 @@ use ndarray::Array; use ndarray::IntoDimension; use ndarray::ShapeBuilder; use ndarray::Ix3; +use ndarray::Order; #[test] fn test_ixdyn() { @@ -39,14 +40,14 @@ fn test_ixdyn_out_of_bounds() { #[test] fn test_ixdyn_iterate() { - for &rev in &[false, true] { - let mut a = Array::zeros((2, 3, 4).set_f(rev)); + for &order in &[Order::C, Order::F] { + let mut a = Array::zeros((2, 3, 4).set_f(order.is_column_major())); let dim = a.shape().to_vec(); for (i, elt) in a.iter_mut().enumerate() { *elt = i; } println!("{:?}", a.dim()); - let mut a = a.into_shape(dim).unwrap(); + let mut a = a.into_shape_with_order((dim, order)).unwrap(); println!("{:?}", a.dim()); let mut c = 0; for (i, elt) in a.iter_mut().enumerate() { @@ -59,13 +60,13 @@ fn test_ixdyn_iterate() { #[test] fn test_ixdyn_index_iterate() { - for &rev in &[false, true] { - let mut a = Array::zeros((2, 3, 4).set_f(rev)); + for &order in &[Order::C, Order::F] { + let mut a = Array::zeros((2, 3, 4).set_f(order.is_column_major())); let dim = a.shape().to_vec(); for ((i, j, k), elt) in a.indexed_iter_mut() { *elt = i + 10 * j + 100 * k; } - let a = a.into_shape(dim).unwrap(); + let a = a.into_shape_with_order((dim, order)).unwrap(); println!("{:?}", a.dim()); let mut c = 0; for (i, elt) in a.indexed_iter() { @@ -159,8 +160,8 @@ fn test_0_add_broad() { fn test_into_dimension() { use ndarray::{Ix0, Ix1, Ix2, IxDyn}; - let a = Array::linspace(0., 41., 6 * 7).into_shape((6, 7)).unwrap(); - let a2 = a.clone().into_shape(IxDyn(&[6, 7])).unwrap(); + let a = Array::linspace(0., 41., 6 * 7).into_shape_with_order((6, 7)).unwrap(); + let a2 = a.clone().into_shape_with_order(IxDyn(&[6, 7])).unwrap(); let b = a2.clone().into_dimensionality::().unwrap(); assert_eq!(a, b); diff --git a/tests/oper.rs b/tests/oper.rs index ed612bad2..12e822cb7 100644 --- a/tests/oper.rs +++ b/tests/oper.rs @@ -6,6 +6,7 @@ )] #![cfg(feature = "std")] use ndarray::linalg::general_mat_mul; +use ndarray::linalg::kron; use ndarray::prelude::*; use ndarray::{rcarr1, rcarr2}; use ndarray::{Data, LinalgScalar}; @@ -16,24 +17,24 @@ use approx::assert_abs_diff_eq; use defmac::defmac; fn test_oper(op: &str, a: &[f32], b: &[f32], c: &[f32]) { - let aa = rcarr1(a); - let bb = rcarr1(b); - let cc = rcarr1(c); + let aa = CowArray::from(arr1(a)); + let bb = CowArray::from(arr1(b)); + let cc = CowArray::from(arr1(c)); test_oper_arr::(op, aa.clone(), bb.clone(), cc.clone()); let dim = (2, 2); - let aa = aa.reshape(dim); - let bb = bb.reshape(dim); - let cc = cc.reshape(dim); + let aa = aa.to_shape(dim).unwrap(); + let bb = bb.to_shape(dim).unwrap(); + let cc = cc.to_shape(dim).unwrap(); test_oper_arr::(op, aa.clone(), bb.clone(), cc.clone()); let dim = (1, 2, 1, 2); - let aa = aa.reshape(dim); - let bb = bb.reshape(dim); - let cc = cc.reshape(dim); + let aa = aa.to_shape(dim).unwrap(); + let bb = bb.to_shape(dim).unwrap(); + let cc = cc.to_shape(dim).unwrap(); test_oper_arr::(op, aa.clone(), bb.clone(), cc.clone()); } -fn test_oper_arr(op: &str, mut aa: ArcArray, bb: ArcArray, cc: ArcArray) +fn test_oper_arr(op: &str, mut aa: CowArray, bb: CowArray, cc: CowArray) where D: Dimension, { @@ -65,7 +66,7 @@ where } "neg" => { assert_eq!(-&aa, cc); - assert_eq!(-aa.clone(), cc); + assert_eq!(-aa.into_owned(), cc); } _ => panic!(), } @@ -236,7 +237,7 @@ fn dot_product_neg_stride() { #[test] fn fold_and_sum() { - let a = Array::linspace(0., 127., 128).into_shape((8, 16)).unwrap(); + let a = Array::linspace(0., 127., 128).into_shape_with_order((8, 16)).unwrap(); assert_abs_diff_eq!(a.fold(0., |acc, &x| acc + x), a.sum(), epsilon = 1e-5); // test different strides @@ -275,7 +276,7 @@ fn fold_and_sum() { #[test] fn product() { - let a = Array::linspace(0.5, 2., 128).into_shape((8, 16)).unwrap(); + let a = Array::linspace(0.5, 2., 128).into_shape_with_order((8, 16)).unwrap(); assert_abs_diff_eq!(a.fold(1., |acc, &x| acc * x), a.product(), epsilon = 1e-5); // test different strides @@ -295,13 +296,13 @@ fn product() { fn range_mat(m: Ix, n: Ix) -> Array2 { Array::linspace(0., (m * n) as f32 - 1., m * n) - .into_shape((m, n)) + .into_shape_with_order((m, n)) .unwrap() } fn range_mat64(m: Ix, n: Ix) -> Array2 { Array::linspace(0., (m * n) as f64 - 1., m * n) - .into_shape((m, n)) + .into_shape_with_order((m, n)) .unwrap() } @@ -312,7 +313,7 @@ fn range1_mat64(m: Ix) -> Array1 { fn range_i32(m: Ix, n: Ix) -> Array2 { Array::from_iter(0..(m * n) as i32) - .into_shape((m, n)) + .into_shape_with_order((m, n)) .unwrap() } @@ -592,7 +593,7 @@ fn scaled_add_3() { ] }; - let c = range_mat64(n, q).into_shape(cdim).unwrap(); + let c = range_mat64(n, q).into_shape_with_order(cdim).unwrap(); { let mut av = a.slice_mut(s![..;s1, ..;s2]); @@ -710,8 +711,8 @@ fn gen_mat_vec_mul() { S2: Data, { let ((m, _), k) = (lhs.dim(), rhs.dim()); - reference_mat_mul(lhs, &rhs.as_standard_layout().into_shape((k, 1)).unwrap()) - .into_shape(m) + reference_mat_mul(lhs, &rhs.as_standard_layout().into_shape_with_order((k, 1)).unwrap()) + .into_shape_with_order(m) .unwrap() } @@ -775,8 +776,8 @@ fn vec_mat_mul() { S2: Data, { let (m, (_, n)) = (lhs.dim(), rhs.dim()); - reference_mat_mul(&lhs.as_standard_layout().into_shape((1, m)).unwrap(), rhs) - .into_shape(n) + reference_mat_mul(&lhs.as_standard_layout().into_shape_with_order((1, m)).unwrap(), rhs) + .into_shape_with_order(n) .unwrap() } @@ -820,3 +821,65 @@ fn vec_mat_mul() { } } } + +#[test] +fn kron_square_f64() { + let a = arr2(&[[1.0, 0.0], [0.0, 1.0]]); + let b = arr2(&[[0.0, 1.0], [1.0, 0.0]]); + + assert_eq!( + kron(&a, &b), + arr2(&[ + [0.0, 1.0, 0.0, 0.0], + [1.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + [0.0, 0.0, 1.0, 0.0] + ]), + ); + + assert_eq!( + kron(&b, &a), + arr2(&[ + [0.0, 0.0, 1.0, 0.0], + [0.0, 0.0, 0.0, 1.0], + [1.0, 0.0, 0.0, 0.0], + [0.0, 1.0, 0.0, 0.0] + ]), + ) +} + +#[test] +fn kron_square_i64() { + let a = arr2(&[[1, 0], [0, 1]]); + let b = arr2(&[[0, 1], [1, 0]]); + + assert_eq!( + kron(&a, &b), + arr2(&[[0, 1, 0, 0], [1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]), + ); + + assert_eq!( + kron(&b, &a), + arr2(&[[0, 0, 1, 0], [0, 0, 0, 1], [1, 0, 0, 0], [0, 1, 0, 0]]), + ) +} + +#[test] +fn kron_i64() { + let a = arr2(&[[1, 0]]); + let b = arr2(&[[0, 1], [1, 0]]); + let r = arr2(&[[0, 1, 0, 0], [1, 0, 0, 0]]); + assert_eq!(kron(&a, &b), r); + + let a = arr2(&[[1, 0], [0, 0], [0, 1]]); + let b = arr2(&[[0, 1], [1, 0]]); + let r = arr2(&[ + [0, 1, 0, 0], + [1, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 1], + [0, 0, 1, 0], + ]); + assert_eq!(kron(&a, &b), r); +} diff --git a/tests/par_rayon.rs b/tests/par_rayon.rs index 4d5a8f1a9..40670c6bf 100644 --- a/tests/par_rayon.rs +++ b/tests/par_rayon.rs @@ -25,7 +25,7 @@ fn test_axis_iter() { fn test_axis_iter_mut() { use approx::assert_abs_diff_eq; let mut a = Array::linspace(0., 1.0f64, M * N) - .into_shape((M, N)) + .into_shape_with_order((M, N)) .unwrap(); let b = a.mapv(|x| x.exp()); a.axis_iter_mut(Axis(0)) @@ -77,7 +77,7 @@ fn test_axis_chunks_iter() { fn test_axis_chunks_iter_mut() { use approx::assert_abs_diff_eq; let mut a = Array::linspace(0., 1.0f64, M * N) - .into_shape((M, N)) + .into_shape_with_order((M, N)) .unwrap(); let b = a.mapv(|x| x.exp()); a.axis_chunks_iter_mut(Axis(0), CHUNK_SIZE) diff --git a/tests/reshape.rs b/tests/reshape.rs new file mode 100644 index 000000000..24e7d01f8 --- /dev/null +++ b/tests/reshape.rs @@ -0,0 +1,318 @@ +use ndarray::prelude::*; + +use itertools::enumerate; + +use ndarray::Order; + +#[test] +fn reshape() { + let data = [1, 2, 3, 4, 5, 6, 7, 8]; + let v = aview1(&data); + let u = v.into_shape_with_order((3, 3)); + assert!(u.is_err()); + let u = v.into_shape_with_order((2, 2, 2)); + assert!(u.is_ok()); + let u = u.unwrap(); + assert_eq!(u.shape(), &[2, 2, 2]); + let s = u.into_shape_with_order((4, 2)).unwrap(); + assert_eq!(s.shape(), &[4, 2]); + assert_eq!(s, aview2(&[[1, 2], [3, 4], [5, 6], [7, 8]])); +} + +#[test] +#[should_panic(expected = "IncompatibleShape")] +fn reshape_error1() { + let data = [1, 2, 3, 4, 5, 6, 7, 8]; + let v = aview1(&data); + let _u = v.into_shape_with_order((2, 5)).unwrap(); +} + +#[test] +#[should_panic(expected = "IncompatibleLayout")] +fn reshape_error2() { + let data = [1, 2, 3, 4, 5, 6, 7, 8]; + let v = aview1(&data); + let mut u = v.into_shape_with_order((2, 2, 2)).unwrap(); + u.swap_axes(0, 1); + let _s = u.into_shape_with_order((2, 4)).unwrap(); +} + +#[test] +fn reshape_f() { + let mut u = Array::zeros((3, 4).f()); + for (i, elt) in enumerate(u.as_slice_memory_order_mut().unwrap()) { + *elt = i as i32; + } + let v = u.view(); + println!("{:?}", v); + + // noop ok + let v2 = v.into_shape_with_order(((3, 4), Order::F)); + assert!(v2.is_ok()); + assert_eq!(v, v2.unwrap()); + + let u = v.into_shape_with_order(((3, 2, 2), Order::F)); + assert!(u.is_ok()); + let u = u.unwrap(); + println!("{:?}", u); + assert_eq!(u.shape(), &[3, 2, 2]); + let s = u.into_shape_with_order(((4, 3), Order::F)).unwrap(); + println!("{:?}", s); + assert_eq!(s.shape(), &[4, 3]); + assert_eq!(s, aview2(&[[0, 4, 8], [1, 5, 9], [2, 6, 10], [3, 7, 11]])); +} + + +#[test] +fn to_shape_easy() { + // 1D -> C -> C + let data = [1, 2, 3, 4, 5, 6, 7, 8]; + let v = aview1(&data); + let u = v.to_shape(((3, 3), Order::RowMajor)); + assert!(u.is_err()); + + let u = v.to_shape(((2, 2, 2), Order::C)); + assert!(u.is_ok()); + + let u = u.unwrap(); + assert!(u.is_view()); + assert_eq!(u.shape(), &[2, 2, 2]); + assert_eq!(u, array![[[1, 2], [3, 4]], [[5, 6], [7, 8]]]); + + let s = u.to_shape((4, 2)).unwrap(); + assert_eq!(s.shape(), &[4, 2]); + assert_eq!(s, aview2(&[[1, 2], [3, 4], [5, 6], [7, 8]])); + + // 1D -> F -> F + let data = [1, 2, 3, 4, 5, 6, 7, 8]; + let v = aview1(&data); + let u = v.to_shape(((3, 3), Order::ColumnMajor)); + assert!(u.is_err()); + + let u = v.to_shape(((2, 2, 2), Order::ColumnMajor)); + assert!(u.is_ok()); + + let u = u.unwrap(); + assert!(u.is_view()); + assert_eq!(u.shape(), &[2, 2, 2]); + assert_eq!(u, array![[[1, 5], [3, 7]], [[2, 6], [4, 8]]]); + + let s = u.to_shape(((4, 2), Order::ColumnMajor)).unwrap(); + assert_eq!(s.shape(), &[4, 2]); + assert_eq!(s, array![[1, 5], [2, 6], [3, 7], [4, 8]]); +} + +#[test] +fn to_shape_copy() { + // 1D -> C -> F + let v = ArrayView::from(&[1, 2, 3, 4, 5, 6, 7, 8]); + let u = v.to_shape(((4, 2), Order::RowMajor)).unwrap(); + assert_eq!(u.shape(), &[4, 2]); + assert_eq!(u, array![[1, 2], [3, 4], [5, 6], [7, 8]]); + + let u = u.to_shape(((2, 4), Order::ColumnMajor)).unwrap(); + assert_eq!(u.shape(), &[2, 4]); + assert_eq!(u, array![[1, 5, 2, 6], [3, 7, 4, 8]]); + + // 1D -> F -> C + let v = ArrayView::from(&[1, 2, 3, 4, 5, 6, 7, 8]); + let u = v.to_shape(((4, 2), Order::ColumnMajor)).unwrap(); + assert_eq!(u.shape(), &[4, 2]); + assert_eq!(u, array![[1, 5], [2, 6], [3, 7], [4, 8]]); + + let u = u.to_shape((2, 4)).unwrap(); + assert_eq!(u.shape(), &[2, 4]); + assert_eq!(u, array![[1, 5, 2, 6], [3, 7, 4, 8]]); +} + +#[test] +fn to_shape_add_axis() { + // 1D -> C -> C + let data = [1, 2, 3, 4, 5, 6, 7, 8]; + let v = aview1(&data); + let u = v.to_shape(((4, 2), Order::RowMajor)).unwrap(); + + assert!(u.to_shape(((1, 4, 2), Order::RowMajor)).unwrap().is_view()); + assert!(u.to_shape(((1, 4, 2), Order::ColumnMajor)).unwrap().is_view()); +} + + +#[test] +fn to_shape_copy_stride() { + let v = array![[1, 2, 3, 4], [5, 6, 7, 8]]; + let vs = v.slice(s![.., ..3]); + let lin1 = vs.to_shape(6).unwrap(); + assert_eq!(lin1, array![1, 2, 3, 5, 6, 7]); + assert!(lin1.is_owned()); + + let lin2 = vs.to_shape((6, Order::ColumnMajor)).unwrap(); + assert_eq!(lin2, array![1, 5, 2, 6, 3, 7]); + assert!(lin2.is_owned()); +} + + +#[test] +fn to_shape_zero_len() { + let v = array![[1, 2, 3, 4], [5, 6, 7, 8]]; + let vs = v.slice(s![.., ..0]); + let lin1 = vs.to_shape(0).unwrap(); + assert_eq!(lin1, array![]); + assert!(lin1.is_view()); +} + +#[test] +#[should_panic(expected = "IncompatibleShape")] +fn to_shape_error1() { + let data = [1, 2, 3, 4, 5, 6, 7, 8]; + let v = aview1(&data); + let _u = v.to_shape((2, 5)).unwrap(); +} + +#[test] +#[should_panic(expected = "IncompatibleShape")] +fn to_shape_error2() { + // overflow + let data = [3, 4, 5, 6, 7, 8]; + let v = aview1(&data); + let _u = v.to_shape((2, usize::MAX)).unwrap(); +} + +#[test] +fn to_shape_discontig() { + for &create_order in &[Order::C, Order::F] { + let a = Array::from_iter(0..64); + let mut a1 = a.to_shape(((4, 4, 4), create_order)).unwrap(); + a1.slice_collapse(s![.., ..;2, ..]); // now shape (4, 2, 4) + assert!(a1.as_slice_memory_order().is_none()); + + for &order in &[Order::C, Order::F] { + let v1 = a1.to_shape(((2, 2, 2, 2, 2), order)).unwrap(); + assert!(v1.is_view()); + let v1 = a1.to_shape(((4, 1, 2, 1, 2, 2), order)).unwrap(); + assert!(v1.is_view()); + let v1 = a1.to_shape(((4, 2, 4), order)).unwrap(); + assert!(v1.is_view()); + let v1 = a1.to_shape(((8, 4), order)).unwrap(); + assert_eq!(v1.is_view(), order == create_order && create_order == Order::C, + "failed for {:?}, {:?}", create_order, order); + let v1 = a1.to_shape(((4, 8), order)).unwrap(); + assert_eq!(v1.is_view(), order == create_order && create_order == Order::F, + "failed for {:?}, {:?}", create_order, order); + let v1 = a1.to_shape((32, order)).unwrap(); + assert!(!v1.is_view()); + } + } +} + +#[test] +fn to_shape_broadcast() { + for &create_order in &[Order::C, Order::F] { + let a = Array::from_iter(0..64); + let mut a1 = a.to_shape(((4, 4, 4), create_order)).unwrap(); + a1.slice_collapse(s![.., ..1, ..]); // now shape (4, 1, 4) + let v1 = a1.broadcast((4, 4, 4)).unwrap(); // Now shape (4, 4, 4) + assert!(v1.as_slice_memory_order().is_none()); + + for &order in &[Order::C, Order::F] { + let v2 = v1.to_shape(((2, 2, 2, 2, 2, 2), order)).unwrap(); + assert_eq!(v2.strides(), match (create_order, order) { + (Order::C, Order::C) => { &[32, 16, 0, 0, 2, 1] } + (Order::C, Order::F) => { &[16, 32, 0, 0, 1, 2] } + (Order::F, Order::C) => { &[2, 1, 0, 0, 32, 16] } + (Order::F, Order::F) => { &[1, 2, 0, 0, 16, 32] } + _other => unreachable!() + }); + + let v2 = v1.to_shape(((4, 4, 4), order)).unwrap(); + assert!(v2.is_view()); + let v2 = v1.to_shape(((8, 8), order)).unwrap(); + assert!(v2.is_owned()); + } + } +} + + +#[test] +fn into_shape_with_order() { + // 1D -> C -> C + let data = [1, 2, 3, 4, 5, 6, 7, 8]; + let v = aview1(&data); + let u = v.into_shape_with_order(((3, 3), Order::RowMajor)); + assert!(u.is_err()); + + let u = v.into_shape_with_order(((2, 2, 2), Order::C)); + assert!(u.is_ok()); + + let u = u.unwrap(); + assert_eq!(u.shape(), &[2, 2, 2]); + assert_eq!(u, array![[[1, 2], [3, 4]], [[5, 6], [7, 8]]]); + + let s = u.into_shape_with_order((4, 2)).unwrap(); + assert_eq!(s.shape(), &[4, 2]); + assert_eq!(s, aview2(&[[1, 2], [3, 4], [5, 6], [7, 8]])); + + // 1D -> F -> F + let data = [1, 2, 3, 4, 5, 6, 7, 8]; + let v = aview1(&data); + let u = v.into_shape_with_order(((3, 3), Order::ColumnMajor)); + assert!(u.is_err()); + + let u = v.into_shape_with_order(((2, 2, 2), Order::ColumnMajor)); + assert!(u.is_ok()); + + let u = u.unwrap(); + assert_eq!(u.shape(), &[2, 2, 2]); + assert_eq!(u, array![[[1, 5], [3, 7]], [[2, 6], [4, 8]]]); + + let s = u.into_shape_with_order(((4, 2), Order::ColumnMajor)).unwrap(); + assert_eq!(s.shape(), &[4, 2]); + assert_eq!(s, array![[1, 5], [2, 6], [3, 7], [4, 8]]); +} + +#[test] +fn into_shape_clone() { + // 1D -> C -> C + { + let data = [1, 2, 3, 4, 5, 6, 7, 8]; + let v = Array::from(data.to_vec()); + let u = v.clone().into_shape_clone(((3, 3), Order::RowMajor)); + assert!(u.is_err()); + + let u = v.clone().into_shape_clone(((2, 2, 2), Order::C)); + assert!(u.is_ok()); + + let u = u.unwrap(); + assert_eq!(u.shape(), &[2, 2, 2]); + assert_eq!(u, array![[[1, 2], [3, 4]], [[5, 6], [7, 8]]]); + + let s = u.into_shape_clone((4, 2)).unwrap(); + assert_eq!(s.shape(), &[4, 2]); + assert_eq!(s, aview2(&[[1, 2], [3, 4], [5, 6], [7, 8]])); + + let u = v.clone().into_shape_clone(((2, 2, 2), Order::F)); + assert!(u.is_ok()); + + let u = u.unwrap(); + assert_eq!(u.shape(), &[2, 2, 2]); + assert_eq!(u, array![[[1, 5], [3, 7]], [[2, 6], [4, 8]]]); + } + + // 1D -> F -> F + { + let data = [1, 2, 3, 4, 5, 6, 7, 8]; + let v = Array::from(data.to_vec()); + let u = v.clone().into_shape_clone(((3, 3), Order::ColumnMajor)); + assert!(u.is_err()); + + let u = v.into_shape_clone(((2, 2, 2), Order::ColumnMajor)); + assert!(u.is_ok()); + + let u = u.unwrap(); + assert_eq!(u.shape(), &[2, 2, 2]); + assert_eq!(u, array![[[1, 5], [3, 7]], [[2, 6], [4, 8]]]); + + let s = u.into_shape_clone(((4, 2), Order::ColumnMajor)).unwrap(); + assert_eq!(s.shape(), &[4, 2]); + assert_eq!(s, array![[1, 5], [2, 6], [3, 7], [4, 8]]); + } +} diff --git a/tests/stacking.rs b/tests/stacking.rs index 032525ffa..0c4e79c79 100644 --- a/tests/stacking.rs +++ b/tests/stacking.rs @@ -1,4 +1,4 @@ -use ndarray::{arr2, arr3, aview1, concatenate, stack, Array2, Axis, ErrorKind, Ix1}; +use ndarray::{arr2, arr3, aview1, aview2, concatenate, stack, Array2, Axis, ErrorKind, Ix1}; #[test] fn concatenating() { @@ -15,6 +15,13 @@ fn concatenating() { let d = concatenate![Axis(0), a.row(0), &[9., 9.]]; assert_eq!(d, aview1(&[2., 2., 9., 9.])); + let d = concatenate![Axis(1), a.row(0).insert_axis(Axis(1)), aview1(&[9., 9.]).insert_axis(Axis(1))]; + assert_eq!(d, aview2(&[[2., 9.], + [2., 9.]])); + + let d = concatenate![Axis(0), a.row(0).insert_axis(Axis(1)), aview1(&[9., 9.]).insert_axis(Axis(1))]; + assert_eq!(d, aview2(&[[2.], [2.], [9.], [9.]])); + let res = ndarray::concatenate(Axis(1), &[a.view(), c.view()]); assert_eq!(res.unwrap_err().kind(), ErrorKind::IncompatibleShape); diff --git a/tests/windows.rs b/tests/windows.rs index b0482e4bd..c24a47ef9 100644 --- a/tests/windows.rs +++ b/tests/windows.rs @@ -6,7 +6,7 @@ )] use ndarray::prelude::*; -use ndarray::Zip; +use ndarray::{arr3, Zip}; // Edge Cases for Windows iterator: // @@ -26,14 +26,14 @@ use ndarray::Zip; #[test] #[should_panic] fn windows_iterator_zero_size() { - let a = Array::from_iter(10..37).into_shape((3, 3, 3)).unwrap(); + let a = Array::from_iter(10..37).into_shape_with_order((3, 3, 3)).unwrap(); a.windows(Dim((0, 0, 0))); } -/// Test that verifites that no windows are yielded on oversized window sizes. +/// Test that verifies that no windows are yielded on oversized window sizes. #[test] fn windows_iterator_oversized() { - let a = Array::from_iter(10..37).into_shape((3, 3, 3)).unwrap(); + let a = Array::from_iter(10..37).into_shape_with_order((3, 3, 3)).unwrap(); let mut iter = a.windows((4, 3, 2)).into_iter(); // (4,3,2) doesn't fit into (3,3,3) => oversized! assert_eq!(iter.next(), None); } @@ -41,7 +41,7 @@ fn windows_iterator_oversized() { /// Simple test for iterating 1d-arrays via `Windows`. #[test] fn windows_iterator_1d() { - let a = Array::from_iter(10..20).into_shape(10).unwrap(); + let a = Array::from_iter(10..20).into_shape_with_order(10).unwrap(); itertools::assert_equal( a.windows(Dim(4)), vec![ @@ -59,7 +59,7 @@ fn windows_iterator_1d() { /// Simple test for iterating 2d-arrays via `Windows`. #[test] fn windows_iterator_2d() { - let a = Array::from_iter(10..30).into_shape((5, 4)).unwrap(); + let a = Array::from_iter(10..30).into_shape_with_order((5, 4)).unwrap(); itertools::assert_equal( a.windows(Dim((3, 2))), vec![ @@ -79,8 +79,7 @@ fn windows_iterator_2d() { /// Simple test for iterating 3d-arrays via `Windows`. #[test] fn windows_iterator_3d() { - use ndarray::arr3; - let a = Array::from_iter(10..37).into_shape((3, 3, 3)).unwrap(); + let a = Array::from_iter(10..37).into_shape_with_order((3, 3, 3)).unwrap(); itertools::assert_equal( a.windows(Dim((2, 2, 2))), vec![ @@ -96,9 +95,79 @@ fn windows_iterator_3d() { ); } +/// Test that verifies the `Windows` iterator panics when stride has an axis equal to zero. +#[test] +#[should_panic] +fn windows_iterator_stride_axis_zero() { + let a = Array::from_iter(10..37).into_shape_with_order((3, 3, 3)).unwrap(); + a.windows_with_stride((2, 2, 2), (0, 2, 2)); +} + +/// Test that verifies that only first window is yielded when stride is oversized on every axis. +#[test] +fn windows_iterator_only_one_valid_window_for_oversized_stride() { + let a = Array::from_iter(10..135).into_shape_with_order((5, 5, 5)).unwrap(); + let mut iter = a.windows_with_stride((2, 2, 2), (8, 8, 8)).into_iter(); // (4,3,2) doesn't fit into (3,3,3) => oversized! + itertools::assert_equal( + iter.next(), + Some(arr3(&[[[10, 11], [15, 16]], [[35, 36], [40, 41]]])), + ); +} + +/// Simple test for iterating 1d-arrays via `Windows` with stride. +#[test] +fn windows_iterator_1d_with_stride() { + let a = Array::from_iter(10..20).into_shape_with_order(10).unwrap(); + itertools::assert_equal( + a.windows_with_stride(4, 2), + vec![ + arr1(&[10, 11, 12, 13]), + arr1(&[12, 13, 14, 15]), + arr1(&[14, 15, 16, 17]), + arr1(&[16, 17, 18, 19]), + ], + ); +} + +/// Simple test for iterating 2d-arrays via `Windows` with stride. +#[test] +fn windows_iterator_2d_with_stride() { + let a = Array::from_iter(10..30).into_shape_with_order((5, 4)).unwrap(); + itertools::assert_equal( + a.windows_with_stride((3, 2), (2, 1)), + vec![ + arr2(&[[10, 11], [14, 15], [18, 19]]), + arr2(&[[11, 12], [15, 16], [19, 20]]), + arr2(&[[12, 13], [16, 17], [20, 21]]), + arr2(&[[18, 19], [22, 23], [26, 27]]), + arr2(&[[19, 20], [23, 24], [27, 28]]), + arr2(&[[20, 21], [24, 25], [28, 29]]), + ], + ); +} + +/// Simple test for iterating 3d-arrays via `Windows` with stride. +#[test] +fn windows_iterator_3d_with_stride() { + let a = Array::from_iter(10..74).into_shape_with_order((4, 4, 4)).unwrap(); + itertools::assert_equal( + a.windows_with_stride((2, 2, 2), (2, 2, 2)), + vec![ + arr3(&[[[10, 11], [14, 15]], [[26, 27], [30, 31]]]), + arr3(&[[[12, 13], [16, 17]], [[28, 29], [32, 33]]]), + arr3(&[[[18, 19], [22, 23]], [[34, 35], [38, 39]]]), + arr3(&[[[20, 21], [24, 25]], [[36, 37], [40, 41]]]), + arr3(&[[[42, 43], [46, 47]], [[58, 59], [62, 63]]]), + arr3(&[[[44, 45], [48, 49]], [[60, 61], [64, 65]]]), + arr3(&[[[50, 51], [54, 55]], [[66, 67], [70, 71]]]), + arr3(&[[[52, 53], [56, 57]], [[68, 69], [72, 73]]]), + ], + ); +} + #[test] fn test_window_zip() { - let a = Array::from_iter(0..64).into_shape((4, 4, 4)).unwrap(); + let a = Array::from_iter(0..64).into_shape_with_order((4, 4, 4)).unwrap(); for x in 1..4 { for y in 1..4 { @@ -117,16 +186,97 @@ fn test_window_zip() { } } +/// Test verifies that non existent Axis results in panic +#[test] +#[should_panic] +fn axis_windows_outofbound() { + let a = Array::from_iter(10..37).into_shape_with_order((3, 3, 3)).unwrap(); + a.axis_windows(Axis(4), 2); +} + +/// Test verifies that zero sizes results in panic +#[test] +#[should_panic] +fn axis_windows_zero_size() { + let a = Array::from_iter(10..37).into_shape_with_order((3, 3, 3)).unwrap(); + a.axis_windows(Axis(0), 0); +} + +/// Test verifies that over sized windows yield nothing +#[test] +fn axis_windows_oversized() { + let a = Array::from_iter(10..37).into_shape_with_order((3, 3, 3)).unwrap(); + let mut iter = a.axis_windows(Axis(2), 4).into_iter(); + assert_eq!(iter.next(), None); +} + +/// Simple test for iterating 1d-arrays via `Axis Windows`. +#[test] +fn test_axis_windows_1d() { + let a = Array::from_iter(10..20).into_shape_with_order(10).unwrap(); + + itertools::assert_equal( + a.axis_windows(Axis(0), 5), + vec![ + arr1(&[10, 11, 12, 13, 14]), + arr1(&[11, 12, 13, 14, 15]), + arr1(&[12, 13, 14, 15, 16]), + arr1(&[13, 14, 15, 16, 17]), + arr1(&[14, 15, 16, 17, 18]), + arr1(&[15, 16, 17, 18, 19]), + ], + ); +} + +/// Simple test for iterating 2d-arrays via `Axis Windows`. +#[test] +fn test_axis_windows_2d() { + let a = Array::from_iter(10..30).into_shape_with_order((5, 4)).unwrap(); + + itertools::assert_equal( + a.axis_windows(Axis(0), 2), + vec![ + arr2(&[[10, 11, 12, 13], [14, 15, 16, 17]]), + arr2(&[[14, 15, 16, 17], [18, 19, 20, 21]]), + arr2(&[[18, 19, 20, 21], [22, 23, 24, 25]]), + arr2(&[[22, 23, 24, 25], [26, 27, 28, 29]]), + ], + ); +} + +/// Simple test for iterating 3d-arrays via `Axis Windows`. +#[test] +fn test_axis_windows_3d() { + let a = Array::from_iter(0..27).into_shape_with_order((3, 3, 3)).unwrap(); + + itertools::assert_equal( + a.axis_windows(Axis(1), 2), + vec![ + arr3(&[ + [[0, 1, 2], [3, 4, 5]], + [[9, 10, 11], [12, 13, 14]], + [[18, 19, 20], [21, 22, 23]], + ]), + arr3(&[ + [[3, 4, 5], [6, 7, 8]], + [[12, 13, 14], [15, 16, 17]], + [[21, 22, 23], [24, 25, 26]], + ]), + ], + ); +} + + #[test] fn test_window_neg_stride() { - let array = Array::from_iter(1..10).into_shape((3, 3)).unwrap(); + let array = Array::from_iter(1..10).into_shape_with_order((3, 3)).unwrap(); // window neg/pos stride combinations // Make a 2 x 2 array of the windows of the 3 x 3 array // and compute test answers from here let mut answer = Array::from_iter(array.windows((2, 2)).into_iter().map(|a| a.to_owned())) - .into_shape((2, 2)).unwrap(); + .into_shape_with_order((2, 2)).unwrap(); answer.invert_axis(Axis(1)); answer.map_inplace(|a| a.invert_axis(Axis(1))); @@ -152,3 +302,31 @@ fn test_window_neg_stride() { answer.iter() ); } + +#[test] +fn test_windows_with_stride_on_inverted_axis() { + let mut array = Array::from_iter(1..17).into_shape_with_order((4, 4)).unwrap(); + + // inverting axis results in negative stride + array.invert_axis(Axis(0)); + itertools::assert_equal( + array.windows_with_stride((2, 2), (2,2)), + vec![ + arr2(&[[13, 14], [9, 10]]), + arr2(&[[15, 16], [11, 12]]), + arr2(&[[5, 6], [1, 2]]), + arr2(&[[7, 8], [3, 4]]), + ], + ); + + array.invert_axis(Axis(1)); + itertools::assert_equal( + array.windows_with_stride((2, 2), (2,2)), + vec![ + arr2(&[[16, 15], [12, 11]]), + arr2(&[[14, 13], [10, 9]]), + arr2(&[[8, 7], [4, 3]]), + arr2(&[[6, 5], [2, 1]]), + ], + ); +} \ No newline at end of file diff --git a/xtest-blas/Cargo.toml b/xtest-blas/Cargo.toml index 463be6863..7ad33953c 100644 --- a/xtest-blas/Cargo.toml +++ b/xtest-blas/Cargo.toml @@ -11,9 +11,10 @@ test = false approx = "0.4" defmac = "0.2" num-traits = "0.2" +num-complex = { version = "0.4", default-features = false } [dependencies] -ndarray = { path = "../", features = ["approx", "blas"] } +ndarray = { path = "..", features = ["approx", "blas"] } blas-src = { version = "0.8", optional = true } diff --git a/xtest-blas/tests/oper.rs b/xtest-blas/tests/oper.rs index 0aeb47680..5f11893df 100644 --- a/xtest-blas/tests/oper.rs +++ b/xtest-blas/tests/oper.rs @@ -1,8 +1,9 @@ extern crate approx; +extern crate blas_src; extern crate defmac; extern crate ndarray; +extern crate num_complex; extern crate num_traits; -extern crate blas_src; use ndarray::prelude::*; @@ -12,6 +13,8 @@ use ndarray::{Data, Ix, LinalgScalar}; use approx::assert_relative_eq; use defmac::defmac; +use num_complex::Complex32; +use num_complex::Complex64; #[test] fn mat_vec_product_1d() { @@ -21,16 +24,49 @@ fn mat_vec_product_1d() { assert_eq!(a.t().dot(&b), ans); } +#[test] +fn mat_vec_product_1d_broadcast() { + let a = arr2(&[[1.], [2.], [3.]]); + let b = arr1(&[1.]); + let b = b.broadcast(3).unwrap(); + let ans = arr1(&[6.]); + assert_eq!(a.t().dot(&b), ans); +} + +#[test] +fn mat_vec_product_1d_inverted_axis() { + let a = arr2(&[[1.], [2.], [3.]]); + let mut b = arr1(&[1., 2., 3.]); + b.invert_axis(Axis(0)); + + let ans = arr1(&[3. + 4. + 3.]); + assert_eq!(a.t().dot(&b), ans); +} + fn range_mat(m: Ix, n: Ix) -> Array2 { Array::linspace(0., (m * n) as f32 - 1., m * n) - .into_shape((m, n)) + .into_shape_with_order((m, n)) .unwrap() } fn range_mat64(m: Ix, n: Ix) -> Array2 { Array::linspace(0., (m * n) as f64 - 1., m * n) - .into_shape((m, n)) + .into_shape_with_order((m, n)) + .unwrap() +} + +fn range_mat_complex(m: Ix, n: Ix) -> Array2 { + Array::linspace(0., (m * n) as f32 - 1., m * n) + .into_shape_with_order((m, n)) + .unwrap() + .map(|&f| Complex32::new(f, 0.)) +} + +fn range_mat_complex64(m: Ix, n: Ix) -> Array2 { + Array::linspace(0., (m * n) as f64 - 1., m * n) + .into_shape_with_order((m, n)) .unwrap() + .map(|&f| Complex64::new(f, 0.)) } fn range1_mat64(m: Ix) -> Array1 { @@ -39,7 +75,7 @@ fn range1_mat64(m: Ix) -> Array1 { fn range_i32(m: Ix, n: Ix) -> Array2 { Array::from_iter(0..(m * n) as i32) - .into_shape((m, n)) + .into_shape_with_order((m, n)) .unwrap() } @@ -83,8 +119,8 @@ where S2: Data, { let ((m, _), k) = (lhs.dim(), rhs.dim()); - reference_mat_mul(lhs, &rhs.as_standard_layout().into_shape((k, 1)).unwrap()) - .into_shape(m) + reference_mat_mul(lhs, &rhs.as_standard_layout().into_shape_with_order((k, 1)).unwrap()) + .into_shape_with_order(m) .unwrap() } @@ -96,8 +132,8 @@ where S2: Data, { let (m, (_, n)) = (lhs.dim(), rhs.dim()); - reference_mat_mul(&lhs.as_standard_layout().into_shape((1, m)).unwrap(), rhs) - .into_shape(n) + reference_mat_mul(&lhs.as_standard_layout().into_shape_with_order((1, m)).unwrap(), rhs) + .into_shape_with_order(n) .unwrap() } @@ -231,6 +267,77 @@ fn gemm_64_1_f() { assert_relative_eq!(y, answer, epsilon = 1e-12, max_relative = 1e-7); } +#[test] +fn gemm_c64_1_f() { + let a = range_mat_complex64(64, 64).reversed_axes(); + let (m, n) = a.dim(); + // m x n times n x 1 == m x 1 + let x = range_mat_complex64(n, 1); + let mut y = range_mat_complex64(m, 1); + let answer = reference_mat_mul(&a, &x) + &y; + general_mat_mul( + Complex64::new(1.0, 0.), + &a, + &x, + Complex64::new(1.0, 0.), + &mut y, + ); + assert_relative_eq!( + y.mapv(|i| i.norm_sqr()), + answer.mapv(|i| i.norm_sqr()), + epsilon = 1e-12, + max_relative = 1e-7 + ); +} + +#[test] +fn gemm_c32_1_f() { + let a = range_mat_complex(64, 64).reversed_axes(); + let (m, n) = a.dim(); + // m x n times n x 1 == m x 1 + let x = range_mat_complex(n, 1); + let mut y = range_mat_complex(m, 1); + let answer = reference_mat_mul(&a, &x) + &y; + general_mat_mul( + Complex32::new(1.0, 0.), + &a, + &x, + Complex32::new(1.0, 0.), + &mut y, + ); + assert_relative_eq!( + y.mapv(|i| i.norm_sqr()), + answer.mapv(|i| i.norm_sqr()), + epsilon = 1e-12, + max_relative = 1e-7 + ); +} + +#[test] +fn gemm_c64_actually_complex() { + let mut a = range_mat_complex64(4,4); + a = a.map(|&i| if i.re > 8. { i.conj() } else { i }); + let mut b = range_mat_complex64(4,6); + b = b.map(|&i| if i.re > 4. { i.conj() } else {i}); + let mut y = range_mat_complex64(4,6); + let alpha = Complex64::new(0., 1.0); + let beta = Complex64::new(1.0, 1.0); + let answer = alpha * reference_mat_mul(&a, &b) + beta * &y; + general_mat_mul( + alpha.clone(), + &a, + &b, + beta.clone(), + &mut y, + ); + assert_relative_eq!( + y.mapv(|i| i.norm_sqr()), + answer.mapv(|i| i.norm_sqr()), + epsilon = 1e-12, + max_relative = 1e-7 + ); +} + #[test] fn gen_mat_vec_mul() { let alpha = -2.3; diff --git a/xtest-numeric/Cargo.toml b/xtest-numeric/Cargo.toml index 14bbd16ee..8558d39ad 100644 --- a/xtest-numeric/Cargo.toml +++ b/xtest-numeric/Cargo.toml @@ -3,11 +3,12 @@ name = "numeric-tests" version = "0.1.0" authors = ["bluss"] publish = false +edition = "2018" [dependencies] approx = "0.4" ndarray = { path = "..", features = ["approx"] } -ndarray-rand = { path = "../ndarray-rand/" } +ndarray-rand = { path = "../ndarray-rand" } rand_distr = "0.4" blas-src = { optional = true, version = "0.8", default-features = false, features = ["openblas"] } @@ -17,13 +18,12 @@ openblas-src = { optional = true, version = "0.10", default-features = false, fe version = "0.8.0" features = ["small_rng"] +[dev-dependencies] +num-traits = { version = "0.2.14", default-features = false } +num-complex = { version = "0.4", default-features = false } + [lib] test = false [features] test_blas = ["ndarray/blas", "blas-src", "openblas-src"] - -[profile.dev] -opt-level = 2 -[profile.test] -opt-level = 2 diff --git a/xtest-numeric/tests/accuracy.rs b/xtest-numeric/tests/accuracy.rs index 438b73705..679267096 100644 --- a/xtest-numeric/tests/accuracy.rs +++ b/xtest-numeric/tests/accuracy.rs @@ -6,6 +6,8 @@ extern crate rand; extern crate numeric_tests; +use std::fmt; + use ndarray_rand::RandomExt; use rand::{Rng, SeedableRng}; use rand::rngs::SmallRng; @@ -17,10 +19,28 @@ use ndarray::{ }; use ndarray::linalg::general_mat_mul; -use rand_distr::Normal; +use rand_distr::{Normal, StandardNormal, Distribution}; +use num_traits::{Float, AsPrimitive}; +use num_complex::Complex; use approx::{assert_abs_diff_eq, assert_relative_eq}; +fn kahan_sum(iter: impl Iterator) -> A + where A: LinalgScalar +{ + let mut sum = A::zero(); + let mut compensation = A::zero(); + + for elt in iter { + let y = elt - compensation; + let t = sum + y; + compensation = (t - sum) - y; + sum = t; + } + + sum +} + // simple, slow, correct (hopefully) mat mul fn reference_mat_mul(lhs: &ArrayBase, rhs: &ArrayBase) -> Array @@ -29,46 +49,48 @@ fn reference_mat_mul(lhs: &ArrayBase, rhs: &ArrayBase S2: Data, { let ((m, k), (_, n)) = (lhs.dim(), rhs.dim()); - let mut res_elems = Vec::::with_capacity(m * n); - unsafe { - res_elems.set_len(m * n); - } + let mut res_elems = Array::zeros(m * n); let mut i = 0; let mut j = 0; for rr in &mut res_elems { - unsafe { - *rr = (0..k).fold(A::zero(), - move |s, x| s + *lhs.uget((i, x)) * *rhs.uget((x, j))); - } + let lhs_i = lhs.row(i); + let rhs_j = rhs.column(j); + *rr = kahan_sum((0..k).map(move |x| lhs_i[x] * rhs_j[x])); + j += 1; if j == n { j = 0; i += 1; } } - unsafe { - ArrayBase::from_shape_vec_unchecked((m, n), res_elems) - } + + res_elems.into_shape_with_order((m, n)).unwrap() } -fn gen(d: D) -> Array +fn gen(d: D, rng: &mut SmallRng) -> Array where D: Dimension, + A: Float, + StandardNormal: Distribution, { - Array::random(d, Normal::new(0., 1.).unwrap()) + Array::random_using(d, Normal::new(A::zero(), A::one()).unwrap(), rng) } -fn gen_f64(d: D) -> Array + +fn gen_complex(d: D, rng: &mut SmallRng) -> Array, D> where D: Dimension, + A: Float, + StandardNormal: Distribution, { - Array::random(d, Normal::new(0., 1.).unwrap()) + gen(d.clone(), rng).mapv(Complex::from) + gen(d, rng).mapv(|x| Complex::new(A::zero(), x)) } #[test] fn accurate_eye_f32() { + let rng = &mut SmallRng::from_entropy(); for i in 0..20 { let eye = Array::eye(i); for j in 0..20 { - let a = gen(Ix2(i, j)); + let a = gen::(Ix2(i, j), rng); let a2 = eye.dot(&a); assert_abs_diff_eq!(a, a2, epsilon = 1e-6); let a3 = a.t().dot(&eye); @@ -76,12 +98,11 @@ fn accurate_eye_f32() { } } // pick a few random sizes - let mut rng = SmallRng::from_entropy(); for _ in 0..10 { let i = rng.gen_range(15..512); let j = rng.gen_range(15..512); println!("Testing size {} by {}", i, j); - let a = gen(Ix2(i, j)); + let a = gen::(Ix2(i, j), rng); let eye = Array::eye(i); let a2 = eye.dot(&a); assert_abs_diff_eq!(a, a2, epsilon = 1e-6); @@ -92,11 +113,12 @@ fn accurate_eye_f32() { #[test] fn accurate_eye_f64() { + let rng = &mut SmallRng::from_entropy(); let abs_tol = 1e-15; for i in 0..20 { let eye = Array::eye(i); for j in 0..20 { - let a = gen_f64(Ix2(i, j)); + let a = gen::(Ix2(i, j), rng); let a2 = eye.dot(&a); assert_abs_diff_eq!(a, a2, epsilon = abs_tol); let a3 = a.t().dot(&eye); @@ -104,12 +126,11 @@ fn accurate_eye_f64() { } } // pick a few random sizes - let mut rng = SmallRng::from_entropy(); for _ in 0..10 { let i = rng.gen_range(15..512); let j = rng.gen_range(15..512); println!("Testing size {} by {}", i, j); - let a = gen_f64(Ix2(i, j)); + let a = gen::(Ix2(i, j), rng); let eye = Array::eye(i); let a2 = eye.dot(&a); assert_abs_diff_eq!(a, a2, epsilon = 1e-6); @@ -119,114 +140,125 @@ fn accurate_eye_f64() { } #[test] -fn accurate_mul_f32() { - // pick a few random sizes - let mut rng = SmallRng::from_entropy(); - for i in 0..20 { - let m = rng.gen_range(15..512); - let k = rng.gen_range(15..512); - let n = rng.gen_range(15..1560); - let a = gen(Ix2(m, k)); - let b = gen(Ix2(n, k)); - let b = b.t(); - let (a, b) = if i > 10 { - (a.slice(s![..;2, ..;2]), - b.slice(s![..;2, ..;2])) - } else { (a.view(), b) }; - - println!("Testing size {} by {} by {}", a.shape()[0], a.shape()[1], b.shape()[1]); - let c = a.dot(&b); - let reference = reference_mat_mul(&a, &b); - - assert_relative_eq!(c, reference, epsilon = 1e-4, max_relative = 1e-3); - } +fn accurate_mul_f32_dot() { + accurate_mul_float_general::(1e-5, false); } #[test] fn accurate_mul_f32_general() { - // pick a few random sizes - let mut rng = SmallRng::from_entropy(); - for i in 0..20 { - let m = rng.gen_range(15..512); - let k = rng.gen_range(15..512); - let n = rng.gen_range(15..1560); - let a = gen(Ix2(m, k)); - let b = gen(Ix2(n, k)); - let mut c = gen(Ix2(m, n)); - let b = b.t(); - let (a, b, mut c) = if i > 10 { - (a.slice(s![..;2, ..;2]), - b.slice(s![..;2, ..;2]), - c.slice_mut(s![..;2, ..;2])) - } else { (a.view(), b, c.view_mut()) }; - - println!("Testing size {} by {} by {}", a.shape()[0], a.shape()[1], b.shape()[1]); - general_mat_mul(1., &a, &b, 0., &mut c); - let reference = reference_mat_mul(&a, &b); - - assert_relative_eq!(c, reference, epsilon = 1e-4, max_relative = 1e-3); - } + accurate_mul_float_general::(1e-5, true); +} + +#[test] +fn accurate_mul_f64_dot() { + accurate_mul_float_general::(1e-14, false); } #[test] -fn accurate_mul_f64() { +fn accurate_mul_f64_general() { + accurate_mul_float_general::(1e-14, true); +} + +/// Generate random sized matrices using the given generator function. +/// Compute gemm using either .dot() (if use_general is false) otherwise general_mat_mul. +/// Return tuple of actual result matrix and reference matrix, which should be equal. +fn random_matrix_mul(rng: &mut SmallRng, use_stride: bool, use_general: bool, + generator: fn(Ix2, &mut SmallRng) -> Array2) + -> (Array2, Array2) + where A: LinalgScalar, +{ + let m = rng.gen_range(15..512); + let k = rng.gen_range(15..512); + let n = rng.gen_range(15..1560); + let a = generator(Ix2(m, k), rng); + let b = generator(Ix2(n, k), rng); + let c = if use_general { + Some(generator(Ix2(m, n), rng)) + } else { + None + }; + + let b = b.t(); + let (a, b, mut c) = if use_stride { + (a.slice(s![..;2, ..;2]), + b.slice(s![..;2, ..;2]), + c.map(|c_| c_.slice_move(s![..;2, ..;2]))) + } else { + (a.view(), + b, + c) + }; + + println!("Testing size {} by {} by {}", a.shape()[0], a.shape()[1], b.shape()[1]); + if let Some(c) = &mut c { + general_mat_mul(A::one(), &a, &b, A::zero(), c); + } else { + c = Some(a.dot(&b)); + } + let c = c.unwrap(); + let reference = reference_mat_mul(&a, &b); + + (c, reference) +} + +fn accurate_mul_float_general(limit: f64, use_general: bool) + where A: Float + Copy + 'static + AsPrimitive, + StandardNormal: Distribution, + A: fmt::Debug, +{ // pick a few random sizes let mut rng = SmallRng::from_entropy(); for i in 0..20 { - let m = rng.gen_range(15..512); - let k = rng.gen_range(15..512); - let n = rng.gen_range(15..1560); - let a = gen_f64(Ix2(m, k)); - let b = gen_f64(Ix2(n, k)); - let b = b.t(); - let (a, b) = if i > 10 { - (a.slice(s![..;2, ..;2]), - b.slice(s![..;2, ..;2])) - } else { (a.view(), b) }; - - println!("Testing size {} by {} by {}", a.shape()[0], a.shape()[1], b.shape()[1]); - let c = a.dot(&b); - let reference = reference_mat_mul(&a, &b); - - assert_relative_eq!(c, reference, epsilon = 1e-12, max_relative = 1e-7); + let (c, reference) = random_matrix_mul(&mut rng, i > 10, use_general, gen::); + + let diff = &c - &reference; + let max_diff = diff.iter().copied().fold(A::zero(), A::max); + let max_elt = reference.iter().copied().fold(A::zero(), A::max); + println!("Max elt diff={:?}, max={:?}, ratio={:.4e}", max_diff, max_elt, (max_diff/max_elt).as_()); + assert!((max_diff / max_elt).as_() < limit, + "Expected relative norm diff < {:e}, found {:?} / {:?}", limit, max_diff, max_elt); } } #[test] -fn accurate_mul_f64_general() { +fn accurate_mul_complex32() { + accurate_mul_complex_general::(1e-5); +} + +#[test] +fn accurate_mul_complex64() { + accurate_mul_complex_general::(1e-14); +} + +fn accurate_mul_complex_general(limit: f64) + where A: Float + Copy + 'static + AsPrimitive, + StandardNormal: Distribution, + A: fmt::Debug, +{ // pick a few random sizes let mut rng = SmallRng::from_entropy(); for i in 0..20 { - let m = rng.gen_range(15..512); - let k = rng.gen_range(15..512); - let n = rng.gen_range(15..1560); - let a = gen_f64(Ix2(m, k)); - let b = gen_f64(Ix2(n, k)); - let mut c = gen_f64(Ix2(m, n)); - let b = b.t(); - let (a, b, mut c) = if i > 10 { - (a.slice(s![..;2, ..;2]), - b.slice(s![..;2, ..;2]), - c.slice_mut(s![..;2, ..;2])) - } else { (a.view(), b, c.view_mut()) }; - - println!("Testing size {} by {} by {}", a.shape()[0], a.shape()[1], b.shape()[1]); - general_mat_mul(1., &a, &b, 0., &mut c); - let reference = reference_mat_mul(&a, &b); - - assert_relative_eq!(c, reference, epsilon = 1e-12, max_relative = 1e-7); + let (c, reference) = random_matrix_mul(&mut rng, i > 10, true, gen_complex::); + + let diff = &c - &reference; + let max_elt = |elt: &Complex<_>| A::max(A::abs(elt.re), A::abs(elt.im)); + let max_diff = diff.iter().map(max_elt).fold(A::zero(), A::max); + let max_elt = reference.iter().map(max_elt).fold(A::zero(), A::max); + println!("Max elt diff={:?}, max={:?}, ratio={:.4e}", max_diff, max_elt, (max_diff/max_elt).as_()); + assert!((max_diff / max_elt).as_() < limit, + "Expected relative norm diff < {:e}, found {:?} / {:?}", limit, max_diff, max_elt); } } #[test] fn accurate_mul_with_column_f64() { // pick a few random sizes - let mut rng = SmallRng::from_entropy(); + let rng = &mut SmallRng::from_entropy(); for i in 0..10 { let m = rng.gen_range(1..350); let k = rng.gen_range(1..350); - let a = gen_f64(Ix2(m, k)); - let b_owner = gen_f64(Ix2(k, k)); + let a = gen::(Ix2(m, k), rng); + let b_owner = gen::(Ix2(k, k), rng); let b_row_col; let b_sq; diff --git a/xtest-serialization/Cargo.toml b/xtest-serialization/Cargo.toml index 973a688fe..857e31fe6 100644 --- a/xtest-serialization/Cargo.toml +++ b/xtest-serialization/Cargo.toml @@ -8,7 +8,7 @@ publish = false test = false [dependencies] -ndarray = { path = "../", features = ["serde"] } +ndarray = { path = "..", features = ["serde"] } [features] default = ["ron"] diff --git a/xtest-serialization/tests/serialize.rs b/xtest-serialization/tests/serialize.rs index efb3bacd9..cea84dd7f 100644 --- a/xtest-serialization/tests/serialize.rs +++ b/xtest-serialization/tests/serialize.rs @@ -45,7 +45,7 @@ fn serial_many_dim_serde() { { // Test a sliced array. - let mut a = ArcArray::linspace(0., 31., 32).reshape((2, 2, 2, 4)); + let mut a = ArcArray::linspace(0., 31., 32).into_shape_with_order((2, 2, 2, 4)).unwrap(); a.slice_collapse(s![..;-1, .., .., ..2]); let serial = serde_json::to_string(&a).unwrap(); println!("Encode {:?} => {:?}", a, serial); @@ -77,7 +77,7 @@ fn serial_ixdyn_serde() { { let a = arr2(&[[3., 1., 2.2], [3.1, 4., 7.]]) - .into_shape(IxDyn(&[3, 1, 1, 1, 2, 1])) + .into_shape_with_order(IxDyn(&[3, 1, 1, 1, 2, 1])) .unwrap(); let serial = serde_json::to_string(&a).unwrap(); println!("Serde encode {:?} => {:?}", a, serial); @@ -155,7 +155,7 @@ fn serial_many_dim_serde_msgpack() { { // Test a sliced array. - let mut a = ArcArray::linspace(0., 31., 32).reshape((2, 2, 2, 4)); + let mut a = ArcArray::linspace(0., 31., 32).into_shape_with_order((2, 2, 2, 4)).unwrap(); a.slice_collapse(s![..;-1, .., .., ..2]); let mut buf = Vec::new(); @@ -208,7 +208,7 @@ fn serial_many_dim_ron() { { // Test a sliced array. - let mut a = ArcArray::linspace(0., 31., 32).reshape((2, 2, 2, 4)); + let mut a = ArcArray::linspace(0., 31., 32).into_shape_with_order((2, 2, 2, 4)).unwrap(); a.slice_collapse(s![..;-1, .., .., ..2]); let a_s = ron_serialize(&a).unwrap();

/// -/// [`CowArray::from(a)`](type.CowArray.html#impl-From%2C%20D>>) +/// [`CowArray::from(a)`](CowArray#impl-From%2C%20D>>) /// /// /// -/// [`CowArray::from(a.into_owned())`](type.CowArray.html#impl-From%2C%20D>>) +/// [`CowArray::from(a.into_owned())`](CowArray#impl-From%2C%20D>>) /// /// @@ -898,12 +896,12 @@ pub type Ixs = isize; /// /// -/// [`CowArray::from(a)`](type.CowArray.html#impl-From%2C%20D>>) +/// [`CowArray::from(a)`](CowArray#impl-From%2C%20D>>) /// /// /// -/// [`CowArray::from(a.view())`](type.CowArray.html#impl-From%2C%20D>>) +/// [`CowArray::from(a.view())`](CowArray#impl-From%2C%20D>>) /// ///