Skip to content

Commit 55c8681

Browse files
committed
Add comments to the matrix exponential computation
1 parent 2e97e17 commit 55c8681

1 file changed

Lines changed: 31 additions & 5 deletions

File tree

src/model.rs

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
463480
fn 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

Comments
 (0)