-
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
Feature request: symmetric matrix constructors #144
Comments
Using the function I mentioned in #145, here's a sketch of a symmetric matrix constructor: fn upper_diag_inds(nrows: usize, ncols: usize) -> Vec<[usize; 2]> {
(0..nrows)
.enumerate()
.flat_map(|(iter_ind, row_ind)| {
(iter_ind..ncols).map(move |col_ind| {
[row_ind, col_ind]
})
}).collect()
}
// Computes the inverse of the triangular number function. Returns Result,
// because not every number is triangular.
fn triangular_inverse(tri_num: usize) -> Result<usize, Error> {
let test_num = (((tri_num * 8 + 1) as Float).sqrt() - 1) / 2;
if test_num == test_num.floor() {
Ok(test_num)
} else {
Err("Input {} was not triangular.", &tri_num);
}
}
// Assumes that the values are passed in row-major order, with all values below
// the diagonal omitted.
fn symmetric_matrix<T>(values: Vec<T>) -> Matrix<T>
where T: NumCast + Float + Any {
// Panics here if the number of values for the upper diagonal is not a
// triangular number.
let dim: usize = triangular_inverse(values.len()).unwrap();
let mat: Matrix<T> = Matrix::zeros(dim, dim);
for (val_ind, upper_diag_ind) in upper_diag_inds(dim, dim).enumerate() {
mat[upper_diag_ind] = values[val_ind];
// Add elements to transpose
if upper_diag_ind[0] != upper_diag_ind[1] {
mat[upper_diag_ind[upper_diag_ind[1],
upper_diag_ind[0]]] = values[val_ind];
}
}
mat
} |
Sorry for my slow response on this! I agree with you that adding simple constructors for these matrices could be a good thing. I'll give my thoughts on this issue and #145 together here. Firstly I think that because these are fairly niche things they are probably better kept a little apart from the current Matrix API. Additionally we have had some discussion about Symmetric/Triangular matrix storage in the past and these types seem like they may be better tackled as part of that work. See @Andlon 's work on the Permutation matrix for an idea of what I'm talking about. As for the for i in 0..rows {
for j in i..cols {
}
} As always I'm happy to hear thoughts from the other side. Let me know if I am missing anything! |
I'll think more deeply about the other matrix items. I think you're right that there is potential if matrices that are known to have special properties are given "special" status within the library, certainly if they are known to be The reason why it's useful to have a On the other hand, if there was a |
I think in general it's nice to provide some functionality for working with commonly occurring matrices with special structure. However, I think we should maybe think about this from a usage perspective. What applications actually require this functionality? Let's take the symmetric matrix construction example. The proposed function takes a vector of values corresponding to the upper triangular part of the matrix. In most applications that I can think of, it's significantly harder to build a vector of such values than to work with a full matrix, and such a function would just copy the values into a full matrix anyway, so it would be both more intuitive and more efficient to just build a full matrix in the first place, using a double for loop or whatever is appropriate for your application. A useful alternative, as @AtheMathmo mentioned, would be to have a custom struct for Now, the Toeplitz matrix is interesting, however. Like the Finally, I just want to say that while I think custom storage for e.g. triangular, symmetric and other types of matrices is too much work to be worth it, a more enticing approach is the concept of special matrix "wrappers" through which one could access special APIs for special matrices. We've discussed this before in various different issues. As an example, one could imagine this (purely imaginary, not sure if it would work out this way): let x = matrix![ 1.0, 0.0, 0.0;
2.0, 1.0, 0.0;
1.0, 2.0, 3.0];
// Wrapper verifies that the matrix satisfies the lower triangular constraint
let lower_triangular = LowerTriangular::from_matrix(x).expect("Matrix is lower triangular");
// Wrapper makes no verification
let lower_triangular = LowerTriangular::from_matrix_unchecked(x);
// Wrapper verifies using an element-wise tolerance rather than exactly zero
let lower_triangular = LowerTriangular::from_matrix_with_tol(x, 1e-12)
.expect("Matrix is lower triangular to given precision");
// Once obtained, one can efficiently solve the triangular system
let y = lower_triangular.solve(Vector::zeros(3))
.expect("Triangular system is invertible.")
// Or compute the determinant
let det = lower_triangular.det(); A major benefit of such an approach, is that it allows us to check that the constraint is satisfied exactly once, and then solve many such systems without risk of the constraint breaking between different calls to |
Some common and useful symmetric matrices it would be nice to construct in one step:
https://en.wikipedia.org/wiki/Symmetric_matrix
The text was updated successfully, but these errors were encountered: