logicaffeine_base/
matrix_exp.rs1use crate::algebraic::Algebraic;
13use crate::complex_algebraic::{complex_roots, ComplexAlgebraic};
14use crate::exact_real::ExactReal;
15use crate::numeric::Rational;
16use crate::poly::Poly;
17
18pub fn characteristic_polynomial(m: &[Vec<Rational>]) -> Option<Poly> {
20 let n = m.len();
21 if n == 0 || m.iter().any(|r| r.len() != n) {
22 return None;
23 }
24 let mul = |a: &[Vec<Rational>], b: &[Vec<Rational>]| -> Vec<Vec<Rational>> {
25 (0..n)
26 .map(|i| {
27 (0..n)
28 .map(|j| (0..n).fold(Rational::zero(), |acc, k| acc.add(&a[i][k].mul(&b[k][j]))))
29 .collect()
30 })
31 .collect()
32 };
33 let mut c = vec![Rational::zero(); n + 1];
34 c[n] = Rational::one();
35 let mut acc = vec![vec![Rational::zero(); n]; n];
36 for k in 1..=n {
37 acc = mul(m, &acc);
38 for (i, row) in acc.iter_mut().enumerate() {
39 row[i] = row[i].add(&c[n - k + 1]);
40 }
41 let am = mul(m, &acc);
42 let trace = (0..n).fold(Rational::zero(), |t, i| t.add(&am[i][i]));
43 c[n - k] = trace
44 .negated()
45 .div(&Rational::from_i64(k as i64))
46 .expect("k ≥ 1");
47 }
48 Some(Poly::from_coeffs(c))
49}
50
51fn falling(k: usize, j: usize) -> i64 {
53 if j > k {
54 return 0;
55 }
56 ((k - j + 1)..=k).map(|v| v as i64).product::<i64>().max(1)
57}
58
59fn invert(mut a: Vec<Vec<ComplexAlgebraic>>) -> Option<Vec<Vec<ComplexAlgebraic>>> {
61 let n = a.len();
62 let mut inv: Vec<Vec<ComplexAlgebraic>> = (0..n)
63 .map(|i| {
64 (0..n)
65 .map(|j| {
66 if i == j {
67 ComplexAlgebraic::one()
68 } else {
69 ComplexAlgebraic::zero()
70 }
71 })
72 .collect()
73 })
74 .collect();
75 for col in 0..n {
76 let pivot = (col..n).find(|&r| !a[r][col].is_zero())?;
77 a.swap(col, pivot);
78 inv.swap(col, pivot);
79 let scale = a[col][col].recip()?;
80 for j in 0..n {
81 a[col][j] = a[col][j].mul(&scale);
82 inv[col][j] = inv[col][j].mul(&scale);
83 }
84 for r in 0..n {
85 if r == col || a[r][col].is_zero() {
86 continue;
87 }
88 let f = a[r][col].clone();
89 for j in 0..n {
90 let (pa, pi) = (a[col][j].mul(&f), inv[col][j].mul(&f));
91 a[r][j] = a[r][j].sub(&pa);
92 inv[r][j] = inv[r][j].sub(&pi);
93 }
94 }
95 }
96 Some(inv)
97}
98
99pub fn exp_matrix(m: &[Vec<Rational>], t: &Rational) -> Option<Vec<Vec<ExactReal>>> {
101 let n = m.len();
102 let chi = characteristic_polynomial(m)?;
103 let mut nodes: Vec<(ComplexAlgebraic, usize)> = Vec::new();
104 for (factor, multiplicity) in chi.factor_over_q() {
105 for lambda in complex_roots(&factor) {
106 nodes.push((lambda, multiplicity as usize));
107 }
108 }
109 let mut rows: Vec<Vec<ComplexAlgebraic>> = Vec::with_capacity(n);
110 let mut labels: Vec<(usize, usize)> = Vec::with_capacity(n);
111 for (i, (lambda, multiplicity)) in nodes.iter().enumerate() {
112 for j in 0..*multiplicity {
113 rows.push(
114 (0..n)
115 .map(|k| {
116 let f = falling(k, j);
117 if f == 0 {
118 ComplexAlgebraic::zero()
119 } else {
120 lambda.pow((k - j) as u32).scale(&Rational::from_i64(f))
121 }
122 })
123 .collect(),
124 );
125 labels.push((i, j));
126 }
127 }
128 let w = invert(rows)?;
129 let identity: Vec<Vec<Rational>> = (0..n)
130 .map(|i| {
131 (0..n)
132 .map(|j| if i == j { Rational::one() } else { Rational::zero() })
133 .collect()
134 })
135 .collect();
136 let mut powers = vec![identity];
137 for _ in 1..n {
138 let last = powers.last().expect("the identity is there");
139 let next: Vec<Vec<Rational>> = (0..n)
140 .map(|i| {
141 (0..n)
142 .map(|j| (0..n).fold(Rational::zero(), |acc, k| acc.add(&last[i][k].mul(&m[k][j]))))
143 .collect()
144 })
145 .collect();
146 powers.push(next);
147 }
148 let mut out = vec![vec![ExactReal::from_i64(0); n]; n];
149 for (r, row) in out.iter_mut().enumerate() {
150 for (s, entry) in row.iter_mut().enumerate() {
151 let mut weights = vec![ComplexAlgebraic::zero(); nodes.len()];
152 for (col, (node, j)) in labels.iter().enumerate() {
153 let alpha = (0..n).fold(ComplexAlgebraic::zero(), |acc, k| {
154 acc.add(&w[k][col].scale(&powers[k][r][s]))
155 });
156 weights[*node] = weights[*node].add(&alpha.scale(&t.pow(*j as i32).expect("t^j")));
157 }
158 *entry = assemble(&nodes, &weights, t);
159 }
160 }
161 Some(out)
162}
163
164fn assemble(nodes: &[(ComplexAlgebraic, usize)], weights: &[ComplexAlgebraic], t: &Rational) -> ExactReal {
166 let t_alg = Algebraic::from_rational(t.clone());
167 let mut total = ExactReal::from_i64(0);
168 for ((lambda, _), beta) in nodes.iter().zip(weights) {
169 if beta.is_zero() {
170 continue;
171 }
172 let growth = ExactReal::from_algebraic(lambda.re.mul(&t_alg)).exp();
173 if lambda.is_real() {
174 debug_assert!(beta.is_real(), "a real eigenvalue's weight is real");
175 total = total.add(&ExactReal::from_algebraic(beta.re.clone()).mul(&growth));
176 } else if lambda.im.signum() > 0 {
177 let angle = ExactReal::from_algebraic(lambda.im.mul(&t_alg));
178 let two = Algebraic::from_i64(2);
179 let cosine = ExactReal::from_algebraic(beta.re.mul(&two)).mul(&angle.cos());
180 let sine = ExactReal::from_algebraic(beta.im.mul(&two)).mul(&angle.sin());
181 total = total.add(&growth.mul(&cosine.sub(&sine)));
182 }
183 }
184 total
185}
186
187#[cfg(test)]
188#[path = "../../logicaffeine_tests/unit/logicaffeine_base/matrix_exp/tests.rs"]
189mod tests;