use crate::error::Error;
#[derive(Debug, Clone)]
pub struct Estimate {
pub total: Vec<f64>,
pub random_draws: usize,
pub sd_variance: Vec<f64>,
}
pub fn multiplicity_estimate(
y: &[f64],
n_rows: usize,
units: &[usize],
positions: &[usize],
expected_hits: &[f64],
hits: &[u64],
) -> Result<Estimate, Error> {
if n_rows == 0 || y.len() % n_rows != 0 {
return Err(Error::Input(format!(
"a matrix of {} values cannot have {} rows",
y.len(),
n_rows
)));
}
let n_cols = y.len() / n_rows;
let m = units.len();
if positions.len() != m || expected_hits.len() != m || hits.len() != m {
return Err(Error::Input(
"units, positions, expected hits and hits must have one entry per \
sampled unit"
.into(),
));
}
for &v in y {
if !v.is_finite() {
return Err(Error::Input(format!(
"study variables must be finite; found {v}"
)));
}
}
for &u in units {
if u >= n_rows {
return Err(Error::Input(format!(
"sampled unit {u} is beyond the {n_rows} rows of the study matrix"
)));
}
}
for (s, &e) in expected_hits.iter().enumerate() {
if !e.is_finite() || e <= 0.0 {
return Err(Error::Input(format!(
"expected hits must be positive on the sample; entry {s} is {e}"
)));
}
}
let mut by_list: Vec<usize> = (0..m).collect();
by_list.sort_by_key(|&s| positions[s]);
let mut total = vec![0.0_f64; n_cols];
for c in 0..n_cols {
for &s in &by_list {
total[c] += y[c * n_rows + units[s]] / expected_hits[s] * hits[s] as f64;
}
}
let random: Vec<usize> = by_list
.iter()
.copied()
.filter(|&s| hits[s] as i64 - expected_hits[s].floor() as i64 == 1)
.collect();
let draws = random.len();
let sd_variance: Vec<f64> = (0..n_cols)
.map(|c| {
if draws == 0 {
0.0
} else if draws == 1 {
f64::NAN
} else {
let mut ss = 0.0_f64;
for w in random.windows(2) {
let a = y[c * n_rows + units[w[0]]] / expected_hits[w[0]];
let b = y[c * n_rows + units[w[1]]] / expected_hits[w[1]];
ss += (b - a) * (b - a);
}
draws as f64 / (2.0 * (draws as f64 - 1.0)) * ss
}
})
.collect();
Ok(Estimate {
total,
random_draws: draws,
sd_variance,
})
}