-
Notifications
You must be signed in to change notification settings - Fork 58
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
Returning MatrixSlice when requesting rows and columns #87
Comments
I want to put a little more momentum into this to see if we can come to a decision. After thinking a little more I'm coming around more with @Andlon 's thinking. I expect that most of our users would prefer I want to run a few tests to check that we don't see any obvious regressions from this modification. If all looks good then hopefully we can get this change in and push in a lot of the other PRs which are waiting on 0.4. |
One more disadvantage of switching Maybe we can play around with |
@AtheMathmo: I'm probably missing something basic here, but just based on what you said, isn't it just a matter of implementing |
Yes that would work and is much easier :). Thanks! |
A related issue: Does it actually make sense to have functions that return an Unless there are obvious situations where this is truly useful, I think the API would be a lot cleaner if it simply panicked when the index is out of bounds: mat.row_mut(i) += 2.0; vs. mat.get_row_mut(i).unwrap() += 2.0; (Here I've used the proposed feature where these functions return a |
I also just realized an ambiguity in the Consider the following: // Rows
let mat: Matrix<f64> = other_mat.row_iter().collect();
// Columns
let mat: Matrix<f64> = other_mat.col_iter().collect();
// Slice. Very contrived, but just to illustrate my point
let mat = Matrix<f64> = (0..5).map(|i| mat.sub_slice([i, 0], 2, 1))
.collect(); In particular, given a set of This would be solved by custom // Wouldn't be easily possible with custom `Row` struct,
// as we don't want to reimplement all binary operators
mat.row_mut(i) += 2.0;
// However, this works if we implement Deref
let row_doubled = mat.row(5) * 2.0;
// We could restore the top example with:
mat.row_mut(i).as_slice() += 2.0; The end result here is not as nice as the above example, but I suppose it's bearable, and you don't need to provide |
@AtheMathmo I would strongly prefer these references (particularly Couldn't you just That would mean that you could keep the |
There's a lot to comment on here (and I will jump in to say more soon), but in response to @andrewcsmith: we cannot guarantee that I also agree that returning a |
@AtheMathmo Oh right, I was thinking specifically of the current implementation of It does seem that there are a few places where we can guarantee contiguous data at compile time, and if I understand correctly you don't want to lose that compile-time-optimization. We might then create two structs: I'm taking some inspiration in these thoughts from Niko Matsakis's series on parallel iterators in Rayon, which splits the functionality of what can be parallelized into a few different categories. And, I think for such a performance-sensitive library like rulinalg, increasing type complexity is worth the (potential) performance gains. *Obviously these names are terrible. |
@andrewcsmith: that idea certainly has merit, and in many ways would be ideal. One could also leverage
EDIT: Actually, when considering only the functionality you mentioned in your example, it might perhaps work with generic binary operators on the |
@Andlon Right, that gets complicated quickly. Well, depending on the kind of available time/urgency of the issue, it does seem like separate structs and traits leverage Rust's type system and low-level power in the best possible way. And of course macros could help. Anyone know anything about whether a custom Edit: Seems to me there is one instance where Macros 1.1 could help, and that is to create the macro |
Sorry I forgot to properly respond to the comments here. I think I agree with @Andlon in that we probably don't need the I also agree that having As for the explicit contiguous and non-contiguous Right now we've tried to move a lot of functionality into the I've probably missed some things. Please feel free to poke me on those. |
Regarding complexity of traits and specialized impls for non/contiguous data, here's my thought: we already have PR #83 for sparse matrices. Conceivably, whatever implementations we develop allowing interaction between contiguous and non-contiguous storage schemes should also allow interoperability with sparse matrices. I do think it would be good to have a fairly granular trait interface, as long as that granularity yields compile-time optimizations. In the case of non/contiguous data, that's definitely possible. I'm not totally clear on what is vague, aside from outlining maybe the exact functions that will fall under each trait. On having to re-implement all arithmetic operations: we could provide a |
@andrewcsmith: Currently, matrix multiplication in rulinalg is implemented in terms of the @AtheMathmo: We can let // Through Deref, this should work
let doubled_row = 2.0 * &mat.row(i);
// But I think this does not, because the receiver (left-hand side) is not auto-dereferenced
let doubled_row = &mat.row(i) * 2.0;
// However, I _think_ this works
let doubled_Row = *mat.row(i) * 2.0; Furthermore, I'm not sure if this actually works, but perhaps implementing *mat.row(i) += 2.0; It is my hope that the above would work because it first dereferences to a In any case, it's not ideal, but I thought I'd explore some options. I think we're running into some present constraints of the Rust language at the moment. Hence I think we might want to settle for something that is not too complex for now. Once specialization lands I think a much better solution will be available. Should we perhaps compile a list of desired properties of the solution? |
Just posting to say that I'm going to try and get a draft version of this working over the weekend. Just to give us an idea of what this actually looks like. This seems a helpful idea to me as there are a lot of things floating around right now. I'll just focus on getting a working API right now, but will want to get benchmarks etc. sorted before merging. Thank you both for your comments - they are going to be very helpful! |
Have made some progress on this but ran into some problems now. Changing the
We can get around each of this by using the This makes me want to move to the custom pub struct Row<'a, T>(&'a [T])
pub struct Column<'a, T>(MatrixSlice<'a, T>) And equivalent for Unless there are any objections I'll move in this direction. To handle operator overloading for now I'm going to try using |
@AtheMathmo: I think this sounds like a very promising direction! Completely agree with everything. Your approach of just internally wrapping a regular slice for the Row iterator sounds like a very good approach.
Did you mean
Don't forget to implement |
I did mean However we will also need
I hope so! I always find it hard to tell whether I am missing an obvious way to do things or we really are right at the boundary of what is currently possible. Fortunately that boundary moves pretty quickly! |
@AtheMathmo: ah. |
It looks like we wont be able to have deref coersion in our arithmetic expressions. I think that having to explicitly deref isn't too painful though. For me |
Yes, this is what I've concluded with in earlier efforts too. That is, that auto-dereferencing does not take place on the receiver for binary operators (and likely won't do so for any foreseeable future, as it would cause potential combinatorial explosions for the Rust compiler). I also think this is acceptable, provided the docs are clear on this point, as you say. |
Well, as I probably should have expecting getting impl<'a, T: 'a> Deref for Row<'a, T> {
type Target = MatrixSlice<'a, T>;
fn deref(&self) -> &MatrixSlice<'a, T> {
unsafe {
let raw = &MatrixSlice::from_raw_parts(self.0.as_ptr(),
1,
self.0.len(),
self.0.len()) as *const _;
&*raw
}
}
} Though I haven't even begun to think about the safety implications there. We cannot get I don't think there is a way around this one sadly. EDIT: The way around I'm adopting is to have |
@AtheMathmo: I otherwise agree with your edit that this sounds like a good idea. The unsafe example you mentioned will have issues with lifetimes. Also, you could alternatively store both a matrix slice and a reference to a slice, if this helps...? (avoids having to implement the utility function on |
This issue is to move discussion from #84 and #78 in one place. I'll fill out this issue properly soon, but primarily we want to discuss how best to handle the return values on the row and column access functions.
The text was updated successfully, but these errors were encountered: