@@ -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) ]
396509mod tests {
397-
398510 use super :: {
399511 filled_matrix,
400512 identity_matrix,
0 commit comments