@@ -460,16 +460,34 @@ fn validate_state_space_dimensions(
460460 Ok ( ( ) )
461461}
462462
463+ /// Computes a matrix exponential using a scaling-and-series strategy.
464+ ///
465+ /// Algorithm outline:
466+ ///
467+ /// 1. Compute a matrix norm (`||A||_1`) and choose a power-of-two scale `s` so
468+ /// `A / 2^s` has smaller norm.
469+ /// 2. Approximate `exp(A / 2^s)` by truncating the Taylor series
470+ /// `I + M + M^2/2! + ...`.
471+ /// 3. Recover `exp(A)` via repeated squaring:
472+ /// `exp(A) = (exp(A / 2^s))^(2^s)`.
473+ ///
474+ /// This is a simple implementation intended for moderate matrix sizes in this
475+ /// library context.
476+ ///
477+ /// References:
478+ /// - C. Moler and C. Van Loan, "Nineteen Dubious Ways to Compute the Exponential of a Matrix,
479+ /// Twenty-Five Years Later", SIAM Review, 45(1), 2003.
463480fn matrix_exponential ( mat : & na:: DMatrix < f64 > ) -> na:: DMatrix < f64 > {
464481 let n = mat. nrows ( ) ;
465482 if n == 0 {
466483 return na:: DMatrix :: < f64 > :: zeros ( 0 , 0 ) ;
467484 }
468485
469- // Scaling-and-series approximation:
470- // 1) choose a power-of-two scale so ||A/scale|| is small,
471- // 2) evaluate exp(A/scale) by truncated Taylor series,
472- // 3) square the result repeatedly to recover exp(A).
486+ // Step 1: choose a power-of-two scaling factor.
487+ //
488+ // We scale A -> A/2^s so the truncated Taylor series converges rapidly.
489+ // Using powers of two makes the reconstruction step exact in structure:
490+ // repeated squaring s times gives exponentiation by 2^s.
473491 let norm_one = max_column_sum_norm ( mat) ;
474492 let scaling_power = if norm_one <= 0.5 {
475493 0
@@ -484,7 +502,13 @@ fn matrix_exponential(mat: &na::DMatrix<f64>) -> na::DMatrix<f64> {
484502 let mut result = identity. clone ( ) ;
485503 let mut term = identity;
486504
487- // exp(M) = I + M + M^2/2! + M^3/3! + ...
505+ // Step 2: evaluate exp(M) with truncated Taylor series, where M = A/2^s.
506+ //
507+ // Recurrence:
508+ // term_k = term_{k-1} * M / k, with term_0 = I.
509+ // result = sum_k term_k.
510+ //
511+ // Stop when the latest term is numerically tiny in max-abs norm.
488512 for k in 1 ..=64 {
489513 term = ( & term * & scaled) / ( k as f64 ) ;
490514 result += & term;
@@ -495,6 +519,8 @@ fn matrix_exponential(mat: &na::DMatrix<f64>) -> na::DMatrix<f64> {
495519 }
496520 }
497521
522+ // Step 3: undo scaling by repeated squaring.
523+ // exp(A) = (exp(A/2^s))^(2^s).
498524 let mut out = result;
499525 for _ in 0 ..scaling_power {
500526 out = & out * & out;
0 commit comments