Conversation
|
@AtheMathmo: when you have time, I would appreciate if you could let me know whether you feel this is going in the right direction. If so, I'll polish it and get it ready when I have the opportunity to work on it. No rush though, I'm quite busy these days anyway! |
|
Sorry I didn't have any time earlier :(. Will be able to go through it tomorrow! |
|
I'm sorry, the purpose of my previous comment was not to make you look into it sooner. By all means, please take as much time as you want! Really did not intend to put any kind of pressure on you. I just wasn't sure if I'd given the impression that I wanted you to look at it before I invested much more time working on it, so I just wanted to make that clear :) Just making sure we're on the same page! |
|
No worries! |
|
I'll have some more comments from the code shortly. But the following is just with respects to your post body.
I agree that this should be left out for now but it's great that it can be done without further API breakage.
I think this is a great idea too! Also happy to have this follow later. Though we should do some benchmarking to confirm that we do indeed outperform the naive approach. On principle rather than following an expectation.
This would be my failing! I'm glad you picked up on this and changed it. Hopefully I'll infer from the code soon, but could you explain how we handle singular matrices (of rank k)? Do we just set the diagonal to zero? I really like that we don't need to have a specific order to unpack the values too! It's a nice plus. Ok, going to read through the code now so hopefully can give some more constructive comments. |
AtheMathmo
left a comment
There was a problem hiding this comment.
I think this is a great start! Overall it looks like the right direction to me.
Other than the comments here the only other thing I think we should discuss is the duplicate solve, inverse and det functions.
I can absolutely see why we need the extra solve function, but I'm not sure why we have the extra inverse and det functions. It seems to me that we can take advantage of the new solve function directly in the Matrix inverse and det functions? Please let me know if I'm missing anything.
|
|
||
| let b = try!(forward_substitution(&l, p * y)); | ||
| back_substitution(&u, b) | ||
| self.clone().lu().solve(y) |
There was a problem hiding this comment.
Might also be worth modifying the API here to consume self. Under the same mantra that if we're cloning &self the api is broken.
There was a problem hiding this comment.
Completely agree! One could also argue to remove Matrix::solve completely, but I suppose it's useful as the user doesn't have to worry about decompositions if he just wants to solve a linear system. That said, I think the documentation should make it clear that it is just a convenience wrapper around lu().
There was a problem hiding this comment.
Yes I agree. We should state what is going on but I think many users wont want to think about LU decompositions and having solve on Matrix will be a big help.
| Error::new(ErrorKind::DecompFailure, | ||
| "Could not compute LUP factorization for inverse.") | ||
| })); | ||
| let LU { l, u, p } = self.clone().lu().decompose(); |
| 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, -4.0, 1.0, 0.0; | ||
| 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, -4.0, 1.0; | ||
| 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, -4.0]; | ||
| // TODO: Fix these tests to take the new API into account |
There was a problem hiding this comment.
Just adding a comment here so that I don't forget about these.
| use libnum::{Float}; | ||
|
|
||
| /// TODO | ||
| pub trait Decomposition { |
There was a problem hiding this comment.
I'm not sure about the name (and partly the functionality) of this trait.
Is the idea that this trait represents a decomposition which returns multiple values that we're packing into a custom struct? Maybe we should have the function be called unpack?
Actually this probably is fine. I think my worry is that people may think that this decompose function does all of the legwork whereas actually there are custom functions doing this (like lu).
There was a problem hiding this comment.
That's a very good point! I picked it because I liked the look of it, i.e.: lu().decompose(), but this can sound a little misleading as you point out. I'll take some time to think about this.
The name of the trait is simply due to the fact that the only thing in common between different kinds of decomposition/factorizations is that they all reduce a single matrix A into a number of matrices M_1, M_2, ..., M_n such that A = M_1 * M_2 * ... * M_n. My idea was hence to encode this into a trait (I just didn't document it yet), as although the decompositions will offer different kinds of functionality, they will at least all share this property.
I'm certainly open to changing the names of both trait and/or function!
There was a problem hiding this comment.
I'll think about it too. What you said definitely makes sense and I think the naming probably is fine especially if we make the purpose of the trait clear in the docs.
|
Eh, some Github issues here. Apparently I accidentally deleted my previous response about the singular matrices (Github showed 3 copies of it, and then suddenly they were all gone after deleting one of them). From memory, it said something to the effect of: About the singular matrices, I'm not entirely convinced that I'm handling it correctly, but I will investigate this more thoroughly. I believe the essence is indeed to let the diagonal in U be zero, zero out the remaining elements in L, and leave the rest in U unchanged. This is currently doing more work than needed in this case, so I'll probably special case it a little to avoid redundant operations. As for the existence of |
|
Actually yes I agree that having these in there is a good idea. I actually have a use case in rusty-machine where I need both the inverse and the determinant. |
|
@AtheMathmo: one thing I realized about I could take this into account by moving the LU decomposition into an internal method which returns a EDIT: This also applies to |
|
Hey @AtheMathmo: could we consider adding |
|
I agree with your comments about early-exiting for some of the LU routines. If you can find a nice way to get that into the API I think that it is worth having. Perhaps have a private function in the As for |
That's more or less exactly what I had in mind :) We'll see how it turns out in the end though.
Well, I mostly just recall thinking "itertools would have been useful now" several times throughout the contributions I've made. In this case, it would be useful to have One more thing: Currently the scalar type is a generic parameter for trait BaseMatrix {
type Scalar;
// ...
}I'm currently working on my ideas for the The only downside I've found so far is that you wouldn't be able to mix operations with different scalar fields, but we would never do that anyway (i.e. multiply a matrix of type (I wish I could somehow stop coming up with all these long-winded questions for you!) |
Permutation is a representation of a permutation of an abstract ordered set. It will greatly simplify the implementation of PermutationMatrix.
|
I think that @tafia played around with associated types in this PR: #18 . I was a little unsure whether they would really help and it looks like after toying with them a little he decided against them too. I think that it might be a good idea to try adding them in the hope that it might clean up the code in some places. If @tafia remembers why he decided against them I'd love to hear from him too! Something else I have been thinking about is adding more trait bounds to the |
I skimmed through the conversation in #18. Would indeed be interesting to hear what made @tafia feel that associated types don't feel so nice, and in what way he was leveraging associated types.
Well, we'd have to think about what exactly constitutes a matrix. For example, in fields such as graph theory, a matrix of
|
|
I haven't had any time to do more work on this lately, but I have a small update. I realized that my previous comment about LUP decompositions for singular matrices is only partly true. While it is true that every square matrix has an LUP decomposition, the common LUP decomposition algorithms using partial pivoting are only numerically stable for well-conditioned invertible matrices. LUP decompositions for singular matrices are typically computed by "full pivoting", in which case one also obtains an additional permutation matrix (as one permutes both the columns and rows). The most straightforward explanation of this I could find was in Eigen's documentation. This suggests we should probably have multiple LU decompositions in the future, but I suppose for now, it is sufficient to only support partial pivoting. |
|
Sorry for the late answer!
From what I remember it was just too verbose and didn't really bring benefit (the api didn't look more explicit). But I don't have any strong opinion on the matter and I would be happy whatever the decision you guys choose. |
|
@Andlon - thanks for looking into the singular matrix decomposition. I agree that we should limit ourselves for now and move on to singular matrices at a later date. I'll add a ticket for this. @tafia thanks for your comments! I think it would be a good idea to at least look into using associated types - especially if this is a common pattern. Sorry for pushing you away from this idea in the first place @tafia ! |
|
Closing in favor of #142, which will eventually supersede the functionality in this PR. |
This is by no means finished, but I wanted to give contributors an opportunity to weigh in with some opinions, if anyone wants to.
In line with some of the ideas discussed in #40, I've recently started to put together a redesign of the LU decomposition. If we reach concencus on the ideas put forth, I plan to do a similar redesign of the other decompositions.
Currently, the LU(P) decomposition as implemented in
rulinalgsimply returns a triplet of the three matricesL,UandP. One of the strengths of the LU decomposition, is that once decomposed, one can very efficiently solve many linear systemsAx = bwhere the matrixAis constant, butbvaries. It would thus be convenient if this functionality was readily available and optimized through rulinalg's API. Thus, I propose to instead let the decomposition return a specialized type that makes this operation easier.In addition, returning all three matrices is not optimal in terms of storage space. In particular, one can store the L and U matrices in the space of a single matrix, rather than two separate matrices, halving the memory costs. This is not yet implemented in this PR, but it's possible to implement without breaking changes.
Finally, I want to implement a custom
PermutationMatrixstructure. Currently the permutation matrix is returned as a fullMatrix<T>, but by its nature, a permutation matrix needs onlyO(n)storage, and it can be applied much more efficiently than performing a full matrix-matrix or matrix-vector application. This is also not yet implemented in this PR.I've also made one more important change: for whatever reason, the LUP decomposition that is currently implemented fails if the matrix is singular. However, there always exists an LUP decomposition for any square matrix, so I've taken this into account, and as such we no longer need to return a
Resultfor the decomposition itself.In short, we currently write:
With the proposed changes, we have instead:
Note the last line in the previous example. I've explicitly avoided returning a triplet here, because it's very easy to mess up the order. In rulinalg we have (l, u, p), but i.e. NumPy returns (p, l, u). I even made this mistake once when working with the previous API. These sort of errors can be very frustrating if they are not quickly discovered! Of course, with the above struct deconstructing API, the call is order-independent, so the last line could have been replaced with
LU { p, l, u }orLU { u, p, l }without having any effect on the behavior of the code.