use crate::error::Error;
use crate::pmr::{pmr, Selection};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Head {
All,
Residual,
}
#[derive(Debug, Clone)]
pub struct Selected {
pub head: usize,
pub list: Vec<usize>,
pub hits: Vec<u64>,
pub effective_lambda: Vec<f64>,
pub accounting: Option<Selection>,
}
fn check_order(order: &[usize], n_units: usize) -> Result<(), Error> {
if order.len() != n_units {
return Err(Error::Input(format!(
"{} expected hits but an order of {} positions",
n_units,
order.len()
)));
}
let mut seen = vec![false; n_units];
for &u in order {
if u >= n_units || seen[u] {
return Err(Error::Input(
"the order must be a permutation of the frame".into(),
));
}
seen[u] = true;
}
Ok(())
}
pub fn eligible_positions(lambda: &[f64], order: &[usize], head: Head) -> Vec<usize> {
match head {
Head::All => (0..order.len()).collect(),
Head::Residual => (0..order.len())
.filter(|&p| lambda[order[p]] != lambda[order[p]].floor())
.collect(),
}
}
pub fn required_uniforms(lambda: &[f64], order: &[usize], head: Head) -> usize {
match head {
Head::All => order.len(),
Head::Residual => eligible_positions(lambda, order, head).len(),
}
}
pub fn head_position(weights: &[f64], u: f64) -> Result<usize, Error> {
if !(0.0..1.0).contains(&u) {
return Err(Error::Input(format!(
"the head uniform must lie in [0, 1); it is {u}"
)));
}
let mut total = 0.0_f64;
for &w in weights {
if !w.is_finite() || w < 0.0 {
return Err(Error::Input(format!(
"head weights must be finite and non-negative; found {w}"
)));
}
total += w;
}
if total <= 0.0 {
return Err(Error::Input("the head weights sum to zero".into()));
}
let target = u * total;
let mut cum = 0.0_f64;
for (p, &w) in weights.iter().enumerate() {
cum += w;
if cum > target {
return Ok(p);
}
}
Ok(weights.len() - 1)
}
pub fn select_from(
lambda: &[f64],
order: &[usize],
start: usize,
uniforms: &[f64],
head: Head,
) -> Result<Selected, Error> {
let n_units = lambda.len();
check_order(order, n_units)?;
let eligible = eligible_positions(lambda, order, head);
if eligible.is_empty() {
let hits = order
.iter()
.map(|&u| {
if lambda[u] < 0.0 || !lambda[u].is_finite() {
Err(Error::Input(format!(
"expected hits must be finite and non-negative; unit {u} \
has {}",
lambda[u]
)))
} else {
Ok(lambda[u] as u64)
}
})
.collect::<Result<Vec<_>, _>>()?;
return Ok(Selected {
head: 0,
list: order.to_vec(),
hits,
effective_lambda: lambda.to_vec(),
accounting: None,
});
}
if !eligible.contains(&start) {
return Err(Error::Input(format!(
"the head position {start} is not eligible"
)));
}
let positions: Vec<usize> = (start..n_units).chain(0..start).collect();
let list: Vec<usize> = positions.iter().map(|&p| order[p]).collect();
let mut effective_lambda = lambda.to_vec();
let (hits, accounting) = match head {
Head::All => {
let listed: Vec<f64> = list.iter().map(|&u| lambda[u]).collect();
let selection = pmr(&listed, uniforms, &positions)?;
for (i, &u) in list.iter().enumerate() {
effective_lambda[u] = selection.effective_lambda[i];
}
(selection.hits.clone(), selection)
}
Head::Residual => {
let fractional: Vec<usize> = (0..n_units)
.filter(|&i| lambda[list[i]] != lambda[list[i]].floor())
.collect();
let listed: Vec<f64> = fractional.iter().map(|&i| lambda[list[i]]).collect();
let priority: Vec<usize> = fractional.iter().map(|&i| positions[i]).collect();
let selection = pmr(&listed, uniforms, &priority)?;
let mut hits = vec![0_u64; n_units];
for i in 0..n_units {
if lambda[list[i]] == lambda[list[i]].floor() {
hits[i] = lambda[list[i]] as u64;
}
}
for (s, &i) in fractional.iter().enumerate() {
hits[i] = selection.hits[s];
effective_lambda[list[i]] = selection.effective_lambda[s];
}
(hits, selection)
}
};
Ok(Selected {
head: start,
list,
hits,
effective_lambda,
accounting: Some(accounting),
})
}
pub fn select(
lambda: &[f64],
order: &[usize],
head: Head,
u_head: f64,
uniforms: &[f64],
) -> Result<Selected, Error> {
check_order(order, lambda.len())?;
let eligible = eligible_positions(lambda, order, head);
if eligible.is_empty() {
return select_from(lambda, order, 0, uniforms, head);
}
let weights: Vec<f64> = eligible.iter().map(|&p| lambda[order[p]]).collect();
let start = eligible[head_position(&weights, u_head)?];
select_from(lambda, order, start, uniforms, head)
}