Skip to content

Commit f7498e2

Browse files
committed
[update] smaller arrays to use gaussian elimination for computing the determinant
1 parent d00056e commit f7498e2

File tree

1 file changed

+139
-27
lines changed

1 file changed

+139
-27
lines changed

crates/lambda-rs/src/math/matrix.rs

Lines changed: 139 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,62 @@ pub fn identity_matrix<
259259
return result;
260260
}
261261

262+
/// Compute determinant via Gaussian elimination on fixed-size stack storage.
263+
///
264+
/// This avoids heap allocations in hot fixed-size paths (for example 4x4
265+
/// transform matrices).
266+
///
267+
/// # Arguments
268+
///
269+
/// - `data`: Square matrix values stored in stack-allocated fixed-size arrays.
270+
///
271+
/// # Returns
272+
///
273+
/// - Determinant of `data`.
274+
/// - `0.0` when elimination detects a near-zero pivot (singular matrix).
275+
fn determinant_gaussian_stack<const N: usize>(mut data: [[f32; N]; N]) -> f32 {
276+
let mut sign = 1.0_f32;
277+
for pivot in 0..N {
278+
let mut pivot_row = pivot;
279+
let mut pivot_abs = data[pivot][pivot].abs();
280+
for (candidate, row) in data.iter().enumerate().skip(pivot + 1) {
281+
let candidate_abs = row[pivot].abs();
282+
if candidate_abs > pivot_abs {
283+
pivot_abs = candidate_abs;
284+
pivot_row = candidate;
285+
}
286+
}
287+
288+
if pivot_abs <= f32::EPSILON {
289+
return 0.0;
290+
}
291+
292+
if pivot_row != pivot {
293+
data.swap(pivot, pivot_row);
294+
sign = -sign;
295+
}
296+
297+
let pivot_value = data[pivot][pivot];
298+
let pivot_tail = data[pivot];
299+
for row in data.iter_mut().skip(pivot + 1) {
300+
let factor = row[pivot] / pivot_value;
301+
if factor == 0.0 {
302+
continue;
303+
}
304+
for (dst, src) in row[pivot..].iter_mut().zip(pivot_tail[pivot..].iter())
305+
{
306+
*dst -= factor * *src;
307+
}
308+
}
309+
}
310+
311+
let mut det = sign;
312+
for (i, row) in data.iter().enumerate().take(N) {
313+
det *= row[i];
314+
}
315+
return det;
316+
}
317+
262318
// -------------------------- ARRAY IMPLEMENTATION -----------------------------
263319

264320
/// Matrix implementations for arrays of f32 arrays. Including the trait Matrix into
@@ -325,7 +381,15 @@ where
325381
todo!()
326382
}
327383

328-
/// Computes the determinant of any square matrix using Laplace expansion.
384+
/// Computes the determinant of any square matrix using Gaussian elimination
385+
/// with partial pivoting.
386+
///
387+
/// # Behavior
388+
///
389+
/// - Uses stack-allocated elimination for 3x3 and 4x4 matrices to avoid heap
390+
/// allocation on common transform sizes.
391+
/// - Uses a generic dense fallback for larger matrices.
392+
/// - Overall asymptotic runtime is `O(n^3)` for square `n x n` matrices.
329393
fn determinant(&self) -> Result<f32, MathError> {
330394
let rows = self.as_ref().len();
331395
if rows == 0 {
@@ -341,34 +405,83 @@ where
341405
return Err(MathError::NonSquareMatrix { rows, cols });
342406
}
343407

344-
return match rows {
345-
1 => Ok(self.as_ref()[0].as_ref()[0]),
346-
2 => {
347-
let a = self.at(0, 0);
348-
let b = self.at(0, 1);
349-
let c = self.at(1, 0);
350-
let d = self.at(1, 1);
351-
return Ok(a * d - b * c);
408+
if rows == 1 {
409+
return Ok(self.as_ref()[0].as_ref()[0]);
410+
}
411+
412+
if rows == 2 {
413+
let a = self.at(0, 0);
414+
let b = self.at(0, 1);
415+
let c = self.at(1, 0);
416+
let d = self.at(1, 1);
417+
return Ok(a * d - b * c);
418+
}
419+
420+
if rows == 3 {
421+
let mut data = [[0.0_f32; 3]; 3];
422+
for (i, row) in data.iter_mut().enumerate() {
423+
for (j, value) in row.iter_mut().enumerate() {
424+
*value = self.at(i, j);
425+
}
426+
}
427+
return Ok(determinant_gaussian_stack::<3>(data));
428+
}
429+
430+
if rows == 4 {
431+
let mut data = [[0.0_f32; 4]; 4];
432+
for (i, row) in data.iter_mut().enumerate() {
433+
for (j, value) in row.iter_mut().enumerate() {
434+
*value = self.at(i, j);
435+
}
436+
}
437+
return Ok(determinant_gaussian_stack::<4>(data));
438+
}
439+
440+
// Convert to a mutable dense matrix so we can perform in-place elimination.
441+
let mut working = vec![vec![0.0_f32; rows]; rows];
442+
for (i, row) in working.iter_mut().enumerate() {
443+
for (j, value) in row.iter_mut().enumerate() {
444+
*value = self.at(i, j);
352445
}
353-
_ => {
354-
let mut result = 0.0;
355-
for i in 0..rows {
356-
let mut submatrix: Vec<Vec<f32>> = Vec::with_capacity(rows - 1);
357-
for j in 1..rows {
358-
let mut row = Vec::new();
359-
for k in 0..rows {
360-
if k != i {
361-
row.push(self.at(j, k));
362-
}
363-
}
364-
submatrix.push(row);
365-
}
366-
let sub_determinant = submatrix.determinant()?;
367-
result += self.at(0, i) * sub_determinant * (-1.0_f32).powi(i as i32);
446+
}
447+
448+
let mut sign = 1.0_f32;
449+
for pivot in 0..rows {
450+
// Partial pivoting improves numerical stability and avoids division by 0.
451+
let mut pivot_row = pivot;
452+
let mut pivot_abs = working[pivot][pivot].abs();
453+
for (candidate, row) in working.iter().enumerate().skip(pivot + 1) {
454+
let candidate_abs = row[pivot].abs();
455+
if candidate_abs > pivot_abs {
456+
pivot_abs = candidate_abs;
457+
pivot_row = candidate;
368458
}
369-
return Ok(result);
370459
}
371-
};
460+
461+
if pivot_abs <= f32::EPSILON {
462+
return Ok(0.0);
463+
}
464+
465+
if pivot_row != pivot {
466+
working.swap(pivot, pivot_row);
467+
sign = -sign;
468+
}
469+
470+
let pivot_value = working[pivot][pivot];
471+
let pivot_tail: Vec<f32> = working[pivot][pivot..].to_vec();
472+
for row in working.iter_mut().skip(pivot + 1) {
473+
let factor = row[pivot] / pivot_value;
474+
if factor == 0.0 {
475+
continue;
476+
}
477+
for (dst, src) in row[pivot..].iter_mut().zip(pivot_tail.iter()) {
478+
*dst -= factor * *src;
479+
}
480+
}
481+
}
482+
483+
let diagonal_product = (0..rows).map(|i| working[i][i]).product::<f32>();
484+
return Ok(sign * diagonal_product);
372485
}
373486

374487
/// Return the size as a (rows, columns).
@@ -394,7 +507,6 @@ where
394507

395508
#[cfg(test)]
396509
mod tests {
397-
398510
use super::{
399511
filled_matrix,
400512
identity_matrix,

0 commit comments

Comments
 (0)