1use crate::acov::autocovariance;
14use crate::criteria::{ic_order, Criterion};
15use crate::linalg::{singular_values, SquareMatrix};
16use crate::{check_input, Error, Result};
17
18#[derive(Debug, Clone)]
20pub struct Cancorr {
21 pub correlations: Vec<f64>,
23 pub f: usize,
24 pub p: usize,
25}
26
27pub fn canonical_correlations(x: &[f64], f: usize, p: usize) -> Result<Cancorr> {
30 if f < 1 || p < 1 {
31 return Err(Error::Invalid("window sizes f and p must be >= 1"));
32 }
33 let r = autocovariance(x, f + p - 1)?;
34 canonical_correlations_from_acov(&r, f, p)
35}
36
37pub fn canonical_correlations_from_acov(r: &[f64], f: usize, p: usize) -> Result<Cancorr> {
40 if r.len() < f + p {
41 return Err(Error::TooShort {
42 n: r.len(),
43 needed: f + p,
44 });
45 }
46 let toeplitz = |m: usize| -> SquareMatrix {
47 let mut g = SquareMatrix::zeros(m);
48 for i in 0..m {
49 for j in 0..m {
50 g.set(i, j, r[i.abs_diff(j)]);
51 }
52 }
53 g
54 };
55 let lf = toeplitz(f).cholesky()?;
56 let lp = toeplitz(p).cholesky()?;
57 let mut gfp = vec![0.0; f * p];
59 for i in 0..f {
60 for j in 0..p {
61 gfp[i * p + j] = r[i + j + 1];
62 }
63 }
64 let mut xm = vec![0.0; f * p];
66 for j in 0..p {
67 for i in 0..f {
68 let mut s = gfp[i * p + j];
69 for k in 0..i {
70 s -= lf.get(i, k) * xm[k * p + j];
71 }
72 xm[i * p + j] = s / lf.get(i, i);
73 }
74 }
75 let mut m = vec![0.0; f * p];
77 for i in 0..f {
78 for j in 0..p {
79 let mut s = xm[i * p + j];
80 for k in 0..j {
81 s -= lp.get(j, k) * m[i * p + k];
82 }
83 m[i * p + j] = s / lp.get(j, j);
84 }
85 }
86 let mut correlations = singular_values(&m, f, p);
87 correlations.truncate(f.min(p));
88 for c in correlations.iter_mut() {
90 if *c > 1.0 && *c < 1.0 + 1e-10 {
91 *c = 1.0;
92 }
93 }
94 Ok(Cancorr { correlations, f, p })
95}
96
97pub fn canonical_correlations_stacked(x: &[f64], f: usize, p: usize) -> Result<Cancorr> {
106 if f < 1 || p < 1 {
107 return Err(Error::Invalid("window sizes f and p must be >= 1"));
108 }
109 check_input(x, 2 * (f + p))?;
110 let t_len = x.len();
111 if t_len < f + p + f + p {
112 return Err(Error::TooShort {
113 n: t_len,
114 needed: 2 * (f + p),
115 });
116 }
117 let mean = x.iter().sum::<f64>() / t_len as f64;
118 let xc: Vec<f64> = x.iter().map(|v| v - mean).collect();
119 let mut gff = SquareMatrix::zeros(f);
120 let mut gpp = SquareMatrix::zeros(p);
121 let mut gfp = vec![0.0; f * p];
122 for t in p..t_len - f {
124 for i in 0..f {
125 let yi = xc[t + i];
126 for j in 0..=i {
127 let v = gff.get(i, j) + yi * xc[t + j];
128 gff.set(i, j, v);
129 gff.set(j, i, v);
130 }
131 for j in 0..p {
132 gfp[i * p + j] += yi * xc[t - 1 - j];
133 }
134 }
135 for i in 0..p {
136 let zi = xc[t - 1 - i];
137 for j in 0..=i {
138 let v = gpp.get(i, j) + zi * xc[t - 1 - j];
139 gpp.set(i, j, v);
140 gpp.set(j, i, v);
141 }
142 }
143 }
144 whitened_correlations(gff, gpp, gfp, f, p)
145}
146
147fn whitened_correlations(
150 gff: SquareMatrix,
151 gpp: SquareMatrix,
152 gfp: Vec<f64>,
153 f: usize,
154 p: usize,
155) -> Result<Cancorr> {
156 let lf = gff.cholesky()?;
157 let lp = gpp.cholesky()?;
158 let mut xm = vec![0.0; f * p];
159 for j in 0..p {
160 for i in 0..f {
161 let mut s = gfp[i * p + j];
162 for k in 0..i {
163 s -= lf.get(i, k) * xm[k * p + j];
164 }
165 xm[i * p + j] = s / lf.get(i, i);
166 }
167 }
168 let mut m = vec![0.0; f * p];
169 for i in 0..f {
170 for j in 0..p {
171 let mut s = xm[i * p + j];
172 for k in 0..j {
173 s -= lp.get(j, k) * m[i * p + k];
174 }
175 m[i * p + j] = s / lp.get(j, j);
176 }
177 }
178 let mut correlations = singular_values(&m, f, p);
179 correlations.truncate(f.min(p));
180 for c in correlations.iter_mut() {
181 if *c > 1.0 && *c < 1.0 + 1e-10 {
182 *c = 1.0;
183 }
184 }
185 Ok(Cancorr { correlations, f, p })
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq)]
190pub enum SvcPenalty {
191 LogT,
195 FpLogT,
198}
199
200#[derive(Debug, Clone, Copy)]
202pub struct SvcConfig {
203 pub window: Option<usize>,
206 pub window_factor: f64,
209 pub penalty: SvcPenalty,
210}
211
212impl Default for SvcConfig {
213 fn default() -> Self {
214 SvcConfig {
215 window: None,
216 window_factor: 1.0,
217 penalty: SvcPenalty::LogT,
218 }
219 }
220}
221
222#[derive(Debug, Clone)]
224pub struct SvcResult {
225 pub order: usize,
227 pub correlations: Vec<f64>,
229 pub values: Vec<f64>,
231 pub f: usize,
232 pub p: usize,
233}
234
235pub fn svc_order(x: &[f64], config: SvcConfig) -> Result<SvcResult> {
241 let t_len = x.len();
242 let w = match config.window {
243 Some(w) => w.max(1),
244 None => {
245 let aic = ic_order(x, Criterion::Aic, None)?;
246 ((config.window_factor * aic.order as f64).floor() as usize).max(2)
247 }
248 };
249 let cc = canonical_correlations_stacked(x, w, w)?;
250 let m = w;
251 let t = t_len as f64;
252 let c_t = match config.penalty {
253 SvcPenalty::LogT => t.ln(),
254 SvcPenalty::FpLogT => (w * w) as f64 * t.ln(),
255 };
256 let values: Vec<f64> = (0..=m)
257 .map(|k| {
258 let sig2 = if k < m {
259 cc.correlations[k].powi(2)
260 } else {
261 0.0
262 };
263 sig2 + c_t * (2 * k) as f64 / t
264 })
265 .collect();
266 let order = crate::criteria::argmin(&values);
267 Ok(SvcResult {
268 order,
269 correlations: cc.correlations,
270 values,
271 f: w,
272 p: w,
273 })
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279
280 #[test]
281 fn ar1_has_exactly_one_canonical_correlation_equal_to_phi() {
282 let phi: f64 = 0.8;
286 let r: Vec<f64> = (0..12).map(|k| phi.powi(k) / (1.0 - phi * phi)).collect();
287 let cc = canonical_correlations_from_acov(&r, 6, 6).unwrap();
288 assert!(
289 (cc.correlations[0] - phi).abs() < 1e-10,
290 "{:?}",
291 cc.correlations
292 );
293 for c in &cc.correlations[1..] {
294 assert!(c.abs() < 1e-8, "{:?}", cc.correlations);
295 }
296 }
297
298 #[test]
299 fn ma1_has_rank_one_past_future_covariance() {
300 let theta = 0.6f64;
302 let mut r = vec![0.0; 12];
303 r[0] = 1.0 + theta * theta;
304 r[1] = theta;
305 let cc = canonical_correlations_from_acov(&r, 6, 6).unwrap();
306 assert!(cc.correlations[0] > 0.3);
307 for c in &cc.correlations[1..] {
308 assert!(c.abs() < 1e-10, "{:?}", cc.correlations);
309 }
310 }
311
312 #[test]
313 fn white_noise_correlations_are_zero() {
314 let mut r = vec![0.0; 10];
315 r[0] = 2.5;
316 let cc = canonical_correlations_from_acov(&r, 5, 5).unwrap();
317 for c in &cc.correlations {
318 assert!(c.abs() < 1e-12);
319 }
320 }
321}