conspire/math/tensor/rank_2/logarithm/
mod.rs1#[cfg(test)]
2mod test;
3
4use std::f64::consts::TAU;
5
6use super::{
7 super::{
8 super::assert_eq_within_tols,
9 Rank2, Tensor, TensorArray, TensorError,
10 rank_0::{TensorRank0, list::TensorRank0List},
11 rank_1::{CrossProduct, TensorRank1},
12 rank_4::TensorRank4,
13 },
14 TensorRank2,
15};
16use crate::ABS_TOL;
17
18impl<const I: usize> TensorRank2<3, I, I> {
19 pub fn logm(&self) -> Result<Self, TensorError> {
21 if self.is_diagonal() {
22 let mut logm = TensorRank2::zero();
23 logm.iter_mut()
24 .enumerate()
25 .zip(self.iter())
26 .for_each(|((i, logm_i), self_i)| logm_i[i] = self_i[i].ln());
27 Ok(logm)
28 } else {
29 let tensor = self - &TensorRank2::identity();
30 let norm = tensor.norm();
31 if norm < 1e-2 {
32 let num_terms = if norm < 1e-4 {
33 2
34 } else if norm < 1e-3 {
35 3
36 } else {
37 5
38 };
39 let mut logm = tensor.clone();
40 let mut power = tensor.clone();
41 (2..=num_terms).for_each(|k| {
42 power *= &tensor;
43 logm += &power * (if k % 2 == 0 { -1.0 } else { 1.0 } / k as f64);
44 });
45 Ok(logm)
46 } else if self.is_symmetric() {
47 let mut eigenvalues = solve_cubic_symmetric(self.invariants())?;
48 if eigenvalues.iter().any(|eigenvalue| eigenvalue <= &0.0) {
49 panic!("Symmetric matrix has a non-positive eigenvalue")
50 }
51 let eigenvectors = find_orthonormal_eigenvectors(&eigenvalues, self);
52 eigenvalues
53 .iter_mut()
54 .for_each(|eigenvalue| *eigenvalue = eigenvalue.ln());
55 Ok(reconstruct_symmetric(eigenvalues, eigenvectors))
56 } else {
57 panic!("Matrix logarithm only implemented for symmetric cases")
58 }
59 }
60 }
61 pub fn dlogm(&self) -> Result<TensorRank4<3, I, I, I, I>, TensorError> {
63 if self.is_diagonal() {
64 let mut dlogm = TensorRank4::zero();
65 dlogm.iter_mut().enumerate().for_each(|(i, dlogm_i)| {
66 dlogm_i.iter_mut().enumerate().for_each(|(j, dlogm_ij)| {
67 dlogm_ij.iter_mut().enumerate().for_each(|(k, dlogm_ijk)| {
68 dlogm_ijk
69 .iter_mut()
70 .enumerate()
71 .filter(|(l, _)| i == k && &j == l)
72 .for_each(|(_, dlogm_ijkl)| {
73 *dlogm_ijkl = if assert_eq_within_tols(&self[i][i], &self[j][j])
74 .is_ok()
75 {
76 1.0 / self[j][j]
77 } else {
78 (self[i][i].ln() - self[j][j].ln()) / (self[i][i] - self[j][j])
79 }
80 })
81 })
82 })
83 });
84 Ok(dlogm)
85 } else if self.is_symmetric() {
86 let eigenvalues = solve_cubic_symmetric(self.invariants())?;
87 if eigenvalues.iter().any(|eigenvalue| eigenvalue <= &0.0) {
88 panic!("Symmetric matrix has a non-positive eigenvalue")
89 }
90 let divided_difference: Self = eigenvalues
91 .iter()
92 .map(|eigenvalue_i| {
93 eigenvalues
94 .iter()
95 .map(|eigenvalue_j| {
96 if assert_eq_within_tols(eigenvalue_i, eigenvalue_j).is_ok() {
97 1.0 / eigenvalue_j
98 } else {
99 (eigenvalue_i.ln() - eigenvalue_j.ln())
100 / (eigenvalue_i - eigenvalue_j)
101 }
102 })
103 .collect()
104 })
105 .collect();
106 let eigenvectors = find_orthonormal_eigenvectors(&eigenvalues, self).transpose();
107 Ok(eigenvectors.iter().map(|eigenvector_i|
108 eigenvectors.iter().map(|eigenvector_j|
109 eigenvectors.iter().map(|eigenvector_k|
110 eigenvectors.iter().map(|eigenvector_l|
111 eigenvector_i.iter().zip(eigenvector_k.iter().zip(divided_difference.iter())).map(|(eigenvector_ip, (eigenvector_kp, divided_difference_p))|
112 eigenvector_j.iter().zip(eigenvector_l.iter().zip(divided_difference_p.iter())).map(|(eigenvector_jq, (eigenvector_lq, divided_difference_pq))|
113 eigenvector_ip * eigenvector_kp * divided_difference_pq * eigenvector_jq * eigenvector_lq
114 ).sum::<TensorRank0>()
115 ).sum()
116 ).collect()
117 ).collect()
118 ).collect()
119 ).collect())
120 } else {
121 panic!("Matrix logarithm only implemented for symmetric cases")
122 }
123 }
124 pub fn invariants(&self) -> TensorRank0List<3> {
126 let trace = self.trace();
127 TensorRank0List::from([
128 trace,
129 0.5 * (trace.powi(2) - self.squared_trace()),
130 self.determinant(),
131 ])
132 }
133}
134
135fn solve_cubic_symmetric(
136 coefficients: TensorRank0List<3>,
137) -> Result<TensorRank0List<3>, TensorError> {
138 let c2 = coefficients[0];
139 let c1 = coefficients[1];
140 let c0 = coefficients[2];
141 let p = c1 - c2 * c2 / 3.0;
142 let q = -(2.0 * c2.powi(3) - 9.0 * c2 * c1 + 27.0 * c0) / 27.0;
143 if p.abs() < ABS_TOL {
144 let t = (-q).cbrt();
145 let lambda = t + c2 / 3.0;
146 return Ok(TensorRank0List::from([lambda; _]));
147 }
148 let discriminant = -4.0 * p * p * p - 27.0 * q * q;
149 let scale = (4.0 * p * p * p).abs().max(27.0 * q * q);
150 if discriminant.abs() <= 1e-13 * scale {
151 let r = (q / 2.0).cbrt();
152 let lambda_double = r + c2 / 3.0;
153 let lambda_simple = -2.0 * r + c2 / 3.0;
154 let lambdas = if lambda_double >= lambda_simple {
155 [lambda_double, lambda_double, lambda_simple]
156 } else {
157 [lambda_simple, lambda_double, lambda_double]
158 };
159 Ok(TensorRank0List::from(lambdas))
160 } else if discriminant > 0.0 {
161 let sqrt_term = (-p / 3.0).sqrt();
162 let cos_arg = 3.0 * q / (2.0 * p * (-p / 3.0).sqrt());
163 let cos_arg = cos_arg.clamp(-1.0, 1.0);
164 let theta = cos_arg.acos();
165 let mut lambdas = [
166 2.0 * sqrt_term * (theta / 3.0).cos() + c2 / 3.0,
167 2.0 * sqrt_term * ((theta + TAU) / 3.0).cos() + c2 / 3.0,
168 2.0 * sqrt_term * ((theta + 2.0 * TAU) / 3.0).cos() + c2 / 3.0,
169 ];
170 lambdas.iter_mut().for_each(|lambda| {
171 for _ in 0..2 {
172 let x = *lambda;
173 let f = x * x * x - c2 * x * x + c1 * x - c0;
174 let f_prime = 3.0 * x * x - 2.0 * c2 * x + c1;
175 if f_prime.abs() < ABS_TOL {
176 break;
177 }
178 *lambda -= f / f_prime;
179 }
180 });
181 lambdas.sort_by(|a, b| b.partial_cmp(a).unwrap());
182 Ok(TensorRank0List::from(lambdas))
183 } else {
184 Err(TensorError::SymmetricMatrixComplexEigenvalues)
185 }
186}
187
188fn find_orthonormal_eigenvectors<const I: usize>(
189 eigenvalues: &TensorRank0List<3>,
190 tensor: &TensorRank2<3, I, I>,
191) -> TensorRank2<3, I, I> {
192 if assert_eq_within_tols(&eigenvalues[0], &eigenvalues[1]).is_ok() {
193 let mut eigenvectors = TensorRank2::zero();
194 eigenvectors[2] = eigenvector_symmetric(eigenvalues[2], tensor);
195 eigenvectors[0] = orthogonal_unit_vector(&eigenvectors[2]);
196 eigenvectors[1] = eigenvectors[2].cross(&eigenvectors[0]);
197 eigenvectors
198 } else if assert_eq_within_tols(&eigenvalues[1], &eigenvalues[2]).is_ok() {
199 let mut eigenvectors = TensorRank2::zero();
200 eigenvectors[0] = eigenvector_symmetric(eigenvalues[0], tensor);
201 eigenvectors[1] = orthogonal_unit_vector(&eigenvectors[0]);
202 eigenvectors[2] = eigenvectors[0].cross(&eigenvectors[1]);
203 eigenvectors
204 } else {
205 let mut eigenvectors = eigenvalues
206 .iter()
207 .map(|&eigenvalue| eigenvector_symmetric(eigenvalue, tensor))
208 .collect::<TensorRank2<3, I, I>>();
209 eigenvectors[0].normalize();
210 let proj1 = &eigenvectors[1] * &eigenvectors[0];
211 for i in 0..3 {
212 eigenvectors[1][i] -= proj1 * eigenvectors[0][i];
213 }
214 eigenvectors[1].normalize();
215 eigenvectors[2] = eigenvectors[0].cross(&eigenvectors[1]);
216 eigenvectors
217 }
218}
219
220fn orthogonal_unit_vector<const I: usize>(vector: &TensorRank1<3, I>) -> TensorRank1<3, I> {
221 let axis = vector
222 .iter()
223 .enumerate()
224 .min_by(|(_, a), (_, b)| a.abs().partial_cmp(&b.abs()).unwrap())
225 .map(|(i, _)| i)
226 .unwrap();
227 let mut other = TensorRank1::<3, I>::zero();
228 other[axis] = 1.0;
229 vector.cross(&other).normalized()
230}
231
232fn eigenvector_symmetric<const I: usize>(
233 eigenvalue: TensorRank0,
234 tensor: &TensorRank2<3, I, I>,
235) -> TensorRank1<3, I> {
236 let m = tensor - TensorRank2::identity() * eigenvalue;
237 [m[1].cross(&m[2]), m[0].cross(&m[2]), m[0].cross(&m[1])]
238 .into_iter()
239 .max_by(|a, b| a.norm().partial_cmp(&b.norm()).unwrap())
240 .unwrap()
241 .normalized()
242}
243
244fn reconstruct_symmetric<const I: usize>(
245 eigenvalues: TensorRank0List<3>,
246 eigenvectors: TensorRank2<3, I, I>,
247) -> TensorRank2<3, I, I> {
248 let mut tensor = TensorRank2::zero();
249 eigenvalues
250 .iter()
251 .zip(eigenvectors.iter())
252 .for_each(|(eigenvalue, eigenvector)| {
253 tensor
254 .iter_mut()
255 .zip(eigenvector.iter())
256 .for_each(|(tensor_i, eigenvector_i)| {
257 tensor_i.iter_mut().zip(eigenvector.iter()).for_each(
258 |(tensor_ij, eigenvector_j)| {
259 *tensor_ij += eigenvalue * eigenvector_i * eigenvector_j
260 },
261 )
262 })
263 });
264 tensor
265}