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