1use polydat::derive_support::Ext;
43use polydat::iteration::cursor_partition::{Partition, PartitionList};
44
45#[polydat::polydat_node(category = Arithmetic)]
47fn cardinality(partition: Ext<Partition>) -> u64 {
48 partition.cardinality()
49}
50
51#[polydat::polydat_node(category = Arithmetic)]
53fn start_of(partition: Ext<Partition>) -> u64 {
54 partition.start_ord
55}
56
57#[polydat::polydat_node(category = Arithmetic)]
59fn end_of(partition: Ext<Partition>) -> u64 {
60 partition.end_ord
61}
62
63#[polydat::polydat_node(category = Arithmetic)]
65fn idx_of(partition: Ext<Partition>) -> u64 {
66 partition.idx
67}
68
69#[polydat::polydat_node(category = Arithmetic)]
74fn count_of(partition: Ext<Partition>) -> u64 {
75 partition.count
76}
77
78#[polydat::polydat_node(category = Arithmetic)]
83fn mod_in(n: u64, partition: Ext<Partition>) -> u64 {
84 let card = partition.cardinality();
85 if card == 0 {
86 partition.start_ord
87 } else {
88 partition.start_ord + (n % card)
89 }
90}
91
92#[polydat::polydat_node(category = Arithmetic)]
97fn at(partition: Ext<Partition>, i: u64) -> u64 {
98 let card = partition.cardinality();
99 if i >= card {
100 panic!(
101 "at({}, {i}): index out of range — partition #{} cardinality is {card}",
102 partition.start_ord, partition.idx
103 );
104 }
105 partition.start_ord + i
106}
107
108#[polydat::polydat_node(category = Arithmetic)]
113fn clamp_in(n: u64, partition: Ext<Partition>) -> u64 {
114 if partition.cardinality() == 0 {
115 partition.start_ord
116 } else {
117 n.max(partition.start_ord).min(partition.end_ord - 1)
118 }
119}
120
121#[polydat::polydat_node(category = Hashing)]
129fn random_in(partition: Ext<Partition>, seed: u64) -> u64 {
130 let card = partition.cardinality();
131 if card == 0 {
132 partition.start_ord
133 } else {
134 partition.start_ord + crate::hash::splitmix64_u64(seed) % card
135 }
136}
137
138#[polydat::polydat_node(category = Arithmetic)]
147fn subdivide(partition: Ext<Partition>, n: u64) -> Ext<PartitionList> {
148 let parts = polydat::iteration::cursor_partition::subdivide_partition(&partition, n)
149 .unwrap_or_else(|e| panic!("{e}"));
150 Ext(PartitionList(std::sync::Arc::new(parts)))
151}
152
153#[polydat::polydat_node(category = Arithmetic)]
159fn partitions(
160 spec: &str,
161 #[poly_default(100u64)] extent: polydat::derive_support::Const<u64>,
162) -> Ext<PartitionList> {
163 let parsed = polydat::iteration::cursor_partition::parse(spec)
164 .unwrap_or_else(|e| panic!("partitions: bad spec `{spec}`: {e}"));
165 let parts = polydat::iteration::cursor_partition::resolve(&parsed, 0, *extent)
166 .unwrap_or_else(|e| panic!("partitions: resolve failed: {e}"));
167 Ext(PartitionList(std::sync::Arc::new(parts)))
168}
169
170#[polydat::polydat_node(category = Arithmetic)]
172fn partition_count(list: Ext<PartitionList>) -> u64 {
173 list.0.len() as u64
174}
175
176#[polydat::polydat_node(category = Arithmetic)]
179fn partition_at(list: Ext<PartitionList>, i: u64) -> Ext<Partition> {
180 let n = list.0.len() as u64;
181 if i >= n {
182 panic!("partition_at: index {i} out of range for a list of {n} partition(s)");
183 }
184 Ext(list.0.0[i as usize])
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190 use polydat::ast::{PolydatNode, Value};
191
192 fn fixture(idx: u64, start: u64, end: u64) -> Partition {
193 Partition {
194 idx,
195 count: idx + 1,
196 start_ord: start,
197 end_ord: end,
198 start_pct: 0.0,
199 end_pct: 0.0,
200 base_extent: end,
201 }
202 }
203
204 #[test]
205 fn cardinality_returns_end_minus_start() {
206 let node = Cardinality::new();
207 let mut out = [Value::None];
208 node.eval(&[Value::from_partition(fixture(0, 100, 500))], &mut out);
209 assert_eq!(out[0].as_u64(), 400);
210 }
211
212 #[test]
213 fn start_of_returns_start_ord() {
214 let node = StartOf::new();
215 let mut out = [Value::None];
216 node.eval(&[Value::from_partition(fixture(2, 100, 500))], &mut out);
217 assert_eq!(out[0].as_u64(), 100);
218 }
219
220 #[test]
221 fn end_of_returns_end_ord() {
222 let node = EndOf::new();
223 let mut out = [Value::None];
224 node.eval(&[Value::from_partition(fixture(0, 100, 500))], &mut out);
225 assert_eq!(out[0].as_u64(), 500);
226 }
227
228 #[test]
229 fn idx_of_returns_idx() {
230 let node = IdxOf::new();
231 let mut out = [Value::None];
232 node.eval(&[Value::from_partition(fixture(3, 100, 500))], &mut out);
233 assert_eq!(out[0].as_u64(), 3);
234 }
235
236 #[test]
237 fn mod_in_wraps_inside_partition() {
238 let node = ModIn::new();
239 let mut out = [Value::None];
240 let p = Value::from_partition(fixture(0, 100, 200));
241 for (n, expected) in [(0, 100), (50, 150), (99, 199), (100, 100), (250, 150)] {
242 node.eval(&[Value::U64(n), p.clone()], &mut out);
243 assert_eq!(out[0].as_u64(), expected, "mod_in({n}) over [100, 200)");
244 }
245 }
246
247 #[test]
248 fn mod_in_zero_cardinality_returns_start() {
249 let node = ModIn::new();
250 let mut out = [Value::None];
251 let p = Value::from_partition(fixture(0, 100, 100));
252 node.eval(&[Value::U64(42), p], &mut out);
253 assert_eq!(out[0].as_u64(), 100);
254 }
255
256 #[test]
257 fn at_offset_within_bounds() {
258 let node = At::new();
259 let mut out = [Value::None];
260 let p = Value::from_partition(fixture(0, 100, 200));
261 node.eval(&[p, Value::U64(15)], &mut out);
262 assert_eq!(out[0].as_u64(), 115);
263 }
264
265 #[test]
266 #[should_panic(expected = "index out of range")]
267 fn at_offset_out_of_range_panics() {
268 let node = At::new();
269 let mut out = [Value::None];
270 let p = Value::from_partition(fixture(0, 100, 200));
271 node.eval(&[p, Value::U64(100)], &mut out);
272 }
273
274 #[test]
275 fn clamp_in_saturates_at_bounds() {
276 let node = ClampIn::new();
277 let mut out = [Value::None];
278 let p = Value::from_partition(fixture(0, 100, 200));
279 for (n, expected) in [
280 (50, 100),
281 (100, 100),
282 (150, 150),
283 (199, 199),
284 (200, 199),
285 (1000, 199),
286 ] {
287 node.eval(&[Value::U64(n), p.clone()], &mut out);
288 assert_eq!(out[0].as_u64(), expected, "clamp_in({n}) over [100, 200)");
289 }
290 }
291
292 #[test]
293 fn random_in_deterministic_and_bounded() {
294 let node = RandomIn::new();
295 let mut out = [Value::None];
296 let p = Value::from_partition(fixture(0, 100, 200));
297 let mut first = Vec::new();
298 for seed in 0..32u64 {
299 node.eval(&[p.clone(), Value::U64(seed)], &mut out);
300 let v = out[0].as_u64();
301 assert!(
302 (100..200).contains(&v),
303 "random_in(seed={seed}) = {v} outside [100, 200)"
304 );
305 first.push(v);
306 }
307 for (seed, expected) in first.iter().enumerate() {
309 node.eval(&[p.clone(), Value::U64(seed as u64)], &mut out);
310 assert_eq!(out[0].as_u64(), *expected);
311 }
312 assert!(first.windows(2).any(|w| w[0] != w[1]));
314 }
315
316 #[test]
317 fn random_in_zero_cardinality_returns_start() {
318 let node = RandomIn::new();
319 let mut out = [Value::None];
320 node.eval(
321 &[Value::from_partition(fixture(0, 100, 100)), Value::U64(7)],
322 &mut out,
323 );
324 assert_eq!(out[0].as_u64(), 100);
325 }
326
327 #[test]
328 fn partition_at_and_count_index_a_list_by_position() {
329 let list = Value::Ext(Box::new(PartitionList::new(vec![
330 fixture(0, 0, 10),
331 fixture(1, 10, 25),
332 fixture(2, 25, 100),
333 ])));
334 let mut out = [Value::None];
335 PartitionCount::new().eval(std::slice::from_ref(&list), &mut out);
336 assert_eq!(out[0], Value::U64(3));
337 PartitionAt::new().eval(&[list.clone(), Value::U64(1)], &mut out);
338 let p = out[0].as_partition().expect("Partition");
339 assert_eq!((p.start_ord, p.end_ord), (10, 25));
340 }
341
342 #[test]
343 #[should_panic(expected = "out of range")]
344 fn partition_at_past_the_end_panics() {
345 let list = Value::Ext(Box::new(PartitionList::new(vec![fixture(0, 0, 10)])));
346 let mut out = [Value::None];
347 PartitionAt::new().eval(&[list, Value::U64(1)], &mut out);
348 }
349 #[test]
350 fn subdivide_splits_into_near_equal_contiguous_parts() {
351 let node = Subdivide::new();
352 let mut out = [Value::None];
353 let parent = Partition {
354 idx: 1,
355 count: 2,
356 start_ord: 900,
357 end_ord: 1000,
358 start_pct: 90.0,
359 end_pct: 100.0,
360 base_extent: 1000,
361 };
362 node.eval(&[Value::from_partition(parent), Value::U64(10)], &mut out);
363 let list = out[0].as_partition_list().expect("PartitionList");
364 assert_eq!(list.len(), 10);
365 let subs = list.as_slice();
366 assert_eq!(subs[0].start_ord, 900);
367 assert_eq!(subs[9].end_ord, 1000);
368 for (i, s) in subs.iter().enumerate() {
369 assert_eq!(s.idx, i as u64, "indices restart at 0");
370 assert_eq!(s.cardinality(), 10);
371 assert_eq!(s.base_extent, 1000, "base_extent propagates");
372 }
373 for w in subs.windows(2) {
374 assert_eq!(w[0].end_ord, w[1].start_ord, "contiguous");
375 }
376 assert!((subs[0].start_pct - 90.0).abs() < 1e-9);
378 assert!((subs[4].end_pct - 95.0).abs() < 1e-9);
379 assert!((subs[9].end_pct - 100.0).abs() < 1e-9);
380 }
381
382 #[test]
383 #[should_panic(expected = "non-empty sub-partitions")]
384 fn subdivide_finer_than_cardinality_panics() {
385 let node = Subdivide::new();
386 let mut out = [Value::None];
387 node.eval(
388 &[Value::from_partition(fixture(0, 0, 5)), Value::U64(10)],
389 &mut out,
390 );
391 }
392
393 #[test]
394 #[should_panic(expected = "must be >= 1")]
395 fn subdivide_zero_count_panics() {
396 let node = Subdivide::new();
397 let mut out = [Value::None];
398 node.eval(
399 &[Value::from_partition(fixture(0, 0, 100)), Value::U64(0)],
400 &mut out,
401 );
402 }
403
404 #[test]
405 fn partitions_node_resolves_spec_against_extent() {
406 let node = Partitions::new(1000);
407 let mut out = [Value::None];
408 node.eval(&[Value::Str("linear:4".into())], &mut out);
409 let list = out[0].as_partition_list().expect("PartitionList");
410 assert_eq!(list.len(), 4);
411 for (i, p) in list.as_slice().iter().enumerate() {
412 assert_eq!(p.idx, i as u64);
413 assert_eq!(p.cardinality(), 250);
414 }
415 }
416
417 #[test]
418 fn partitions_node_handles_form1_single_range() {
419 let node = Partitions::new(1000);
420 let mut out = [Value::None];
421 node.eval(&[Value::Str("0..50%".into())], &mut out);
422 let list = out[0].as_partition_list().expect("PartitionList");
423 assert_eq!(list.len(), 1);
424 assert_eq!(list.as_slice()[0].start_ord, 0);
425 assert_eq!(list.as_slice()[0].end_ord, 500);
426 }
427}