Cannot use generic const parameters in arithmetic operations
impl<T, const R: usize, const C: usize> Matrix<T, R, C>
where
T: Default + Copy,
{
pub fn minor_matrix(&self, row: usize, col: usize) -> Matrix<T, { C - 1 }, { R - 1 }> {
let mut result = Matrix::default();
let mut new_r = 0;
for r in 0..R {
if r == row {
continue;
}
let mut new_c = 0;
for c in 0..C {
if c == col {
continue;
}
result[new_r][new_c] = self[r][c];
new_c += 1;
}
new_r += 1;
}
result
}
}
It works (or seems to work) with the nightly-only generic_const_exprs feature: Rust Playground.
Though do note that there’s a warning: “the feature generic_const_exprs is incomplete and may not be safe to use and/or cause compiler crashes”.
I think there’s 0 chance of something like that using const generics working on stable. There are other ways of encoding numbers in the type system, though.
N-1 as generic param cannot work with recursive determinant
impl<T, const N: usize> Matrix<T, N, N>
where
T: Default + Copy + Sub<Output = T> + Mul<Output = T> + AddAssign + Neg<Output = T>,
{
const fn determinant(&self) -> T {
if N == 1 {
self[0][0]
} else if N == 2 {
self[0][0] * self[1][1] - self[1][0] * self[0][1]
} else {
let mut det = T::default();
for j in 0..N {
let minor = self.minor_matrix(0, j);
if j % 2 == 0 {
det += self[0][j] * minor.determinant();
} else {
det += -(self[0][j] * minor.determinant());
} ;
}
det
}
}
}
Remember to account for N==0; integer underflow might be causing a compile-time error.
Also, a match would be more idiomatic than if / else if on the same variable.
impl<T, const R: usize, const C: usize> Matrix<T, R, C>
where
T: Default + Copy,
[(); R - 1]:,
[(); C - 1]:,
{
pub fn minor_matrix(&self, row: usize, col: usize) -> Matrix<T, { R - 1 }, { C - 1 }>
where
{
let mut result = Matrix::default();
let mut new_r = 0;
for r in 0..R {
if r == row {
continue;
}
let mut new_c = 0;
for c in 0..C {
if c == col {
continue;
}
result[new_r][new_c] = self[r][c];
new_c += 1;
}
new_r += 1;
}
result
}
}
impl<T, const N: usize> Matrix<T, N, N>
where
T: Default + Copy + Sub<Output = T> + Mul<Output = T> + AddAssign + Neg<Output = T>,
[(); N - 1]:,
{
fn determinant(&self) -> T {
match N {
1 => self[0][0],
2 => self[0][0] * self[1][1] - self[1][0] * self[0][1],
_ => {
let mut det = T::default();
for j in 0..N {
let minor = self.minor_matrix(0, j);
if j % 2 == 0 {
det += self[0][j] * minor.determinant();
} else {
det += -(self[0][j] * minor.determinant());
};
}
det
}
}
}
}
However, I should have already constrained N != 0.
The Rust compiler seems unable to infer generic recursion in minor.determinent() for Matrix<T,{R-1},{C-1}>
I'm exploring Rust's generics, so I'm not using LU.
While in cpp, I can easily do like:
template <class T, size_t R, size_t C>
auto Matrix<T, R, C>::det() const
requires(R == C && std::is_arithmetic_v<T>)
{
if constexpr (R == 1) {
return data[0][0];
} else if constexpr (R == 2) {
return data[0][0] * data[1][1] - data[0][1] * data[1][0];
} else {
T determinant = 0;
for (size_t c = 0; c < C; ++c) {
T cofactor = ((c % 2 == 0) ? 1 : -1) * data[0][c];
auto minor = minor_matrix(0, c);
determinant += cofactor * minor.det();
}
return determinant;
}
}
Ah, I guess recursion won't work out very well. Rust tries to avoid post-monomorphization errors in its type/trait system (therefore, the minor_matrix method literally does not exist on a 0x0 matrix, and the trait bounds enforce that fact even before monomorphization), and I should've known that the type system doesn't care whether something is reachable.
if false {
let x = 1_i32.non_existent_method();
}
will throw an error, and for the same reason, it doesn't matter that the problematic function call is unreachable in the N==0 case. I don't think Rust has an equivalent of constexpr if statements... though there might be something similar with specialization? I briefly tried out implementing recursion and a base case with the min_specialization feature, but it indicated that the bound [(); N - 1]:, cannot be specialized on for whatever reason.
(There is typenum if you haven't heard of it, but feel free to have fun exploring Rust's generics and type system.)