use crate::algorithms::subspace::{discretize, normal_quantile_bins, z_normalize};
pub fn mdl(
ts: &[&[f64]],
m: usize,
subseq_idx: &[usize],
nn_idx: &[usize],
) -> (Vec<f64>, Vec<Vec<usize>>) {
let d = ts.len();
assert!(d >= 1, "Need at least one dimension");
assert_eq!(subseq_idx.len(), d, "subseq_idx must have one entry per k");
assert_eq!(nn_idx.len(), d, "nn_idx must have one entry per k");
let n_bit: usize = 8;
let bins = normal_quantile_bins(n_bit);
let mut bit_sizes = vec![0.0f64; d];
let mut subspaces = vec![Vec::new(); d];
for k in 0..d {
let mut disc_subseqs: Vec<Vec<usize>> = Vec::with_capacity(d);
let mut disc_neighbors: Vec<Vec<usize>> = Vec::with_capacity(d);
for ts_dim in ts {
let sub = z_normalize(&ts_dim[subseq_idx[k]..subseq_idx[k] + m]);
let nei = z_normalize(&ts_dim[nn_idx[k]..nn_idx[k] + m]);
disc_subseqs.push(discretize(&sub, &bins));
disc_neighbors.push(discretize(&nei, &bins));
}
let d_norms: Vec<f64> = (0..d)
.map(|dim| {
disc_subseqs[dim]
.iter()
.zip(&disc_neighbors[dim])
.map(|(&a, &b)| {
let diff = a as f64 - b as f64;
diff * diff
})
.sum::<f64>()
.sqrt()
})
.collect();
let mut indices: Vec<usize> = (0..d).collect();
indices.sort_by(|&a, &b| d_norms[a].partial_cmp(&d_norms[b]).unwrap());
let s: Vec<usize> = indices[..k + 1].to_vec();
let sub_dims = s.len();
let mut residuals: Vec<i32> = Vec::with_capacity(sub_dims * m);
for &dim in &s {
for j in 0..m {
residuals.push(disc_subseqs[dim][j] as i32 - disc_neighbors[dim][j] as i32);
}
}
residuals.sort();
residuals.dedup();
let n_val = residuals.len();
let bit_size = n_bit as f64 * (2 * d * m - sub_dims * m) as f64
+ sub_dims as f64 * m as f64 * (n_val as f64).log2()
+ n_val as f64 * n_bit as f64;
bit_sizes[k] = bit_size;
subspaces[k] = s;
}
(bit_sizes, subspaces)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mdl_basic() {
let n = 100;
let m = 10;
let ts0: Vec<f64> = (0..n).map(|i| (i as f64 * 0.2).sin()).collect();
let ts1: Vec<f64> = (0..n).map(|i| (i as f64 * 0.3).cos()).collect();
let ts2: Vec<f64> = (0..n).map(|i| (i as f64 * 0.15).sin() + 0.5).collect();
let ts_refs: [&[f64]; 3] = [&ts0, &ts1, &ts2];
let subseq_idx = [5, 5, 5];
let nn_idx = [50, 50, 50];
let (bit_sizes, subspaces) = mdl(&ts_refs, m, &subseq_idx, &nn_idx);
assert_eq!(bit_sizes.len(), 3);
assert_eq!(subspaces.len(), 3);
for (k, &bs) in bit_sizes.iter().enumerate() {
assert!(bs > 0.0, "bit_sizes[{k}] = {bs} is not positive");
}
for (k, s) in subspaces.iter().enumerate() {
assert_eq!(s.len(), k + 1, "subspace[{k}] should have {}", k + 1);
}
}
#[test]
fn test_mdl_identical_pair() {
let n = 50;
let m = 5;
let period = 10;
let ts0: Vec<f64> = (0..n)
.map(|i| (i as f64 * 2.0 * std::f64::consts::PI / period as f64).sin())
.collect();
let ts1: Vec<f64> = (0..n)
.map(|i| (i as f64 * 2.0 * std::f64::consts::PI / period as f64).cos())
.collect();
let ts_refs: [&[f64]; 2] = [&ts0, &ts1];
let subseq_idx = [0, 0];
let nn_idx = [period, period];
let (bit_sizes, _) = mdl(&ts_refs, m, &subseq_idx, &nn_idx);
for (k, &bs) in bit_sizes.iter().enumerate() {
assert!(bs > 0.0, "bit_sizes[{k}] = {bs} is not positive");
}
}
}