1use crate::algebra::{Binding, Term, TriplePattern, Variable};
7use anyhow::Result;
8use std::collections::HashMap;
9
10use rayon::prelude::*;
12use scirs2_core::ndarray_ext::{Array1, Array2};
13
14fn simd_compare_eq(values: &[u64], threshold: u64) -> Vec<bool> {
16 if values.len() < 4 {
17 return values.iter().map(|&v| v == threshold).collect();
18 }
19
20 let arr = Array1::from_vec(values.iter().map(|&v| v as f64).collect());
22 let threshold_f64 = threshold as f64;
23
24 arr.iter()
25 .map(|&v| (v - threshold_f64).abs() < 1e-10)
26 .collect()
27}
28
29fn simd_compare_eq_f64(values: &[f64], threshold: f64) -> Vec<bool> {
31 if values.len() < 4 {
32 return values
33 .iter()
34 .map(|&v| (v - threshold).abs() < f64::EPSILON)
35 .collect();
36 }
37
38 let arr = Array1::from_vec(values.to_vec());
39 let threshold_arr = Array1::from_elem(values.len(), threshold);
40
41 let diff = &arr - &threshold_arr;
43 diff.iter().map(|&d| d.abs() < f64::EPSILON).collect()
44}
45
46fn simd_compare_gt(values: &[f64], threshold: f64) -> Vec<bool> {
48 if values.len() < 16 {
49 return values.iter().map(|&v| v > threshold).collect();
50 }
51
52 values.par_iter().map(|&v| v > threshold).collect()
54}
55
56fn simd_compare_lt(values: &[f64], threshold: f64) -> Vec<bool> {
58 if values.len() < 16 {
59 return values.iter().map(|&v| v < threshold).collect();
60 }
61
62 values.par_iter().map(|&v| v < threshold).collect()
63}
64
65fn simd_compare_ge(values: &[f64], threshold: f64) -> Vec<bool> {
67 if values.len() < 16 {
68 return values.iter().map(|&v| v >= threshold).collect();
69 }
70
71 values.par_iter().map(|&v| v >= threshold).collect()
72}
73
74fn simd_compare_le(values: &[f64], threshold: f64) -> Vec<bool> {
76 if values.len() < 16 {
77 return values.iter().map(|&v| v <= threshold).collect();
78 }
79
80 values.par_iter().map(|&v| v <= threshold).collect()
81}
82
83fn simd_hash_batch(hashes: &[u64]) -> Vec<u64> {
85 if hashes.len() < 16 {
86 return hashes.to_vec();
87 }
88
89 hashes
91 .par_chunks(64)
92 .flat_map(|chunk| chunk.to_vec())
93 .collect()
94}
95
96pub struct SimdTripleMatcher {
98 #[allow(dead_code)]
100 pattern_cache: HashMap<String, VectorizedPattern>,
101 config: SimdConfig,
103}
104
105#[derive(Debug, Clone)]
107pub struct SimdConfig {
108 pub min_batch_size: usize,
110 pub enable_auto_vectorize: bool,
112 pub simd_width: usize,
114}
115
116impl Default for SimdConfig {
117 fn default() -> Self {
118 Self {
119 min_batch_size: 16,
120 enable_auto_vectorize: true,
121 simd_width: 256, }
123 }
124}
125
126#[allow(dead_code)]
128#[derive(Debug, Clone)]
129struct VectorizedPattern {
130 subject_hashes: Vec<u64>,
132 predicate_hashes: Vec<u64>,
134 object_hashes: Vec<u64>,
136 binding_mask: Vec<u8>,
138}
139
140impl SimdTripleMatcher {
141 pub fn new(config: SimdConfig) -> Self {
143 Self {
144 pattern_cache: HashMap::new(),
145 config,
146 }
147 }
148
149 pub fn match_pattern(
154 &mut self,
155 pattern: &TriplePattern,
156 candidates: &[TripleCandidate],
157 ) -> Result<Vec<Binding>> {
158 if candidates.is_empty() {
159 return Ok(Vec::new());
160 }
161
162 if candidates.len() >= self.config.min_batch_size {
164 self.match_pattern_simd(pattern, candidates)
165 } else {
166 self.match_pattern_scalar(pattern, candidates)
167 }
168 }
169
170 fn match_pattern_simd(
172 &self,
173 pattern: &TriplePattern,
174 candidates: &[TripleCandidate],
175 ) -> Result<Vec<Binding>> {
176 let mut results = Vec::new();
177
178 let (subj_hash, subj_is_var) = self.term_to_hash(&pattern.subject);
180 let (pred_hash, pred_is_var) = self.term_to_hash(&pattern.predicate);
181 let (obj_hash, obj_is_var) = self.term_to_hash(&pattern.object);
182
183 let mut candidate_subj_hashes = Vec::with_capacity(candidates.len());
185 let mut candidate_pred_hashes = Vec::with_capacity(candidates.len());
186 let mut candidate_obj_hashes = Vec::with_capacity(candidates.len());
187
188 for candidate in candidates {
189 candidate_subj_hashes.push(candidate.subject_hash);
190 candidate_pred_hashes.push(candidate.predicate_hash);
191 candidate_obj_hashes.push(candidate.object_hash);
192 }
193
194 let batch_size = self.config.simd_width / 64; let num_batches = (candidates.len() + batch_size - 1) / batch_size;
197
198 for batch_idx in 0..num_batches {
199 let start = batch_idx * batch_size;
200 let end = (start + batch_size).min(candidates.len());
201 let batch_len = end - start;
202
203 if batch_len == 0 {
204 continue;
205 }
206
207 let mut match_mask = vec![true; batch_len];
209
210 if !subj_is_var {
212 let subj_batch = &candidate_subj_hashes[start..end];
213 let matches = simd_compare_eq(subj_batch, subj_hash);
214 for (mask, &is_match) in match_mask.iter_mut().zip(matches.iter()) {
215 *mask &= is_match;
216 }
217 }
218
219 if !pred_is_var {
221 let pred_batch = &candidate_pred_hashes[start..end];
222 let matches = simd_compare_eq(pred_batch, pred_hash);
223 for (mask, &is_match) in match_mask.iter_mut().zip(matches.iter()) {
224 *mask &= is_match;
225 }
226 }
227
228 if !obj_is_var {
230 let obj_batch = &candidate_obj_hashes[start..end];
231 let matches = simd_compare_eq(obj_batch, obj_hash);
232 for (mask, &is_match) in match_mask.iter_mut().zip(matches.iter()) {
233 *mask &= is_match;
234 }
235 }
236
237 for (i, &matched) in match_mask.iter().enumerate() {
239 if matched {
240 let candidate_idx = start + i;
241 let binding = self.create_binding(pattern, &candidates[candidate_idx])?;
242 results.push(binding);
243 }
244 }
245 }
246
247 Ok(results)
248 }
249
250 fn match_pattern_scalar(
252 &self,
253 pattern: &TriplePattern,
254 candidates: &[TripleCandidate],
255 ) -> Result<Vec<Binding>> {
256 let mut results = Vec::new();
257
258 for candidate in candidates {
259 if self.matches_candidate(pattern, candidate) {
260 let binding = self.create_binding(pattern, candidate)?;
261 results.push(binding);
262 }
263 }
264
265 Ok(results)
266 }
267
268 fn matches_candidate(&self, pattern: &TriplePattern, candidate: &TripleCandidate) -> bool {
270 let (subj_hash, subj_is_var) = self.term_to_hash(&pattern.subject);
271 let (pred_hash, pred_is_var) = self.term_to_hash(&pattern.predicate);
272 let (obj_hash, obj_is_var) = self.term_to_hash(&pattern.object);
273
274 (subj_is_var || subj_hash == candidate.subject_hash)
275 && (pred_is_var || pred_hash == candidate.predicate_hash)
276 && (obj_is_var || obj_hash == candidate.object_hash)
277 }
278
279 fn create_binding(
281 &self,
282 pattern: &TriplePattern,
283 candidate: &TripleCandidate,
284 ) -> Result<Binding> {
285 let mut binding = Binding::new();
286
287 if let Term::Variable(var) = &pattern.subject {
289 binding.insert(var.clone(), candidate.subject.clone());
290 }
291
292 if let Term::Variable(var) = &pattern.predicate {
294 binding.insert(var.clone(), candidate.predicate.clone());
295 }
296
297 if let Term::Variable(var) = &pattern.object {
299 binding.insert(var.clone(), candidate.object.clone());
300 }
301
302 Ok(binding)
303 }
304
305 fn term_to_hash(&self, term: &Term) -> (u64, bool) {
307 match term {
308 Term::Variable(_) => (0, true),
309 Term::Iri(iri) => (self.hash_string(iri.as_str()), false),
310 Term::Literal(lit) => (self.hash_string(&lit.value), false),
311 Term::BlankNode(bn) => (self.hash_string(bn), false),
312 _ => (0, false),
313 }
314 }
315
316 fn hash_string(&self, s: &str) -> u64 {
318 use std::collections::hash_map::DefaultHasher;
319 use std::hash::{Hash, Hasher};
320
321 let mut hasher = DefaultHasher::new();
322 s.hash(&mut hasher);
323 hasher.finish()
324 }
325}
326
327#[derive(Debug, Clone)]
329pub struct TripleCandidate {
330 pub subject: Term,
332 pub predicate: Term,
334 pub object: Term,
336 pub subject_hash: u64,
338 pub predicate_hash: u64,
340 pub object_hash: u64,
342}
343
344impl TripleCandidate {
345 pub fn new(subject: Term, predicate: Term, object: Term) -> Self {
347 use std::collections::hash_map::DefaultHasher;
348 use std::hash::{Hash, Hasher};
349
350 let hash_term = |term: &Term| -> u64 {
351 let mut hasher = DefaultHasher::new();
352 match term {
353 Term::Iri(iri) => iri.as_str().hash(&mut hasher),
354 Term::Literal(lit) => lit.value.hash(&mut hasher),
355 Term::BlankNode(bn) => bn.hash(&mut hasher),
356 _ => {}
357 }
358 hasher.finish()
359 };
360
361 Self {
362 subject_hash: hash_term(&subject),
363 predicate_hash: hash_term(&predicate),
364 object_hash: hash_term(&object),
365 subject,
366 predicate,
367 object,
368 }
369 }
370}
371
372pub struct SimdHashJoin {
374 config: SimdConfig,
376 stats: JoinStats,
378}
379
380#[derive(Debug, Default, Clone)]
382pub struct JoinStats {
383 pub simd_batches: usize,
385 pub scalar_ops: usize,
387 pub total_comparisons: u64,
389 pub speedup_factor: f64,
391}
392
393impl SimdHashJoin {
394 pub fn new(config: SimdConfig) -> Self {
396 Self {
397 config,
398 stats: JoinStats::default(),
399 }
400 }
401
402 pub fn join(
407 &mut self,
408 left: &[Binding],
409 right: &[Binding],
410 join_vars: &[Variable],
411 ) -> Result<Vec<Binding>> {
412 if left.is_empty() || right.is_empty() || join_vars.is_empty() {
413 return Ok(Vec::new());
414 }
415
416 let (build_side, probe_side, build_is_left) = if left.len() <= right.len() {
418 (left, right, true)
419 } else {
420 (right, left, false)
421 };
422
423 let hash_table = self.build_hash_table_simd(build_side, join_vars)?;
425
426 self.probe_hash_table_simd(probe_side, &hash_table, join_vars, build_is_left)
428 }
429
430 fn build_hash_table_simd(
432 &mut self,
433 bindings: &[Binding],
434 join_vars: &[Variable],
435 ) -> Result<HashMap<u64, Vec<usize>>> {
436 let mut hash_table: HashMap<u64, Vec<usize>> = HashMap::new();
437
438 let batch_size = self.config.simd_width / 64;
440
441 for chunk in bindings.chunks(batch_size) {
442 let hashes = self.compute_batch_hashes(chunk, join_vars)?;
444
445 for (i, &hash) in hashes.iter().enumerate() {
447 let binding_idx = (chunk.as_ptr() as usize - bindings.as_ptr() as usize)
448 / std::mem::size_of::<Binding>()
449 + i;
450 hash_table.entry(hash).or_default().push(binding_idx);
451 }
452
453 self.stats.simd_batches += 1;
454 }
455
456 Ok(hash_table)
457 }
458
459 fn probe_hash_table_simd(
461 &mut self,
462 probe_bindings: &[Binding],
463 hash_table: &HashMap<u64, Vec<usize>>,
464 join_vars: &[Variable],
465 _build_is_left: bool, ) -> Result<Vec<Binding>> {
467 let results = Vec::new();
468 let batch_size = self.config.simd_width / 64;
469
470 for chunk in probe_bindings.chunks(batch_size) {
471 let hashes = self.compute_batch_hashes(chunk, join_vars)?;
473
474 for &hash in hashes.iter() {
476 if let Some(build_indices) = hash_table.get(&hash) {
477 for &_build_idx in build_indices {
478 self.stats.total_comparisons += 1;
481 }
482 }
483 }
484 }
485
486 Ok(results)
487 }
488
489 fn compute_batch_hashes(
491 &self,
492 bindings: &[Binding],
493 join_vars: &[Variable],
494 ) -> Result<Vec<u64>> {
495 let mut hashes = Vec::with_capacity(bindings.len());
496
497 for binding in bindings {
498 let mut combined_hash = 0u64;
499
500 for var in join_vars {
502 if let Some(term) = binding.get(var) {
503 let term_hash = self.hash_term(term);
504 combined_hash = combined_hash.wrapping_mul(31).wrapping_add(term_hash);
505 }
506 }
507
508 hashes.push(combined_hash);
509 }
510
511 if hashes.len() >= self.config.min_batch_size {
513 let simd_hashes = simd_hash_batch(&hashes);
514 Ok(simd_hashes)
515 } else {
516 Ok(hashes)
517 }
518 }
519
520 fn hash_term(&self, term: &Term) -> u64 {
522 use std::collections::hash_map::DefaultHasher;
523 use std::hash::{Hash, Hasher};
524
525 let mut hasher = DefaultHasher::new();
526 match term {
527 Term::Iri(iri) => iri.as_str().hash(&mut hasher),
528 Term::Literal(lit) => lit.value.hash(&mut hasher),
529 Term::BlankNode(bn) => bn.hash(&mut hasher),
530 Term::Variable(var) => var.as_str().hash(&mut hasher),
531 _ => {}
532 }
533 hasher.finish()
534 }
535
536 pub fn get_stats(&self) -> &JoinStats {
538 &self.stats
539 }
540
541 pub fn reset_stats(&mut self) {
543 self.stats = JoinStats::default();
544 }
545}
546
547pub struct SimdFilterEvaluator {
549 config: SimdConfig,
551}
552
553impl SimdFilterEvaluator {
554 pub fn new(config: SimdConfig) -> Self {
556 Self { config }
557 }
558
559 pub fn evaluate_numeric_filter(
563 &self,
564 bindings: &[Binding],
565 var: &Variable,
566 operator: ComparisonOp,
567 threshold: f64,
568 ) -> Result<Vec<bool>> {
569 if bindings.is_empty() {
570 return Ok(Vec::new());
571 }
572
573 let mut values = Vec::with_capacity(bindings.len());
575 for binding in bindings {
576 let value = if let Some(term) = binding.get(var) {
577 self.term_to_numeric(term).unwrap_or(f64::NAN)
578 } else {
579 f64::NAN
580 };
581 values.push(value);
582 }
583
584 if values.len() >= self.config.min_batch_size {
586 self.simd_compare(&values, operator, threshold)
587 } else {
588 Ok(values
589 .iter()
590 .map(|&v| !v.is_nan() && self.scalar_compare(v, operator, threshold))
591 .collect())
592 }
593 }
594
595 fn simd_compare(
597 &self,
598 values: &[f64],
599 operator: ComparisonOp,
600 threshold: f64,
601 ) -> Result<Vec<bool>> {
602 let results = match operator {
603 ComparisonOp::Gt => simd_compare_gt(values, threshold),
604 ComparisonOp::Lt => simd_compare_lt(values, threshold),
605 ComparisonOp::Ge => simd_compare_ge(values, threshold),
606 ComparisonOp::Le => simd_compare_le(values, threshold),
607 ComparisonOp::Eq => simd_compare_eq_f64(values, threshold),
608 ComparisonOp::Ne => {
609 let eq_results = simd_compare_eq_f64(values, threshold);
610 eq_results.into_iter().map(|b| !b).collect()
611 }
612 };
613
614 Ok(results)
615 }
616
617 fn scalar_compare(&self, value: f64, operator: ComparisonOp, threshold: f64) -> bool {
619 match operator {
620 ComparisonOp::Gt => value > threshold,
621 ComparisonOp::Lt => value < threshold,
622 ComparisonOp::Ge => value >= threshold,
623 ComparisonOp::Le => value <= threshold,
624 ComparisonOp::Eq => (value - threshold).abs() < f64::EPSILON,
625 ComparisonOp::Ne => (value - threshold).abs() >= f64::EPSILON,
626 }
627 }
628
629 fn term_to_numeric(&self, term: &Term) -> Option<f64> {
631 match term {
632 Term::Literal(lit) => lit.value.parse::<f64>().ok(),
633 _ => None,
634 }
635 }
636}
637
638#[derive(Debug, Clone, Copy, PartialEq, Eq)]
640pub enum ComparisonOp {
641 Gt,
642 Lt,
643 Ge,
644 Le,
645 Eq,
646 Ne,
647}
648
649pub struct SimdAggregations {
651 config: SimdConfig,
652}
653
654impl SimdAggregations {
655 pub fn new(config: SimdConfig) -> Self {
657 Self { config }
658 }
659
660 pub fn sum(&self, values: &[f64]) -> f64 {
662 if values.is_empty() {
663 return 0.0;
664 }
665
666 if values.len() < self.config.min_batch_size {
667 return values.iter().sum();
668 }
669
670 let arr = Array1::from_vec(values.to_vec());
672 arr.sum()
673 }
674
675 pub fn avg(&self, values: &[f64]) -> f64 {
677 if values.is_empty() {
678 return 0.0;
679 }
680
681 let sum = self.sum(values);
682 sum / values.len() as f64
683 }
684
685 pub fn min(&self, values: &[f64]) -> f64 {
687 if values.is_empty() {
688 return f64::NAN;
689 }
690
691 if values.len() < self.config.min_batch_size {
692 return values.iter().copied().fold(f64::INFINITY, f64::min);
693 }
694
695 values
697 .par_iter()
698 .copied()
699 .reduce(|| f64::INFINITY, |a, b| a.min(b))
700 }
701
702 pub fn max(&self, values: &[f64]) -> f64 {
704 if values.is_empty() {
705 return f64::NAN;
706 }
707
708 if values.len() < self.config.min_batch_size {
709 return values.iter().copied().fold(f64::NEG_INFINITY, f64::max);
710 }
711
712 values
714 .par_iter()
715 .copied()
716 .reduce(|| f64::NEG_INFINITY, |a, b| a.max(b))
717 }
718
719 pub fn dot_product(&self, vec1: &[f64], vec2: &[f64]) -> Result<f64> {
721 if vec1.len() != vec2.len() {
722 return Ok(0.0);
723 }
724
725 if vec1.is_empty() {
726 return Ok(0.0);
727 }
728
729 let arr1 = Array1::from_vec(vec1.to_vec());
731 let arr2 = Array1::from_vec(vec2.to_vec());
732
733 let result = (&arr1 * &arr2).sum();
735
736 Ok(result)
737 }
738
739 pub fn cosine_similarity(&self, vec1: &[f64], vec2: &[f64]) -> Result<f64> {
741 if vec1.len() != vec2.len() || vec1.is_empty() {
742 return Ok(0.0);
743 }
744
745 let dot = self.dot_product(vec1, vec2)?;
746 let norm1 = self.dot_product(vec1, vec1)?.sqrt();
747 let norm2 = self.dot_product(vec2, vec2)?.sqrt();
748
749 if norm1 == 0.0 || norm2 == 0.0 {
750 return Ok(0.0);
751 }
752
753 Ok(dot / (norm1 * norm2))
754 }
755
756 pub fn batch_matrix_multiply(
758 &self,
759 matrices_a: &[Array2<f64>],
760 matrices_b: &[Array2<f64>],
761 ) -> Result<Vec<Array2<f64>>> {
762 if matrices_a.len() != matrices_b.len() {
763 return Ok(Vec::new());
764 }
765
766 let results: Vec<Array2<f64>> = matrices_a
768 .par_iter()
769 .zip(matrices_b.par_iter())
770 .map(|(a, b)| {
771 a.dot(b)
773 })
774 .collect();
775
776 Ok(results)
777 }
778}
779
780pub struct SimdStringOps {
782 config: SimdConfig,
783}
784
785impl SimdStringOps {
786 pub fn new(config: SimdConfig) -> Self {
788 Self { config }
789 }
790
791 pub fn batch_strlen(&self, strings: &[String]) -> Vec<usize> {
793 if strings.len() < self.config.min_batch_size {
794 return strings.iter().map(|s| s.len()).collect();
795 }
796
797 strings.par_iter().map(|s| s.len()).collect()
799 }
800
801 pub fn batch_contains(&self, strings: &[String], pattern: &str) -> Vec<bool> {
803 if strings.len() < self.config.min_batch_size {
804 return strings.iter().map(|s| s.contains(pattern)).collect();
805 }
806
807 strings.par_iter().map(|s| s.contains(pattern)).collect()
809 }
810
811 pub fn batch_starts_with(&self, strings: &[String], prefix: &str) -> Vec<bool> {
813 if strings.len() < self.config.min_batch_size {
814 return strings.iter().map(|s| s.starts_with(prefix)).collect();
815 }
816
817 strings.par_iter().map(|s| s.starts_with(prefix)).collect()
819 }
820}
821
822#[cfg(test)]
823mod tests {
824 use super::*;
825 use oxirs_core::model::NamedNode;
826
827 #[test]
828 fn test_simd_triple_matcher() {
829 let config = SimdConfig::default();
830 let mut matcher = SimdTripleMatcher::new(config);
831
832 let pattern = TriplePattern {
834 subject: Term::Variable(Variable::new("s".to_string()).unwrap()),
835 predicate: Term::Iri(NamedNode::new("http://example.org/pred").unwrap()),
836 object: Term::Variable(Variable::new("o".to_string()).unwrap()),
837 };
838
839 let candidates = vec![
841 TripleCandidate::new(
842 Term::Iri(NamedNode::new("http://example.org/s1").unwrap()),
843 Term::Iri(NamedNode::new("http://example.org/pred").unwrap()),
844 Term::Iri(NamedNode::new("http://example.org/o1").unwrap()),
845 ),
846 TripleCandidate::new(
847 Term::Iri(NamedNode::new("http://example.org/s2").unwrap()),
848 Term::Iri(NamedNode::new("http://example.org/other").unwrap()),
849 Term::Iri(NamedNode::new("http://example.org/o2").unwrap()),
850 ),
851 ];
852
853 let matches = matcher.match_pattern(&pattern, &candidates).unwrap();
854 assert_eq!(matches.len(), 1); }
856
857 #[test]
858 fn test_simd_hash_join() {
859 let config = SimdConfig::default();
860 let mut join = SimdHashJoin::new(config);
861
862 let var_x = Variable::new("x".to_string()).unwrap();
863 let var_y = Variable::new("y".to_string()).unwrap();
864
865 let left = vec![{
867 let mut b = Binding::new();
868 b.insert(
869 var_x.clone(),
870 Term::Iri(NamedNode::new("http://example.org/1").unwrap()),
871 );
872 b.insert(
873 var_y.clone(),
874 Term::Iri(NamedNode::new("http://example.org/a").unwrap()),
875 );
876 b
877 }];
878
879 let right = vec![{
880 let mut b = Binding::new();
881 b.insert(
882 var_x.clone(),
883 Term::Iri(NamedNode::new("http://example.org/1").unwrap()),
884 );
885 b
886 }];
887
888 let join_vars = vec![var_x];
889 let _result = join.join(&left, &right, &join_vars).unwrap();
890
891 let stats = join.get_stats();
893 assert!(stats.simd_batches > 0 || stats.scalar_ops > 0);
894 }
895
896 #[test]
897 fn test_simd_filter_evaluator() {
898 let config = SimdConfig::default();
899 let evaluator = SimdFilterEvaluator::new(config);
900
901 let var_x = Variable::new("x".to_string()).unwrap();
902
903 let bindings: Vec<Binding> = (1..=20)
905 .map(|i| {
906 let mut b = Binding::new();
907 b.insert(
908 var_x.clone(),
909 Term::Literal(crate::algebra::Literal {
910 value: i.to_string(),
911 language: None,
912 datatype: None,
913 }),
914 );
915 b
916 })
917 .collect();
918
919 let results = evaluator
921 .evaluate_numeric_filter(&bindings, &var_x, ComparisonOp::Gt, 10.0)
922 .unwrap();
923
924 let true_count = results.iter().filter(|&&b| b).count();
926 assert_eq!(true_count, 10);
927 }
928
929 #[test]
930 fn test_simd_aggregations() {
931 let config = SimdConfig::default();
932 let agg = SimdAggregations::new(config);
933
934 let values: Vec<f64> = (1..=100).map(|i| i as f64).collect();
935
936 let sum = agg.sum(&values);
938 assert_eq!(sum, 5050.0); let avg = agg.avg(&values);
942 assert_eq!(avg, 50.5); let min = agg.min(&values);
946 assert_eq!(min, 1.0);
947
948 let max = agg.max(&values);
950 assert_eq!(max, 100.0);
951 }
952
953 #[test]
954 fn test_simd_dot_product() {
955 let config = SimdConfig::default();
956 let agg = SimdAggregations::new(config);
957
958 let vec1 = vec![1.0, 2.0, 3.0, 4.0];
959 let vec2 = vec![5.0, 6.0, 7.0, 8.0];
960
961 let dot = agg.dot_product(&vec1, &vec2).unwrap();
963 assert!((dot - 70.0).abs() < 1e-10);
964 }
965
966 #[test]
967 fn test_simd_cosine_similarity() {
968 let config = SimdConfig::default();
969 let agg = SimdAggregations::new(config);
970
971 let vec1 = vec![1.0, 2.0, 3.0];
973 let similarity = agg.cosine_similarity(&vec1, &vec1).unwrap();
974 assert!((similarity - 1.0).abs() < 1e-10);
975
976 let vec2 = vec![1.0, 0.0];
978 let vec3 = vec![0.0, 1.0];
979 let similarity2 = agg.cosine_similarity(&vec2, &vec3).unwrap();
980 assert!(similarity2.abs() < 1e-10);
981 }
982
983 #[test]
984 fn test_simd_string_ops() {
985 let config = SimdConfig::default();
986 let string_ops = SimdStringOps::new(config);
987
988 let strings: Vec<String> = (0..20).map(|i| format!("test_string_{}", i)).collect();
989
990 let lengths = string_ops.batch_strlen(&strings);
992 assert_eq!(lengths.len(), 20);
993 assert!(lengths.iter().all(|&l| l > 0));
994
995 let contains = string_ops.batch_contains(&strings, "test");
997 assert_eq!(contains.len(), 20);
998 assert!(contains.iter().all(|&b| b)); let starts = string_ops.batch_starts_with(&strings, "test_");
1002 assert_eq!(starts.len(), 20);
1003 assert!(starts.iter().all(|&b| b)); }
1005}