use ndarray::Array1;
pub fn stable_argsort(window: &[f64]) -> Vec<usize> {
let mut idx: Vec<usize> = (0..window.len()).collect();
idx.sort_by(|&i, &j| {
match window[i].partial_cmp(&window[j]) {
Some(core::cmp::Ordering::Less) => core::cmp::Ordering::Less,
Some(core::cmp::Ordering::Greater) => core::cmp::Ordering::Greater,
Some(core::cmp::Ordering::Equal) | None => i.cmp(&j),
}
});
idx
}
pub fn lehmer_code(perm: &[usize]) -> u64 {
let n = perm.len();
if n > 20 {
panic!("For embedding dimensions larger than 20, the integer will be too large for u64.");
}
let mut fact: Vec<u128> = vec![1u128; n];
for i in 1..n {
fact[i] = fact[i - 1] * (i as u128);
}
let mut acc: u128 = 0;
for i in 0..n {
let mut c = 0u128;
for j in (i + 1)..n {
if perm[i] > perm[j] { c += 1; }
}
let weight = fact[n - 1 - i];
acc += c * weight;
}
acc as u64
}
pub fn symbolize_series(series: &Array1<f64>, order: usize, delay: usize, stable: bool) -> Array1<i32> {
if order < 1 { panic!("The embedding order must be a positive integer."); }
if delay < 1 { panic!("The delay must be a positive integer."); }
if order > 12 { panic!("Temporary limitation: order must be ≤ 12 to fit pattern IDs into i32."); }
let n = series.len();
if n == 0 { return Array1::<i32>::zeros(0); }
let span = (order - 1) * delay;
if n <= span { return Array1::<i32>::zeros(0); }
let n_windows = n - span;
let mut out: Vec<i32> = Vec::with_capacity(n_windows);
for t in 0..n_windows {
let mut w: Vec<f64> = Vec::with_capacity(order);
for j in 0..order { w.push(series[t + j * delay]); }
let perm = if stable { stable_argsort(&w) } else {
stable_argsort(&w)
};
let code_u64 = lehmer_code(&perm);
if code_u64 > i32::MAX as u64 {
panic!("Pattern code exceeds i32 range. Reduce order or generalize key type.");
}
out.push(code_u64 as i32);
}
Array1::from(out)
}