Skip to content

Support subviews in slice s! syntax #215

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
clamydo opened this issue Aug 19, 2016 · 8 comments · Fixed by #377
Closed

Support subviews in slice s! syntax #215

clamydo opened this issue Aug 19, 2016 · 8 comments · Fixed by #377

Comments

@clamydo
Copy link

clamydo commented Aug 19, 2016

It would be nice, if giving just an integer (not a range) to the s! macro, would result into a subview.

I imagine it like this

let a = arr3(&[[[ 1,  2,  3],
                [ 4,  5,  6]], 
               [[ 7,  8,  9],
                [10, 11, 12]]]);

let a = a.slice(s![0, .., 1]);
let r = arr2(&[[2],[5]]);
assert_eq!(a, r);

where a is a vector (an array with shape (2,1)). So this would make slicing similar to the a[0, :, 1] syntax in Python's Numpy.

Is this feasible?

@bluss
Copy link
Member

bluss commented Aug 19, 2016

It's a natural idea, but it's not easily realizable because taking subviews changes the number of of axes, which changes the type of the array (D type parameter), unless the array is a dynamically dimensioned array (D is Vec<Ix>, and this mode is not properly supported either).

Some major type hackery would be needed for the slice method to support this, but another macro might be able to do it.

@bluss
Copy link
Member

bluss commented Aug 19, 2016

We have a fundamental tension in our choice to have explicit type-level axis counts in ndarray. Ix is different from (Ix, Ix) and so on. This could all be dynamic (more like python), but with the detriment that low-level arrays and array views become much more heavyweight. Consider that a ndarray class instance in numpy in Python is kilobytes of data. We've chosen to not go that route.

@bluss
Copy link
Member

bluss commented Apr 3, 2017

This can be implemented for dynamic dimension arrays. Needs a design that shows how, though :)

@jturner314
Copy link
Member

I'd really like this feature, too. I put together a proof-of-concept of one possible strategy: jturner314/ndarray@7b41f5f

Basically, what I did is:

  1. Use an enum Si2 to represent either a range or a single index. (Note that it could alternatively be defined as enum Si2 { Range(Ixs, Option<Ixs>, Ixs), Subview(Ixs)) to get rid of Si.)
  2. Add a new macro s2! that is basically the same as s! with two differences:
    • It allows isize in addition to range elements. (You can do s2![1..5, 3, 4..;2], for example.)
    • It returns the correct output dimension type in addition to the indexing information.
  3. Add a new method ArrayBase::slice2 that takes the indexing information and output dimension type as arguments. I didn't write the implementation, but it should be doable because the output dimension is now known.

The code needs some refinement but shows that this feature is possible with basically the same API as the existing slicing functionality. What do you think?

@bluss
Copy link
Member

bluss commented Oct 16, 2017

Looks absolutely promising, happy you're keeping the compile time checking that the number of indices matches up.

@bluss
Copy link
Member

bluss commented Oct 16, 2017

Having the cryptic and short name Si is a remnant from before the s![] macro, so Si is short and cryptic since you'd need to use it once per axis.

@jturner314
Copy link
Member

jturner314 commented Oct 17, 2017

After working on this a little more, I think I prefer an alternative approach: jturner314/ndarray@1987787

This is a summary:

  1. Add an islice_axis() method to ArrayBase that slices along the specified axis.
  2. Add a trait IntoSliceAxisOrIntoSubview that calls islice_axis() or into_subview() based on whether the argument is a range or index, and add a trait IntoSliceAxisOrIntoSubviewNextAxis that determines the next axis index.
  3. Add slice!, into_slice!, and slice_mut! macros that make calls to view(), view_mut(), and into_slice_axis_or_into_subview() as necessary.

Note that it would also be possible to add an islice! macro if desired.

The implementation is simpler, and the usage is more concise (e.g. slice!(arr[1..4, 3, 2..6;3]) instead of arr.slice(s![1..4, 3, 2..6;3])).

What do you think?

@jturner314
Copy link
Member

jturner314 commented Oct 17, 2017

It's worth pointing out that it would be feasible to provide slicing without any macros in the API, if that's desired. You could do it like this:

  1. Implement From<T> for Si where T is Range<Ixs>, RangeFrom<Ixs>, RangeTo<Ixs>, RangeFull, (Range<Ixs>, Ixs), (RangeFrom<Ixs>, Ixs), (RangeTo<Ixs>, Ixs), and (RangeFull, Ixs).
  2. Implement ArrayBase::slice(T) where T is a tuple of ranges (possibly with steps) and indices. For example, the user could call arr.slice((2..5, 7, (..4, 3), 8, 3)), where the corresponding implementation is impl<R1: Into<Si>, R2: Into<Si>> Slice<(R1, Ixs, R2, Ixs, Ixs)> for ArrayBase<S, Ix5> { type Output = ArrayBase<S, Ix2>; ... }

The implementation side is the complex part of this approach, because you'd have to provide an implementation for all possible tuple inputs. It's still feasible, though, (with use of macros or code generation to write the implementation) because there are only 2^6 = 64 possibilities for the largest fixed dimension (Ix6).

My preference is the approach in my previous comment (slice!, slice_mut!, etc., macros) just for simplicity of implementation and its ability to generalize to higher dimensions without an exponentially increasing number of trait implementations.

If you like the slice!, slice_mut!, etc., approach (or the one I proposed before that), I can finish the implementation and submit a PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants