1use crate::error::Error;
17use crate::pmr::{pmr, Selection};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum Head {
22 All,
24 Residual,
27}
28
29#[derive(Debug, Clone)]
31pub struct Selected {
32 pub head: usize,
34 pub list: Vec<usize>,
36 pub hits: Vec<u64>,
38 pub effective_lambda: Vec<f64>,
41 pub accounting: Option<Selection>,
44}
45
46fn check_order(order: &[usize], n_units: usize) -> Result<(), Error> {
47 if order.len() != n_units {
48 return Err(Error::Input(format!(
49 "{} expected hits but an order of {} positions",
50 n_units,
51 order.len()
52 )));
53 }
54 let mut seen = vec![false; n_units];
55 for &u in order {
56 if u >= n_units || seen[u] {
57 return Err(Error::Input(
58 "the order must be a permutation of the frame".into(),
59 ));
60 }
61 seen[u] = true;
62 }
63 Ok(())
64}
65
66pub fn eligible_positions(lambda: &[f64], order: &[usize], head: Head) -> Vec<usize> {
68 match head {
69 Head::All => (0..order.len()).collect(),
70 Head::Residual => (0..order.len())
71 .filter(|&p| lambda[order[p]] != lambda[order[p]].floor())
72 .collect(),
73 }
74}
75
76pub fn required_uniforms(lambda: &[f64], order: &[usize], head: Head) -> usize {
78 match head {
79 Head::All => order.len(),
80 Head::Residual => eligible_positions(lambda, order, head).len(),
81 }
82}
83
84pub fn head_position(weights: &[f64], u: f64) -> Result<usize, Error> {
87 if !(0.0..1.0).contains(&u) {
88 return Err(Error::Input(format!(
89 "the head uniform must lie in [0, 1); it is {u}"
90 )));
91 }
92 let mut total = 0.0_f64;
93 for &w in weights {
94 if !w.is_finite() || w < 0.0 {
95 return Err(Error::Input(format!(
96 "head weights must be finite and non-negative; found {w}"
97 )));
98 }
99 total += w;
100 }
101 if total <= 0.0 {
102 return Err(Error::Input("the head weights sum to zero".into()));
103 }
104 let target = u * total;
105 let mut cum = 0.0_f64;
106 for (p, &w) in weights.iter().enumerate() {
107 cum += w;
108 if cum > target {
109 return Ok(p);
110 }
111 }
112 Ok(weights.len() - 1)
113}
114
115pub fn select_from(
122 lambda: &[f64],
123 order: &[usize],
124 start: usize,
125 uniforms: &[f64],
126 head: Head,
127) -> Result<Selected, Error> {
128 let n_units = lambda.len();
129 check_order(order, n_units)?;
130
131 let eligible = eligible_positions(lambda, order, head);
132 if eligible.is_empty() {
133 let hits = order
136 .iter()
137 .map(|&u| {
138 if lambda[u] < 0.0 || !lambda[u].is_finite() {
139 Err(Error::Input(format!(
140 "expected hits must be finite and non-negative; unit {u} \
141 has {}",
142 lambda[u]
143 )))
144 } else {
145 Ok(lambda[u] as u64)
146 }
147 })
148 .collect::<Result<Vec<_>, _>>()?;
149 return Ok(Selected {
150 head: 0,
151 list: order.to_vec(),
152 hits,
153 effective_lambda: lambda.to_vec(),
154 accounting: None,
155 });
156 }
157 if !eligible.contains(&start) {
158 return Err(Error::Input(format!(
159 "the head position {start} is not eligible"
160 )));
161 }
162
163 let positions: Vec<usize> = (start..n_units).chain(0..start).collect();
167 let list: Vec<usize> = positions.iter().map(|&p| order[p]).collect();
168
169 let mut effective_lambda = lambda.to_vec();
170 let (hits, accounting) = match head {
171 Head::All => {
172 let listed: Vec<f64> = list.iter().map(|&u| lambda[u]).collect();
173 let selection = pmr(&listed, uniforms, &positions)?;
174 for (i, &u) in list.iter().enumerate() {
175 effective_lambda[u] = selection.effective_lambda[i];
176 }
177 (selection.hits.clone(), selection)
178 }
179 Head::Residual => {
180 let fractional: Vec<usize> = (0..n_units)
181 .filter(|&i| lambda[list[i]] != lambda[list[i]].floor())
182 .collect();
183 let listed: Vec<f64> = fractional.iter().map(|&i| lambda[list[i]]).collect();
184 let priority: Vec<usize> = fractional.iter().map(|&i| positions[i]).collect();
185 let selection = pmr(&listed, uniforms, &priority)?;
186 let mut hits = vec![0_u64; n_units];
187 for i in 0..n_units {
188 if lambda[list[i]] == lambda[list[i]].floor() {
189 hits[i] = lambda[list[i]] as u64;
190 }
191 }
192 for (s, &i) in fractional.iter().enumerate() {
193 hits[i] = selection.hits[s];
194 effective_lambda[list[i]] = selection.effective_lambda[s];
195 }
196 (hits, selection)
197 }
198 };
199
200 Ok(Selected {
201 head: start,
202 list,
203 hits,
204 effective_lambda,
205 accounting: Some(accounting),
206 })
207}
208
209pub fn select(
216 lambda: &[f64],
217 order: &[usize],
218 head: Head,
219 u_head: f64,
220 uniforms: &[f64],
221) -> Result<Selected, Error> {
222 check_order(order, lambda.len())?;
223 let eligible = eligible_positions(lambda, order, head);
224 if eligible.is_empty() {
225 return select_from(lambda, order, 0, uniforms, head);
226 }
227 let weights: Vec<f64> = eligible.iter().map(|&p| lambda[order[p]]).collect();
228 let start = eligible[head_position(&weights, u_head)?];
229 select_from(lambda, order, start, uniforms, head)
230}