1use crate::{Error, Result};
5
6#[derive(Debug, Clone)]
8pub struct SquareMatrix {
9 pub n: usize,
10 pub data: Vec<f64>,
11}
12
13impl SquareMatrix {
14 pub fn zeros(n: usize) -> Self {
15 Self {
16 n,
17 data: vec![0.0; n * n],
18 }
19 }
20 #[inline]
21 pub fn get(&self, i: usize, j: usize) -> f64 {
22 self.data[i * self.n + j]
23 }
24 #[inline]
25 pub fn set(&mut self, i: usize, j: usize, v: f64) {
26 self.data[i * self.n + j] = v;
27 }
28
29 pub fn cholesky(&self) -> Result<SquareMatrix> {
31 let n = self.n;
32 let mut l = SquareMatrix::zeros(n);
33 for i in 0..n {
34 for j in 0..=i {
35 let mut s = self.get(i, j);
36 for k in 0..j {
37 s -= l.get(i, k) * l.get(j, k);
38 }
39 if i == j {
40 if s <= 0.0 || !s.is_finite() {
41 return Err(Error::Singular);
42 }
43 l.set(i, j, s.sqrt());
44 } else {
45 l.set(i, j, s / l.get(j, j));
46 }
47 }
48 }
49 Ok(l)
50 }
51
52 pub fn cholesky_solve(l: &SquareMatrix, b: &[f64]) -> Vec<f64> {
54 Self::cholesky_solve_leading(l, l.n, b)
55 }
56
57 #[allow(clippy::needless_range_loop)] pub fn cholesky_solve_leading(l: &SquareMatrix, k: usize, b: &[f64]) -> Vec<f64> {
63 debug_assert!(k <= l.n && b.len() >= k);
64 let mut y = vec![0.0; k];
65 for i in 0..k {
66 let mut s = b[i];
67 for j in 0..i {
68 s -= l.get(i, j) * y[j];
69 }
70 y[i] = s / l.get(i, i);
71 }
72 let mut x = vec![0.0; k];
73 for i in (0..k).rev() {
74 let mut s = y[i];
75 for j in i + 1..k {
76 s -= l.get(j, i) * x[j];
77 }
78 x[i] = s / l.get(i, i);
79 }
80 x
81 }
82
83 pub fn cholesky_inverse_diag(l: &SquareMatrix) -> Vec<f64> {
86 let n = l.n;
87 let mut diag = vec![0.0; n];
88 let mut e = vec![0.0; n];
89 for j in 0..n {
90 e.iter_mut().for_each(|v| *v = 0.0);
91 e[j] = 1.0;
92 let col = SquareMatrix::cholesky_solve(l, &e);
93 diag[j] = col[j];
94 }
95 diag
96 }
97}
98
99pub fn singular_values(a: &[f64], rows: usize, cols: usize) -> Vec<f64> {
105 debug_assert_eq!(a.len(), rows * cols);
106 let mut u: Vec<f64> = Vec::with_capacity(rows * cols);
108 for j in 0..cols {
109 for i in 0..rows {
110 u.push(a[i * cols + j]);
111 }
112 }
113 let tol = 1e-14;
114 for _sweep in 0..60 {
115 let mut rotated = false;
116 for i in 0..cols {
117 for j in i + 1..cols {
118 let (mut aii, mut ajj, mut aij) = (0.0f64, 0.0f64, 0.0f64);
119 for t in 0..rows {
120 let ui = u[i * rows + t];
121 let uj = u[j * rows + t];
122 aii += ui * ui;
123 ajj += uj * uj;
124 aij += ui * uj;
125 }
126 if aij.abs() <= tol * (aii * ajj).sqrt() || aii == 0.0 || ajj == 0.0 {
127 continue;
128 }
129 rotated = true;
130 let zeta = (ajj - aii) / (2.0 * aij);
132 let t_ = zeta.signum() / (zeta.abs() + (1.0 + zeta * zeta).sqrt());
133 let c = 1.0 / (1.0 + t_ * t_).sqrt();
134 let s = c * t_;
135 for t in 0..rows {
136 let ii = i * rows + t;
137 let jj = j * rows + t;
138 let (ui, uj) = (u[ii], u[jj]);
139 u[ii] = c * ui - s * uj;
140 u[jj] = s * ui + c * uj;
141 }
142 }
143 }
144 if !rotated {
145 break;
146 }
147 }
148 let mut sv: Vec<f64> = (0..cols)
149 .map(|j| {
150 (0..rows)
151 .map(|t| u[j * rows + t].powi(2))
152 .sum::<f64>()
153 .sqrt()
154 })
155 .collect();
156 sv.sort_unstable_by(|a, b| b.total_cmp(a));
157 sv
158}
159
160pub fn ols(x: &[f64], rows: usize, cols: usize, y: &[f64]) -> Result<(Vec<f64>, f64, Vec<f64>)> {
164 if rows <= cols {
165 return Err(Error::TooShort {
166 n: rows,
167 needed: cols + 1,
168 });
169 }
170 let mut xtx = SquareMatrix::zeros(cols);
171 let mut xty = vec![0.0; cols];
172 for t in 0..rows {
173 let row = &x[t * cols..(t + 1) * cols];
174 for i in 0..cols {
175 xty[i] += row[i] * y[t];
176 for j in 0..=i {
177 let v = xtx.get(i, j) + row[i] * row[j];
178 xtx.set(i, j, v);
179 xtx.set(j, i, v);
180 }
181 }
182 }
183 let l = xtx.cholesky()?;
184 let beta = SquareMatrix::cholesky_solve(&l, &xty);
185 let mut rss = 0.0;
186 for t in 0..rows {
187 let row = &x[t * cols..(t + 1) * cols];
188 let fit: f64 = row.iter().zip(&beta).map(|(a, b)| a * b).sum();
189 rss += (y[t] - fit).powi(2);
190 }
191 let s2 = rss / (rows - cols) as f64;
192 let dinv = SquareMatrix::cholesky_inverse_diag(&l);
193 let se = dinv.iter().map(|d| (s2 * d).sqrt()).collect();
194 Ok((beta, s2, se))
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[test]
202 fn singular_values_known_cases() {
203 let d = [3.0, 0.0, 0.0, 2.0];
205 let sv = singular_values(&d, 2, 2);
206 assert!((sv[0] - 3.0).abs() < 1e-12 && (sv[1] - 2.0).abs() < 1e-12);
207 let a = [1.0, 1.0, 0.0, 1.0];
209 let sv = singular_values(&a, 2, 2);
210 let s1 = ((3.0 + 5.0f64.sqrt()) / 2.0).sqrt();
211 let s2 = ((3.0 - 5.0f64.sqrt()) / 2.0).sqrt();
212 assert!((sv[0] - s1).abs() < 1e-12, "{sv:?}");
213 assert!((sv[1] - s2).abs() < 1e-12);
214 let b = [1.0, 2.0, -1.0, 0.5, 3.0, 1.0];
216 let sv = singular_values(&b, 3, 2);
217 let fro: f64 = b.iter().map(|v| v * v).sum();
218 assert!((sv.iter().map(|v| v * v).sum::<f64>() - fro).abs() < 1e-10);
219 let (g00, g01, g11) = (1.0f64 + 1.0 + 9.0, 2.0 - 0.5 + 3.0, 4.0f64 + 0.25 + 1.0);
221 let det = g00 * g11 - g01 * g01;
222 assert!(((sv[0] * sv[1]).powi(2) - det).abs() < 1e-9);
223 }
224
225 #[test]
226 fn cholesky_solve_2x2() {
227 let mut a = SquareMatrix::zeros(2);
228 a.set(0, 0, 4.0);
229 a.set(0, 1, 2.0);
230 a.set(1, 0, 2.0);
231 a.set(1, 1, 3.0);
232 let l = a.cholesky().unwrap();
233 let x = SquareMatrix::cholesky_solve(&l, &[2.0, 1.0]);
234 assert!((x[0] - 0.5).abs() < 1e-12 && x[1].abs() < 1e-12);
236 let d = SquareMatrix::cholesky_inverse_diag(&l);
237 assert!((d[0] - 3.0 / 8.0).abs() < 1e-12 && (d[1] - 0.5).abs() < 1e-12);
239 }
240}