1pub mod reference {
5 use std::collections::HashMap;
6
7 use xlog_core::f64_total_order_key;
8
9 pub fn hash_join_u32(left: &[u32], right: &[u32]) -> Vec<(usize, usize)> {
11 let mut result = Vec::new();
12 let right_map: HashMap<u32, Vec<usize>> =
13 right
14 .iter()
15 .enumerate()
16 .fold(HashMap::new(), |mut acc, (idx, val)| {
17 acc.entry(*val).or_default().push(idx);
18 acc
19 });
20
21 for (left_idx, left_val) in left.iter().enumerate() {
22 if let Some(right_indices) = right_map.get(left_val) {
23 for &right_idx in right_indices {
24 result.push((left_idx, right_idx));
25 }
26 }
27 }
28 result
29 }
30
31 pub fn hash_join_multi(left_cols: &[&[u32]], right_cols: &[&[u32]]) -> Vec<(usize, usize)> {
33 if left_cols.is_empty() || right_cols.is_empty() {
34 return Vec::new();
35 }
36
37 let left_len = left_cols[0].len();
38 let right_len = right_cols[0].len();
39
40 let mut right_map: HashMap<Vec<u32>, Vec<usize>> = HashMap::new();
42 for i in 0..right_len {
43 let key: Vec<u32> = right_cols.iter().map(|col| col[i]).collect();
44 right_map.entry(key).or_default().push(i);
45 }
46
47 let mut result = Vec::new();
49 for i in 0..left_len {
50 let key: Vec<u32> = left_cols.iter().map(|col| col[i]).collect();
51 if let Some(right_indices) = right_map.get(&key) {
52 for &right_idx in right_indices {
53 result.push((i, right_idx));
54 }
55 }
56 }
57 result
58 }
59
60 pub fn semi_join_u32(left: &[u32], right: &[u32]) -> Vec<usize> {
62 let right_set: std::collections::HashSet<u32> = right.iter().copied().collect();
63 left.iter()
64 .enumerate()
65 .filter(|(_, val)| right_set.contains(val))
66 .map(|(idx, _)| idx)
67 .collect()
68 }
69
70 pub fn anti_join_u32(left: &[u32], right: &[u32]) -> Vec<usize> {
72 let right_set: std::collections::HashSet<u32> = right.iter().copied().collect();
73 left.iter()
74 .enumerate()
75 .filter(|(_, val)| !right_set.contains(val))
76 .map(|(idx, _)| idx)
77 .collect()
78 }
79
80 pub fn filter_compare_u32(data: &[u32], op: CompareOp, val: u32) -> Vec<usize> {
82 data.iter()
83 .enumerate()
84 .filter(|(_, d)| op.apply(**d, val))
85 .map(|(idx, _)| idx)
86 .collect()
87 }
88
89 pub fn filter_compare_i64(data: &[i64], op: CompareOp, val: i64) -> Vec<usize> {
91 data.iter()
92 .enumerate()
93 .filter(|(_, d)| op.apply(**d, val))
94 .map(|(idx, _)| idx)
95 .collect()
96 }
97
98 pub fn filter_compare_f64(data: &[f64], op: CompareOp, val: f64) -> Vec<f64> {
100 data.iter()
101 .filter(|d| op.apply_f64(**d, val))
102 .copied()
103 .collect()
104 }
105
106 pub fn compact_by_mask<T: Copy>(data: &[T], mask: &[u8]) -> Vec<T> {
108 data.iter()
109 .zip(mask.iter())
110 .filter(|(_, m)| **m != 0)
111 .map(|(d, _)| *d)
112 .collect()
113 }
114
115 pub fn radix_sort_u32(keys: &[u32]) -> (Vec<u32>, Vec<u32>) {
117 let mut indexed: Vec<(u32, u32)> = keys
118 .iter()
119 .enumerate()
120 .map(|(i, &k)| (k, i as u32))
121 .collect();
122
123 indexed.sort_by_key(|(k, _)| *k);
125
126 let sorted: Vec<u32> = indexed.iter().map(|(k, _)| *k).collect();
127 let perm: Vec<u32> = indexed.iter().map(|(_, i)| *i).collect();
128
129 (sorted, perm)
130 }
131
132 pub fn apply_permutation<T: Copy>(data: &[T], perm: &[u32]) -> Vec<T> {
134 perm.iter().map(|&i| data[i as usize]).collect()
135 }
136
137 pub fn inclusive_scan(data: &[u32]) -> Vec<u32> {
139 let mut result = Vec::with_capacity(data.len());
140 let mut sum = 0u32;
141 for &val in data {
142 sum = sum.wrapping_add(val);
143 result.push(sum);
144 }
145 result
146 }
147
148 pub fn exclusive_scan(data: &[u32]) -> Vec<u32> {
150 let mut result = Vec::with_capacity(data.len());
151 let mut sum = 0u32;
152 for &val in data {
153 result.push(sum);
154 sum = sum.wrapping_add(val);
155 }
156 result
157 }
158
159 pub fn groupby_count_sorted(keys: &[u32]) -> Vec<(u32, u64)> {
161 if keys.is_empty() {
162 return Vec::new();
163 }
164
165 let mut result = Vec::new();
166 let mut current_key = keys[0];
167 let mut count = 1u64;
168
169 for &key in &keys[1..] {
170 if key == current_key {
171 count += 1;
172 } else {
173 result.push((current_key, count));
174 current_key = key;
175 count = 1;
176 }
177 }
178 result.push((current_key, count));
179 result
180 }
181
182 pub fn groupby_sum_sorted(keys: &[u32], vals: &[u32]) -> Vec<(u32, u64)> {
184 if keys.is_empty() {
185 return Vec::new();
186 }
187
188 let mut result = Vec::new();
189 let mut current_key = keys[0];
190 let mut sum = vals[0] as u64;
191
192 for i in 1..keys.len() {
193 if keys[i] == current_key {
194 sum += vals[i] as u64;
195 } else {
196 result.push((current_key, sum));
197 current_key = keys[i];
198 sum = vals[i] as u64;
199 }
200 }
201 result.push((current_key, sum));
202 result
203 }
204
205 pub fn groupby_min_sorted(keys: &[u32], vals: &[u32]) -> Vec<(u32, u32)> {
207 if keys.is_empty() {
208 return Vec::new();
209 }
210
211 let mut result = Vec::new();
212 let mut current_key = keys[0];
213 let mut min_val = vals[0];
214
215 for i in 1..keys.len() {
216 if keys[i] == current_key {
217 min_val = min_val.min(vals[i]);
218 } else {
219 result.push((current_key, min_val));
220 current_key = keys[i];
221 min_val = vals[i];
222 }
223 }
224 result.push((current_key, min_val));
225 result
226 }
227
228 pub fn groupby_max_sorted(keys: &[u32], vals: &[u32]) -> Vec<(u32, u32)> {
230 if keys.is_empty() {
231 return Vec::new();
232 }
233
234 let mut result = Vec::new();
235 let mut current_key = keys[0];
236 let mut max_val = vals[0];
237
238 for i in 1..keys.len() {
239 if keys[i] == current_key {
240 max_val = max_val.max(vals[i]);
241 } else {
242 result.push((current_key, max_val));
243 current_key = keys[i];
244 max_val = vals[i];
245 }
246 }
247 result.push((current_key, max_val));
248 result
249 }
250
251 pub fn dedup_sorted<T: Eq + Copy>(data: &[T]) -> Vec<T> {
253 if data.is_empty() {
254 return Vec::new();
255 }
256
257 let mut result = vec![data[0]];
258 for &val in &data[1..] {
259 if val != *result.last().unwrap() {
260 result.push(val);
261 }
262 }
263 result
264 }
265
266 pub fn mark_duplicates<T: Eq>(sorted: &[T]) -> Vec<bool> {
268 if sorted.is_empty() {
269 return Vec::new();
270 }
271
272 let mut result = vec![true]; for i in 1..sorted.len() {
274 result.push(sorted[i] != sorted[i - 1]);
275 }
276 result
277 }
278
279 pub fn sorted_union<T: Ord + Copy>(a: &[T], b: &[T]) -> Vec<T> {
281 let mut result = Vec::new();
282 let mut i = 0;
283 let mut j = 0;
284
285 while i < a.len() && j < b.len() {
286 if a[i] < b[j] {
287 if result.last() != Some(&a[i]) {
288 result.push(a[i]);
289 }
290 i += 1;
291 } else if a[i] > b[j] {
292 if result.last() != Some(&b[j]) {
293 result.push(b[j]);
294 }
295 j += 1;
296 } else {
297 if result.last() != Some(&a[i]) {
298 result.push(a[i]);
299 }
300 i += 1;
301 j += 1;
302 }
303 }
304
305 while i < a.len() {
306 if result.last() != Some(&a[i]) {
307 result.push(a[i]);
308 }
309 i += 1;
310 }
311
312 while j < b.len() {
313 if result.last() != Some(&b[j]) {
314 result.push(b[j]);
315 }
316 j += 1;
317 }
318
319 result
320 }
321
322 pub fn sorted_diff<T: Ord + Copy>(a: &[T], b: &[T]) -> Vec<T> {
324 let mut result = Vec::new();
325 let mut i = 0;
326 let mut j = 0;
327
328 while i < a.len() && j < b.len() {
329 if a[i] < b[j] {
330 result.push(a[i]);
331 i += 1;
332 } else if a[i] > b[j] {
333 j += 1;
334 } else {
335 i += 1;
336 j += 1;
337 }
338 }
339
340 while i < a.len() {
341 result.push(a[i]);
342 i += 1;
343 }
344
345 result
346 }
347
348 pub fn pack_keys(cols: &[&[u8]], col_sizes: &[usize], num_rows: usize) -> Vec<u8> {
350 let row_size: usize = col_sizes.iter().sum();
351 let mut result = vec![0u8; row_size * num_rows];
352
353 for row in 0..num_rows {
354 let mut offset = 0;
355 for (col_idx, &col_size) in col_sizes.iter().enumerate() {
356 let src_start = row * col_size;
357 let src_end = src_start + col_size;
358 let dst_start = row * row_size + offset;
359 let dst_end = dst_start + col_size;
360 result[dst_start..dst_end].copy_from_slice(&cols[col_idx][src_start..src_end]);
361 offset += col_size;
362 }
363 }
364 result
365 }
366
367 pub fn hash_fnv1a(data: &[u8]) -> u32 {
369 const FNV_PRIME: u32 = 16777619;
370 const FNV_OFFSET: u32 = 2166136261;
371
372 let mut hash = FNV_OFFSET;
373 for &byte in data {
374 hash ^= byte as u32;
375 hash = hash.wrapping_mul(FNV_PRIME);
376 }
377 hash
378 }
379
380 pub fn unpack_column(
382 packed: &[u8],
383 row_size: usize,
384 col_offset: usize,
385 col_size: usize,
386 num_rows: usize,
387 ) -> Vec<u8> {
388 let mut result = vec![0u8; col_size * num_rows];
389
390 for row in 0..num_rows {
391 let src_start = row * row_size + col_offset;
392 let src_end = src_start + col_size;
393 let dst_start = row * col_size;
394 let dst_end = dst_start + col_size;
395 result[dst_start..dst_end].copy_from_slice(&packed[src_start..src_end]);
396 }
397 result
398 }
399
400 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
402 pub enum CompareOp {
403 Eq,
404 Ne,
405 Lt,
406 Le,
407 Gt,
408 Ge,
409 }
410
411 impl CompareOp {
412 pub fn apply<T: PartialOrd>(&self, a: T, b: T) -> bool {
413 match self {
414 CompareOp::Eq => a == b,
415 CompareOp::Ne => a != b,
416 CompareOp::Lt => a < b,
417 CompareOp::Le => a <= b,
418 CompareOp::Gt => a > b,
419 CompareOp::Ge => a >= b,
420 }
421 }
422
423 pub fn apply_f64(&self, a: f64, b: f64) -> bool {
424 let a = f64_total_order_key(a);
425 let b = f64_total_order_key(b);
426 match self {
427 CompareOp::Eq => a == b,
428 CompareOp::Ne => a != b,
429 CompareOp::Lt => a < b,
430 CompareOp::Le => a <= b,
431 CompareOp::Gt => a > b,
432 CompareOp::Ge => a >= b,
433 }
434 }
435 }
436}
437
438pub mod compare {
440 pub fn assert_eq_u32(gpu: &[u32], cpu: &[u32], context: &str) {
442 if gpu.len() != cpu.len() {
443 panic!(
444 "{}: length mismatch: GPU={}, CPU={}",
445 context,
446 gpu.len(),
447 cpu.len()
448 );
449 }
450
451 let mut first_diff = None;
452 let mut diff_count = 0;
453
454 for (i, (g, c)) in gpu.iter().zip(cpu.iter()).enumerate() {
455 if g != c {
456 if first_diff.is_none() {
457 first_diff = Some((i, *g, *c));
458 }
459 diff_count += 1;
460 }
461 }
462
463 if let Some((idx, gpu_val, cpu_val)) = first_diff {
464 panic!(
465 "{}: {} differences found. First at index {}: GPU={}, CPU={}",
466 context, diff_count, idx, gpu_val, cpu_val
467 );
468 }
469 }
470
471 pub fn assert_eq_i64(gpu: &[i64], cpu: &[i64], context: &str) {
473 if gpu.len() != cpu.len() {
474 panic!(
475 "{}: length mismatch: GPU={}, CPU={}",
476 context,
477 gpu.len(),
478 cpu.len()
479 );
480 }
481
482 let mut first_diff = None;
483 let mut diff_count = 0;
484
485 for (i, (g, c)) in gpu.iter().zip(cpu.iter()).enumerate() {
486 if g != c {
487 if first_diff.is_none() {
488 first_diff = Some((i, *g, *c));
489 }
490 diff_count += 1;
491 }
492 }
493
494 if let Some((idx, gpu_val, cpu_val)) = first_diff {
495 panic!(
496 "{}: {} differences found. First at index {}: GPU={}, CPU={}",
497 context, diff_count, idx, gpu_val, cpu_val
498 );
499 }
500 }
501
502 pub fn assert_eq_u64(gpu: &[u64], cpu: &[u64], context: &str) {
504 if gpu.len() != cpu.len() {
505 panic!(
506 "{}: length mismatch: GPU={}, CPU={}",
507 context,
508 gpu.len(),
509 cpu.len()
510 );
511 }
512
513 let mut first_diff = None;
514 let mut diff_count = 0;
515
516 for (i, (g, c)) in gpu.iter().zip(cpu.iter()).enumerate() {
517 if g != c {
518 if first_diff.is_none() {
519 first_diff = Some((i, *g, *c));
520 }
521 diff_count += 1;
522 }
523 }
524
525 if let Some((idx, gpu_val, cpu_val)) = first_diff {
526 panic!(
527 "{}: {} differences found. First at index {}: GPU={}, CPU={}",
528 context, diff_count, idx, gpu_val, cpu_val
529 );
530 }
531 }
532
533 pub fn assert_eq_f64_ulp(gpu: &[f64], cpu: &[f64], max_ulp: u64, context: &str) {
535 if gpu.len() != cpu.len() {
536 panic!(
537 "{}: length mismatch: GPU={}, CPU={}",
538 context,
539 gpu.len(),
540 cpu.len()
541 );
542 }
543
544 let mut first_diff = None;
545 let mut diff_count = 0;
546
547 for (i, (g, c)) in gpu.iter().zip(cpu.iter()).enumerate() {
548 let ulp_diff = ulp_distance(*g, *c);
549 if ulp_diff > max_ulp {
550 if first_diff.is_none() {
551 first_diff = Some((i, *g, *c, ulp_diff));
552 }
553 diff_count += 1;
554 }
555 }
556
557 if let Some((idx, gpu_val, cpu_val, ulp)) = first_diff {
558 panic!(
559 "{}: {} ULP violations (max={}). First at index {}: GPU={}, CPU={}, ULP={}",
560 context, diff_count, max_ulp, idx, gpu_val, cpu_val, ulp
561 );
562 }
563 }
564
565 pub fn assert_eq_f64_rel(gpu: &[f64], cpu: &[f64], rel_tol: f64, context: &str) {
567 if gpu.len() != cpu.len() {
568 panic!(
569 "{}: length mismatch: GPU={}, CPU={}",
570 context,
571 gpu.len(),
572 cpu.len()
573 );
574 }
575
576 let mut first_diff = None;
577 let mut diff_count = 0;
578
579 for (i, (g, c)) in gpu.iter().zip(cpu.iter()).enumerate() {
580 let rel_diff = if *c == 0.0 {
581 g.abs()
582 } else {
583 ((g - c) / c).abs()
584 };
585
586 if rel_diff > rel_tol && !g.is_nan() && !c.is_nan() {
587 if first_diff.is_none() {
588 first_diff = Some((i, *g, *c, rel_diff));
589 }
590 diff_count += 1;
591 }
592 }
593
594 if let Some((idx, gpu_val, cpu_val, rel)) = first_diff {
595 panic!(
596 "{}: {} relative tolerance violations (max={}). First at index {}: GPU={}, CPU={}, rel_diff={}",
597 context, diff_count, rel_tol, idx, gpu_val, cpu_val, rel
598 );
599 }
600 }
601
602 fn ulp_distance(a: f64, b: f64) -> u64 {
604 if a.is_nan() || b.is_nan() {
605 return u64::MAX;
606 }
607 if a == b {
608 return 0;
609 }
610 if a.is_infinite() || b.is_infinite() {
611 return u64::MAX;
612 }
613
614 let a_bits = a.to_bits() as i64;
615 let b_bits = b.to_bits() as i64;
616
617 (a_bits - b_bits).unsigned_abs()
618 }
619
620 pub fn assert_set_eq_u32(gpu: &[u32], cpu: &[u32], context: &str) {
622 let mut gpu_sorted = gpu.to_vec();
623 let mut cpu_sorted = cpu.to_vec();
624 gpu_sorted.sort();
625 cpu_sorted.sort();
626
627 if gpu_sorted != cpu_sorted {
628 panic!(
629 "{}: set mismatch. GPU (sorted): {:?}, CPU (sorted): {:?}",
630 context,
631 &gpu_sorted[..gpu_sorted.len().min(10)],
632 &cpu_sorted[..cpu_sorted.len().min(10)]
633 );
634 }
635 }
636
637 pub fn assert_valid_permutation(perm: &[u32], len: usize, context: &str) {
639 if perm.len() != len {
640 panic!(
641 "{}: permutation length {} != expected {}",
642 context,
643 perm.len(),
644 len
645 );
646 }
647
648 let mut seen = vec![false; len];
649 for (i, &idx) in perm.iter().enumerate() {
650 if idx as usize >= len {
651 panic!(
652 "{}: permutation index {} out of bounds at position {}",
653 context, idx, i
654 );
655 }
656 if seen[idx as usize] {
657 panic!(
658 "{}: duplicate permutation index {} at position {}",
659 context, idx, i
660 );
661 }
662 seen[idx as usize] = true;
663 }
664 }
665
666 pub fn assert_stable_sort(
668 original_keys: &[u32],
669 original_vals: &[u32],
670 sorted_keys: &[u32],
671 sorted_vals: &[u32],
672 context: &str,
673 ) {
674 let mut key_to_vals: std::collections::HashMap<u32, Vec<u32>> =
676 std::collections::HashMap::new();
677 for (&k, &v) in original_keys.iter().zip(original_vals.iter()) {
678 key_to_vals.entry(k).or_default().push(v);
679 }
680
681 let mut key_to_idx: std::collections::HashMap<u32, usize> =
683 std::collections::HashMap::new();
684 for (&k, &v) in sorted_keys.iter().zip(sorted_vals.iter()) {
685 let idx = key_to_idx.entry(k).or_insert(0);
686 let expected_vals = key_to_vals.get(&k).unwrap();
687 if *idx >= expected_vals.len() {
688 panic!("{}: too many values for key {}", context, k);
689 }
690 if v != expected_vals[*idx] {
691 panic!(
692 "{}: stability violation for key {}. Expected value {} but got {}",
693 context, k, expected_vals[*idx], v
694 );
695 }
696 *idx += 1;
697 }
698 }
699
700 pub fn is_sorted_u32(data: &[u32]) -> bool {
702 data.windows(2).all(|w| w[0] <= w[1])
703 }
704
705 pub fn is_sorted_desc_u32(data: &[u32]) -> bool {
707 data.windows(2).all(|w| w[0] >= w[1])
708 }
709}