use rand::Rng;
use rv::{
misc::argmax, prelude::Categorical, prelude::CategoricalError, prelude::Rv,
};
use std::fmt::Display;
use std::fs::File;
use std::io::{self, prelude::*};
#[allow(clippy::ptr_arg)]
pub fn write_data_and_r<T: Display>(
prefix: &str,
data: &[T],
r: &Vec<Vec<f64>>,
change_points: &[usize],
) -> io::Result<()> {
let data_file_path = format!("{}_data.txt", prefix);
let mut data_f = File::create(data_file_path)?;
data.iter().try_for_each(|d| writeln!(data_f, "{}", d))?;
let r_file_path = format!("{}_r.txt", prefix);
let mut r_f = File::create(r_file_path)?;
let t = r.len();
r.iter()
.enumerate()
.try_for_each::<_, io::Result<()>>(|(i, rs)| {
for r in rs {
write!(r_f, "{},", r)?;
if r.is_nan() || r.is_infinite() {
eprintln!("NaN/Infinite value in output: Row {}", i);
}
}
for _ in rs.len()..t {
write!(r_f, ",")?;
}
writeln!(r_f)?;
Ok(())
})?;
let change_points_path = format!("{}_change_points.txt", prefix);
let mut cp_f = File::create(change_points_path)?;
change_points
.iter()
.try_for_each(|cp| writeln!(cp_f, "{}", cp))?;
Ok(())
}
#[allow(clippy::ptr_arg)]
#[must_use]
pub fn window_over_threshold(
r: &Vec<Vec<f64>>,
window: usize,
threshold: f64,
) -> Vec<usize> {
let mut result = Vec::new();
for (i, rs) in r.iter().enumerate() {
let window_sum: f64 = rs.iter().skip(1).take(window).sum();
if window_sum >= threshold {
result.push(i);
}
}
result
}
pub fn infer_changepoints<R: Rng>(
rs: &[Vec<f64>],
sample_size: usize,
rng: &mut R,
) -> Result<Vec<f64>, CategoricalError> {
let n = rs.len();
let dists: Vec<Categorical> =
rs.iter()
.map(|r| Categorical::new(r))
.collect::<Result<Vec<Categorical>, CategoricalError>>()?;
let counts: Vec<usize> =
(0..sample_size).fold(vec![0_usize; n], |mut acc, _| {
let mut s: usize = n - 1;
while s != 0 {
let cur_run_length: usize = dists[s].draw(rng);
s = s.saturating_sub(cur_run_length);
acc[s] += 1;
}
acc
});
Ok(counts
.into_iter()
.map(|c| (c as f64) / (sample_size as f64))
.collect())
}
pub fn infer_pseudo_cmf_changepoints<R: Rng>(
rs: &[Vec<f64>],
sample_size: usize,
rng: &mut R,
) -> Result<Vec<f64>, CategoricalError> {
let ps = infer_changepoints(rs, sample_size, rng)?;
Ok(ps
.into_iter()
.scan(0.0, |acc, x| {
*acc = (*acc + x).rem_euclid(1.0);
Some(*acc)
})
.collect())
}
#[must_use]
pub fn map_changepoints(r: &[Vec<f64>]) -> Vec<usize> {
let mut s = r.len() - 1;
let mut change_points = vec![];
while s != 0 {
s = s.saturating_sub(
*argmax(&r[s]).first().expect("r should not be empty") + 1,
);
change_points.push(s);
}
change_points.reverse();
change_points
}
fn diff<T>(a: T, b: T) -> T
where
T: PartialOrd + std::ops::Sub<Output = T>,
{
if a > b {
a - b
} else {
b - a
}
}
pub fn max_error<T>(predicted: &[T], expected: &[T]) -> T
where
T: PartialOrd + std::ops::Sub<Output = T> + Clone,
{
debug_assert_eq!(
predicted.len(),
expected.len(),
"predicted and expected must be the same size."
);
assert!(!predicted.is_empty(), "Sequences cannot be empty");
predicted.iter().skip(1).zip(expected.iter().skip(1)).fold(
diff(
predicted.first().unwrap().clone(),
expected.first().unwrap().clone(),
),
|acc, (a, b)| {
let d = diff(a.clone(), b.clone());
match acc.partial_cmp(&d).unwrap() {
std::cmp::Ordering::Less | std::cmp::Ordering::Equal => acc,
std::cmp::Ordering::Greater => d,
}
},
)
}