scirs2_linalg/matrix_functions/
hyperbolic.rs

1//! Matrix hyperbolic functions
2
3use scirs2_core::ndarray::{Array2, ArrayView2};
4use scirs2_core::numeric::{Float, NumAssign, One};
5use std::iter::Sum;
6
7use crate::error::{LinalgError, LinalgResult};
8use crate::validation::validate_decomposition;
9
10/// Compute the matrix hyperbolic cosine.
11///
12/// The matrix hyperbolic cosine is computed using:
13/// cosh(A) = (exp(A) + exp(-A)) / 2
14///
15/// For efficiency, this can also be computed using the series expansion:
16/// cosh(A) = I + A²/2! + A⁴/4! + A⁶/6! + ...
17///
18/// # Arguments
19///
20/// * `a` - Input square matrix
21///
22/// # Returns
23///
24/// * Matrix hyperbolic cosine of a
25///
26/// # Examples
27///
28/// ```
29/// use scirs2_core::ndarray::array;
30/// use scirs2_linalg::matrix_functions::coshm;
31///
32/// let a = array![[0.0_f64, 1.0], [1.0, 0.0]];
33/// let cosh_a = coshm(&a.view()).unwrap();
34/// ```
35#[allow(dead_code)]
36pub fn coshm<F>(a: &ArrayView2<F>) -> LinalgResult<Array2<F>>
37where
38    F: Float + NumAssign + Sum + One + Send + Sync + scirs2_core::ndarray::ScalarOperand + 'static,
39{
40    validate_decomposition(a, "Matrix hyperbolic cosine computation", true)?;
41
42    let n = a.nrows();
43
44    // Special case for zero matrix
45    let mut is_zero = true;
46    for i in 0..n {
47        for j in 0..n {
48            if a[[i, j]].abs() > F::epsilon() {
49                is_zero = false;
50                break;
51            }
52        }
53        if !is_zero {
54            break;
55        }
56    }
57
58    if is_zero {
59        return Ok(Array2::eye(n)); // cosh(0) = I
60    }
61
62    // Special case for diagonal matrix
63    let mut is_diagonal = true;
64    for i in 0..n {
65        for j in 0..n {
66            if i != j && a[[i, j]].abs() > F::epsilon() {
67                is_diagonal = false;
68                break;
69            }
70        }
71        if !is_diagonal {
72            break;
73        }
74    }
75
76    if is_diagonal {
77        let mut result = Array2::<F>::zeros((n, n));
78        for i in 0..n {
79            result[[i, i]] = a[[i, i]].cosh();
80        }
81        return Ok(result);
82    }
83
84    // For general matrices, use series expansion
85    // cosh(A) = I + A²/2! + A⁴/4! + A⁶/6! + ...
86
87    // Compute powers of A
88    let mut a2 = Array2::<F>::zeros((n, n));
89    for i in 0..n {
90        for j in 0..n {
91            for k in 0..n {
92                a2[[i, j]] += a[[i, k]] * a[[k, j]];
93            }
94        }
95    }
96
97    let mut a4 = Array2::<F>::zeros((n, n));
98    for i in 0..n {
99        for j in 0..n {
100            for k in 0..n {
101                a4[[i, j]] += a2[[i, k]] * a2[[k, j]];
102            }
103        }
104    }
105
106    let mut a6 = Array2::<F>::zeros((n, n));
107    for i in 0..n {
108        for j in 0..n {
109            for k in 0..n {
110                a6[[i, j]] += a4[[i, k]] * a2[[k, j]];
111            }
112        }
113    }
114
115    // Compute cosh(A) = I + A²/2! + A⁴/4! + A⁶/6! + ...
116    let mut result = Array2::eye(n);
117
118    // Add A²/2!
119    let two_factorial = F::from(2.0).unwrap();
120    for i in 0..n {
121        for j in 0..n {
122            result[[i, j]] += a2[[i, j]] / two_factorial;
123        }
124    }
125
126    // Add A⁴/4!
127    let four_factorial = F::from(24.0).unwrap();
128    for i in 0..n {
129        for j in 0..n {
130            result[[i, j]] += a4[[i, j]] / four_factorial;
131        }
132    }
133
134    // Add A⁶/6!
135    let six_factorial = F::from(720.0).unwrap();
136    for i in 0..n {
137        for j in 0..n {
138            result[[i, j]] += a6[[i, j]] / six_factorial;
139        }
140    }
141
142    Ok(result)
143}
144
145/// Compute the matrix hyperbolic sine.
146///
147/// The matrix hyperbolic sine is computed using:
148/// sinh(A) = (exp(A) - exp(-A)) / 2
149///
150/// For efficiency, this can also be computed using the series expansion:
151/// sinh(A) = A + A³/3! + A⁵/5! + A⁷/7! + ...
152///
153/// # Arguments
154///
155/// * `a` - Input square matrix
156///
157/// # Returns
158///
159/// * Matrix hyperbolic sine of a
160///
161/// # Examples
162///
163/// ```
164/// use scirs2_core::ndarray::array;
165/// use scirs2_linalg::matrix_functions::sinhm;
166///
167/// let a = array![[0.0_f64, 1.0], [1.0, 0.0]];
168/// let sinh_a = sinhm(&a.view()).unwrap();
169/// ```
170#[allow(dead_code)]
171pub fn sinhm<F>(a: &ArrayView2<F>) -> LinalgResult<Array2<F>>
172where
173    F: Float + NumAssign + Sum + One + Send + Sync + scirs2_core::ndarray::ScalarOperand + 'static,
174{
175    validate_decomposition(a, "Matrix hyperbolic sine computation", true)?;
176
177    let n = a.nrows();
178
179    // Special case for zero matrix
180    let mut is_zero = true;
181    for i in 0..n {
182        for j in 0..n {
183            if a[[i, j]].abs() > F::epsilon() {
184                is_zero = false;
185                break;
186            }
187        }
188        if !is_zero {
189            break;
190        }
191    }
192
193    if is_zero {
194        return Ok(Array2::<F>::zeros((n, n))); // sinh(0) = 0
195    }
196
197    // Special case for diagonal matrix
198    let mut is_diagonal = true;
199    for i in 0..n {
200        for j in 0..n {
201            if i != j && a[[i, j]].abs() > F::epsilon() {
202                is_diagonal = false;
203                break;
204            }
205        }
206        if !is_diagonal {
207            break;
208        }
209    }
210
211    if is_diagonal {
212        let mut result = Array2::<F>::zeros((n, n));
213        for i in 0..n {
214            result[[i, i]] = a[[i, i]].sinh();
215        }
216        return Ok(result);
217    }
218
219    // For general matrices, use series expansion
220    // sinh(A) = A + A³/3! + A⁵/5! + A⁷/7! + ...
221
222    // Compute powers of A
223    let mut a2 = Array2::<F>::zeros((n, n));
224    for i in 0..n {
225        for j in 0..n {
226            for k in 0..n {
227                a2[[i, j]] += a[[i, k]] * a[[k, j]];
228            }
229        }
230    }
231
232    let mut a3 = Array2::<F>::zeros((n, n));
233    for i in 0..n {
234        for j in 0..n {
235            for k in 0..n {
236                a3[[i, j]] += a2[[i, k]] * a[[k, j]];
237            }
238        }
239    }
240
241    let mut a5 = Array2::<F>::zeros((n, n));
242    for i in 0..n {
243        for j in 0..n {
244            for k in 0..n {
245                a5[[i, j]] += a3[[i, k]] * a2[[k, j]];
246            }
247        }
248    }
249
250    let mut a7 = Array2::<F>::zeros((n, n));
251    for i in 0..n {
252        for j in 0..n {
253            for k in 0..n {
254                a7[[i, j]] += a5[[i, k]] * a2[[k, j]];
255            }
256        }
257    }
258
259    // Compute sinh(A) = A + A³/3! + A⁵/5! + A⁷/7! + ...
260    let mut result = a.to_owned();
261
262    // Add A³/3!
263    let three_factorial = F::from(6.0).unwrap();
264    for i in 0..n {
265        for j in 0..n {
266            result[[i, j]] += a3[[i, j]] / three_factorial;
267        }
268    }
269
270    // Add A⁵/5!
271    let five_factorial = F::from(120.0).unwrap();
272    for i in 0..n {
273        for j in 0..n {
274            result[[i, j]] += a5[[i, j]] / five_factorial;
275        }
276    }
277
278    // Add A⁷/7!
279    let seven_factorial = F::from(5040.0).unwrap();
280    for i in 0..n {
281        for j in 0..n {
282            result[[i, j]] += a7[[i, j]] / seven_factorial;
283        }
284    }
285
286    Ok(result)
287}
288
289/// Compute the matrix hyperbolic tangent.
290///
291/// The matrix hyperbolic tangent is computed as tanh(A) = sinh(A) * cosh(A)^{-1}
292///
293/// # Arguments
294///
295/// * `a` - Input square matrix
296///
297/// # Returns
298///
299/// * Matrix hyperbolic tangent of a
300///
301/// # Examples
302///
303/// ```
304/// use scirs2_core::ndarray::array;
305/// use scirs2_linalg::matrix_functions::tanhm;
306///
307/// let a = array![[0.1_f64, 0.0], [0.0, 0.1]];
308/// let tanh_a = tanhm(&a.view()).unwrap();
309/// ```
310#[allow(dead_code)]
311pub fn tanhm<F>(a: &ArrayView2<F>) -> LinalgResult<Array2<F>>
312where
313    F: Float + NumAssign + Sum + One + Send + Sync + scirs2_core::ndarray::ScalarOperand + 'static,
314{
315    use crate::solve::solve_multiple;
316
317    let sinh_a = sinhm(a)?;
318    let cosh_a = coshm(a)?;
319
320    // Compute tanh(A) = sinh(A) * cosh(A)^{-1}
321    // which is equivalent to solving cosh(A) * X = sinh(A)
322    solve_multiple(&cosh_a.view(), &sinh_a.view(), None)
323}