ndarray 0.11

Changes in ndarray 0.11.0, including improved slicing

Contents:

ndarray 0.11.0 was just released. ndarray is a Rust crate that provides an n-dimensional array type ArrayBase for general elements and for numerics. Arrays are n-dimensional, so they can represent vectors (1 axis), matrices (2 axes), etc., to n axes. ndarray provides methods for n-dimensional slicing, iteration, view-taking, and some mathematical operations.

ndarray 0.11 has an exciting new feature: combined slicing and subviews!

Combined slicing and subviews

The biggest new feature in ndarray 0.11 is the ability to combine slicing and subviews in a single operation. To get the same result in earlier versions of ndarray, you’d have to chain together multiple .slice() and .into_subview() calls while being careful with the order of the calls. For example,

let arr = array![[[ 1,  2,  3,  4],
                  [ 5,  6,  7,  8]],
                 [[ 9, 10, 11, 12],
                  [13, 14, 15, 16]]];
assert_eq!(arr.shape(), &[2, 2, 4]);

// old
let slice = arr.slice(s![.., .., 1..;2])
    .into_subview(Axis(1), 0)
    .into_subview(Axis(0), 1);

// new
let slice = arr.slice(s![1, 0, 1..;2]);

assert_eq!(slice.shape(), &[2]);
assert_eq!(slice, array![10, 12]);

In this example, the 1..;2 indicates a slice of axis 2 (starting at index 1 and stepping by 2), and the 1 and 0 indicate subviews of axes 0 and 1, respectively. The resulting slice is a view of a subset of arr (without copying). The new version is more concise and is easier to use correctly than a series of .slice() and .into_subview() calls.

You’ll still get a compile-time error if the dimensionality of the array/view being sliced doesn’t match the number of arguments in the s![] macro, and the dimensionality of the sliced array/view is still determined at compile-time.

You may be interested in how this feature was implemented. It’s a story of type hacking: zero-sized types, associated types, traits for compile-time type manipulation, and pointer casting.

Other new features

ArrayBase has new .slice_axis(), .slice_axis_mut(), and .slice_axis_inplace() methods to slice individual axes. These methods simplify functions that slice generic-dimensional arrays/views. For example,

// old
fn reverse_first_axis<A, D: Dimension>(view: ArrayView<A, D>) -> ArrayView<A, D> {
    let mut slice_info = vec![Si(0, None, 1); view.ndim()];
    slice_info[0].2 = -1;
    let mut view_dyn = view.into_dyn();
    view_dyn.islice(&slice_info);
    view_dyn.into_dimensionality().unwrap()
}

// new
fn reverse_first_axis<A, D: Dimension>(mut view: ArrayView<A, D>) -> ArrayView<A, D> {
    view.slice_axis_inplace(Axis(0), Slice::new(0, None, -1));
    view
}

ArrayBase has a new .slice_move() method which consumes the array/view and returns a new one. This method is necessary to be able to change the number of dimensions when slicing an owned array into a smaller owned array. (The .slice_inplace() method (called .islice() in 0.10) can’t change the number of dimensions because it can’t change the type of its argument.) Adding .slice_move() also has the nice side-effect that some uses of slicing are now a little more ergonomic, especially when taking ownership of ArrayViews. For example,

// old
fn reverse_view<A>(mut view: ArrayView1<A>) -> ArrayView1<A> {
    view.islice(s![..;-1]);
    view
}

// new
fn reverse_view<A>(view: ArrayView1<A>) -> ArrayView1<A> {
    view.slice_move(s![..;-1])
}

The s![] macro now supports more index types, such as usize, so you no longer have to manually cast usize indices to isize.

We’ve added many other new features since the last release announcement. See the full release notes for more!

Updating existing code

There are breaking changes in ndarray 0.11, but updating most code written for 0.10 is fairly straightforward. The most common updates will be:

If you explicitly used the old Si type, switch to using the s![] macro if possible. If you need to pass around slicing information for a single axis, you can use the new Slice type, which is very similar to the old Si type and can be used as an argument to the s![] macro and the .slice_axis*() methods. If s![] doesn’t work for your use-case, SliceInfo is analogous to the old [Si; n] type and can be constructed manually from an array/slice/Vec of SliceOrIndex.

The output of s![] may not automatically coerce into the parameter type for .slice*() in all of the places it did in earlier versions of ndarray (primarily when dealing with dynamic-dimensional arrays). You can call .as_ref() on the output of s![] to perform the necessary conversion.

See the full release notes for more.

Thanks

Many thanks to bluss for maintaining ndarray and helping me (jturner314) refine the design of the new features, and, of course, for creating ndarray in the first place! Thanks also to various people on IRC for answering my Rust questions.