1use crate::executor::expression::functions::signature::{FunctionSignature, ValueType};
9use crate::executor::expression::{ExpressionError, ExpressionErrorType};
10use linkrs_core::Value;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub enum VectorFunction {
15 CosineSimilarity,
17 DotProduct,
19 EuclideanDistance,
21 ManhattanDistance,
23 Dimension,
25 L2Norm,
27 Nnz,
29 Normalize,
31 ArrayValue,
33 ArrayCosineSimilarity,
35 ArrayDistance,
37 ArraySquaredDistance,
39 ArrayInnerProduct,
41 ArrayDotProduct,
43}
44
45impl VectorFunction {
46 pub fn name(&self) -> &'static str {
48 match self {
49 VectorFunction::CosineSimilarity => "cosine_similarity",
50 VectorFunction::DotProduct => "dot_product",
51 VectorFunction::EuclideanDistance => "euclidean_distance",
52 VectorFunction::ManhattanDistance => "manhattan_distance",
53 VectorFunction::Dimension => "dimension",
54 VectorFunction::L2Norm => "l2_norm",
55 VectorFunction::Nnz => "nnz",
56 VectorFunction::Normalize => "normalize",
57 VectorFunction::ArrayValue => "array_value",
58 VectorFunction::ArrayCosineSimilarity => "array_cosine_similarity",
59 VectorFunction::ArrayDistance => "array_distance",
60 VectorFunction::ArraySquaredDistance => "array_squared_distance",
61 VectorFunction::ArrayInnerProduct => "array_inner_product",
62 VectorFunction::ArrayDotProduct => "array_dot_product",
63 }
64 }
65
66 pub fn signature(&self) -> FunctionSignature {
68 match self {
69 VectorFunction::CosineSimilarity => FunctionSignature::new(
70 "cosine_similarity",
71 vec![ValueType::Any, ValueType::Any],
72 Some(ValueType::Float),
73 false,
74 ),
75 VectorFunction::DotProduct => FunctionSignature::new(
76 "dot_product",
77 vec![ValueType::Any, ValueType::Any],
78 Some(ValueType::Float),
79 false,
80 ),
81 VectorFunction::EuclideanDistance => FunctionSignature::new(
82 "euclidean_distance",
83 vec![ValueType::Any, ValueType::Any],
84 Some(ValueType::Float),
85 false,
86 ),
87 VectorFunction::ManhattanDistance => FunctionSignature::new(
88 "manhattan_distance",
89 vec![ValueType::Any, ValueType::Any],
90 Some(ValueType::Float),
91 false,
92 ),
93 VectorFunction::Dimension => FunctionSignature::new(
94 "dimension",
95 vec![ValueType::Any],
96 Some(ValueType::Int),
97 false,
98 ),
99 VectorFunction::L2Norm => FunctionSignature::new(
100 "l2_norm",
101 vec![ValueType::Any],
102 Some(ValueType::Float),
103 false,
104 ),
105 VectorFunction::Nnz => {
106 FunctionSignature::new("nnz", vec![ValueType::Any], Some(ValueType::Int), false)
107 }
108 VectorFunction::Normalize => FunctionSignature::new(
109 "normalize",
110 vec![ValueType::Any],
111 Some(ValueType::Any),
112 false,
113 ),
114 VectorFunction::ArrayValue => {
115 FunctionSignature::new("array_value", vec![], Some(ValueType::Any), true)
116 }
117 VectorFunction::ArrayCosineSimilarity => FunctionSignature::new(
118 "array_cosine_similarity",
119 vec![ValueType::Any, ValueType::Any],
120 Some(ValueType::Float),
121 false,
122 ),
123 VectorFunction::ArrayDistance => FunctionSignature::new(
124 "array_distance",
125 vec![ValueType::Any, ValueType::Any],
126 Some(ValueType::Float),
127 false,
128 ),
129 VectorFunction::ArraySquaredDistance => FunctionSignature::new(
130 "array_squared_distance",
131 vec![ValueType::Any, ValueType::Any],
132 Some(ValueType::Float),
133 false,
134 ),
135 VectorFunction::ArrayInnerProduct => FunctionSignature::new(
136 "array_inner_product",
137 vec![ValueType::Any, ValueType::Any],
138 Some(ValueType::Float),
139 false,
140 ),
141 VectorFunction::ArrayDotProduct => FunctionSignature::new(
142 "array_dot_product",
143 vec![ValueType::Any, ValueType::Any],
144 Some(ValueType::Float),
145 false,
146 ),
147 }
148 }
149
150 pub fn arity(&self) -> usize {
152 match self {
153 VectorFunction::CosineSimilarity => 2,
154 VectorFunction::DotProduct => 2,
155 VectorFunction::EuclideanDistance => 2,
156 VectorFunction::ManhattanDistance => 2,
157 VectorFunction::Dimension => 1,
158 VectorFunction::L2Norm => 1,
159 VectorFunction::Nnz => 1,
160 VectorFunction::Normalize => 1,
161 VectorFunction::ArrayValue => 0,
162 VectorFunction::ArrayCosineSimilarity => 2,
163 VectorFunction::ArrayDistance => 2,
164 VectorFunction::ArraySquaredDistance => 2,
165 VectorFunction::ArrayInnerProduct => 2,
166 VectorFunction::ArrayDotProduct => 2,
167 }
168 }
169
170 pub fn is_variadic(&self) -> bool {
172 matches!(self, VectorFunction::ArrayValue)
173 }
174
175 pub fn description(&self) -> &'static str {
177 match self {
178 VectorFunction::CosineSimilarity => "Compute cosine similarity between two vectors",
179 VectorFunction::DotProduct => "Compute dot product of two vectors",
180 VectorFunction::EuclideanDistance => "Compute Euclidean distance between two vectors",
181 VectorFunction::ManhattanDistance => "Compute Manhattan distance between two vectors",
182 VectorFunction::Dimension => "Get the dimension of a vector",
183 VectorFunction::L2Norm => "Compute L2 norm of a vector",
184 VectorFunction::Nnz => "Get number of non-zero elements in a vector",
185 VectorFunction::Normalize => "Normalize a vector to unit length",
186 VectorFunction::ArrayValue => "Create an array from values",
187 VectorFunction::ArrayCosineSimilarity => "Compute cosine similarity between two arrays",
188 VectorFunction::ArrayDistance => "Compute Euclidean distance between two arrays",
189 VectorFunction::ArraySquaredDistance => {
190 "Compute squared Euclidean distance between two arrays"
191 }
192 VectorFunction::ArrayInnerProduct => "Compute inner product of two arrays",
193 VectorFunction::ArrayDotProduct => "Compute dot product of two arrays",
194 }
195 }
196
197 pub fn execute(&self, args: &[Value]) -> Result<Value, ExpressionError> {
199 if !self.is_variadic() && args.len() != self.arity() {
200 return Err(ExpressionError::invalid_arity(
201 self.name(),
202 self.arity(),
203 args.len(),
204 ));
205 }
206 match self {
207 VectorFunction::CosineSimilarity => self.execute_cosine_similarity(args),
208 VectorFunction::DotProduct => self.execute_dot_product(args),
209 VectorFunction::EuclideanDistance => self.execute_euclidean_distance(args),
210 VectorFunction::ManhattanDistance => self.execute_manhattan_distance(args),
211 VectorFunction::Dimension => self.execute_dimension(args),
212 VectorFunction::L2Norm => self.execute_l2_norm(args),
213 VectorFunction::Nnz => self.execute_nnz(args),
214 VectorFunction::Normalize => self.execute_normalize(args),
215 VectorFunction::ArrayValue => self.execute_array_value(args),
216 VectorFunction::ArrayCosineSimilarity => self.execute_cosine_similarity(args),
217 VectorFunction::ArrayDistance => self.execute_euclidean_distance(args),
218 VectorFunction::ArraySquaredDistance => self.execute_array_squared_distance(args),
219 VectorFunction::ArrayInnerProduct => self.execute_dot_product(args),
220 VectorFunction::ArrayDotProduct => self.execute_dot_product(args),
221 }
222 }
223
224 fn execute_cosine_similarity(&self, args: &[Value]) -> Result<Value, ExpressionError> {
226 let vec1 = extract_vector(&args[0])?;
227 let vec2 = extract_vector(&args[1])?;
228
229 let similarity = compute_cosine_similarity(&vec1, &vec2)?;
230 Ok(Value::Double(similarity as f64))
231 }
232
233 fn execute_dot_product(&self, args: &[Value]) -> Result<Value, ExpressionError> {
235 let vec1 = extract_vector(&args[0])?;
236 let vec2 = extract_vector(&args[1])?;
237
238 let dot = compute_dot_product(&vec1, &vec2)?;
239 Ok(Value::Double(dot as f64))
240 }
241
242 fn execute_euclidean_distance(&self, args: &[Value]) -> Result<Value, ExpressionError> {
244 let vec1 = extract_vector(&args[0])?;
245 let vec2 = extract_vector(&args[1])?;
246
247 let distance = compute_euclidean_distance(&vec1, &vec2)?;
248 Ok(Value::Double(distance as f64))
249 }
250
251 fn execute_manhattan_distance(&self, args: &[Value]) -> Result<Value, ExpressionError> {
253 let vec1 = extract_vector(&args[0])?;
254 let vec2 = extract_vector(&args[1])?;
255
256 let distance = compute_manhattan_distance(&vec1, &vec2)?;
257 Ok(Value::Double(distance as f64))
258 }
259
260 fn execute_dimension(&self, args: &[Value]) -> Result<Value, ExpressionError> {
262 let vec = extract_vector(&args[0])?;
263 Ok(Value::BigInt(vec.len() as i64))
264 }
265
266 fn execute_l2_norm(&self, args: &[Value]) -> Result<Value, ExpressionError> {
268 let vec = extract_vector(&args[0])?;
269 let norm = compute_l2_norm(&vec);
270 Ok(Value::Double(norm as f64))
271 }
272
273 fn execute_nnz(&self, args: &[Value]) -> Result<Value, ExpressionError> {
275 let vec = extract_vector(&args[0])?;
276 let nnz = vec.iter().filter(|&&x| x != 0.0).count();
277 Ok(Value::BigInt(nnz as i64))
278 }
279
280 fn execute_normalize(&self, args: &[Value]) -> Result<Value, ExpressionError> {
282 let vec = extract_vector(&args[0])?;
283 let norm = compute_l2_norm(&vec);
284
285 if norm == 0.0 {
286 return Ok(Value::vector(vec)); }
288
289 let normalized: Vec<f32> = vec.iter().map(|&x| x / norm).collect();
290 Ok(Value::vector(normalized))
291 }
292
293 fn execute_array_value(&self, args: &[Value]) -> Result<Value, ExpressionError> {
295 let values: Vec<Value> = args.to_vec();
296 Ok(Value::list(linkrs_core::value::list::List { values }))
297 }
298
299 fn execute_array_squared_distance(&self, args: &[Value]) -> Result<Value, ExpressionError> {
301 let vec1 = extract_vector(&args[0])?;
302 let vec2 = extract_vector(&args[1])?;
303
304 if vec1.len() != vec2.len() {
305 return Err(ExpressionError::new(
306 ExpressionErrorType::TypeError,
307 format!(
308 "Array dimensions must match: {} vs {}",
309 vec1.len(),
310 vec2.len()
311 ),
312 ));
313 }
314
315 let sum: f32 = vec1
316 .iter()
317 .zip(vec2.iter())
318 .map(|(&a, &b)| (a - b).powi(2))
319 .sum();
320 Ok(Value::Double(sum as f64))
321 }
322}
323
324fn extract_vector(value: &Value) -> Result<Vec<f32>, ExpressionError> {
326 match value {
327 Value::Vector(vec) => Ok(vec.to_dense()),
328 Value::List(list) => {
329 let mut vec = Vec::with_capacity(list.values.len());
331 for item in &list.values {
332 match item {
333 Value::Float(f) => vec.push(*f),
334 Value::Double(f) => vec.push(*f as f32),
335 Value::SmallInt(i) => vec.push(*i as f32),
336 Value::Int(i) => vec.push(*i as f32),
337 Value::BigInt(i) => vec.push(*i as f32),
338 _ => {
339 return Err(ExpressionError::new(
340 ExpressionErrorType::TypeError,
341 format!("Vector elements must be numeric, got {:?}", item.get_type()),
342 ))
343 }
344 }
345 }
346 Ok(vec)
347 }
348 _ => Err(ExpressionError::new(
349 ExpressionErrorType::TypeError,
350 format!(
351 "Expected vector or list of numbers, got {:?}",
352 value.get_type()
353 ),
354 )),
355 }
356}
357
358fn compute_cosine_similarity(vec1: &[f32], vec2: &[f32]) -> Result<f32, ExpressionError> {
360 if vec1.len() != vec2.len() {
361 return Err(ExpressionError::new(
362 ExpressionErrorType::TypeError,
363 format!(
364 "Vector dimensions must match: {} vs {}",
365 vec1.len(),
366 vec2.len()
367 ),
368 ));
369 }
370
371 let dot_product: f32 = vec1.iter().zip(vec2.iter()).map(|(&a, &b)| a * b).sum();
372 let norm1: f32 = vec1.iter().map(|&x| x * x).sum::<f32>().sqrt();
373 let norm2: f32 = vec2.iter().map(|&x| x * x).sum::<f32>().sqrt();
374
375 if norm1 == 0.0 || norm2 == 0.0 {
376 return Ok(0.0);
377 }
378
379 Ok(dot_product / (norm1 * norm2))
380}
381
382fn compute_dot_product(vec1: &[f32], vec2: &[f32]) -> Result<f32, ExpressionError> {
384 if vec1.len() != vec2.len() {
385 return Err(ExpressionError::new(
386 ExpressionErrorType::TypeError,
387 format!(
388 "Vector dimensions must match: {} vs {}",
389 vec1.len(),
390 vec2.len()
391 ),
392 ));
393 }
394
395 Ok(vec1.iter().zip(vec2.iter()).map(|(&a, &b)| a * b).sum())
396}
397
398fn compute_euclidean_distance(vec1: &[f32], vec2: &[f32]) -> Result<f32, ExpressionError> {
400 if vec1.len() != vec2.len() {
401 return Err(ExpressionError::new(
402 ExpressionErrorType::TypeError,
403 format!(
404 "Vector dimensions must match: {} vs {}",
405 vec1.len(),
406 vec2.len()
407 ),
408 ));
409 }
410
411 let sum: f32 = vec1
412 .iter()
413 .zip(vec2.iter())
414 .map(|(&a, &b)| (a - b).powi(2))
415 .sum();
416 Ok(sum.sqrt())
417}
418
419fn compute_manhattan_distance(vec1: &[f32], vec2: &[f32]) -> Result<f32, ExpressionError> {
421 if vec1.len() != vec2.len() {
422 return Err(ExpressionError::new(
423 ExpressionErrorType::TypeError,
424 format!(
425 "Vector dimensions must match: {} vs {}",
426 vec1.len(),
427 vec2.len()
428 ),
429 ));
430 }
431
432 Ok(vec1
433 .iter()
434 .zip(vec2.iter())
435 .map(|(&a, &b)| (a - b).abs())
436 .sum())
437}
438
439fn compute_l2_norm(vec: &[f32]) -> f32 {
441 vec.iter().map(|&x| x * x).sum::<f32>().sqrt()
442}
443
444pub fn register_vector_functions(
446 registry: &mut crate::executor::expression::functions::FunctionRegistry,
447) {
448 registry.register_builtin(
449 crate::executor::expression::functions::BuiltinFunction::Vector(
450 VectorFunction::CosineSimilarity,
451 ),
452 );
453
454 registry.register_builtin(
455 crate::executor::expression::functions::BuiltinFunction::Vector(VectorFunction::DotProduct),
456 );
457
458 registry.register_builtin(
459 crate::executor::expression::functions::BuiltinFunction::Vector(
460 VectorFunction::EuclideanDistance,
461 ),
462 );
463
464 registry.register_builtin(
465 crate::executor::expression::functions::BuiltinFunction::Vector(
466 VectorFunction::ManhattanDistance,
467 ),
468 );
469
470 registry.register_builtin(
471 crate::executor::expression::functions::BuiltinFunction::Vector(VectorFunction::Dimension),
472 );
473
474 registry.register_builtin(
475 crate::executor::expression::functions::BuiltinFunction::Vector(VectorFunction::L2Norm),
476 );
477
478 registry.register_builtin(
479 crate::executor::expression::functions::BuiltinFunction::Vector(VectorFunction::Nnz),
480 );
481
482 registry.register_builtin(
483 crate::executor::expression::functions::BuiltinFunction::Vector(VectorFunction::Normalize),
484 );
485
486 registry.register_builtin(
487 crate::executor::expression::functions::BuiltinFunction::Vector(VectorFunction::ArrayValue),
488 );
489
490 registry.register_builtin(
491 crate::executor::expression::functions::BuiltinFunction::Vector(
492 VectorFunction::ArrayCosineSimilarity,
493 ),
494 );
495
496 registry.register_builtin(
497 crate::executor::expression::functions::BuiltinFunction::Vector(
498 VectorFunction::ArrayDistance,
499 ),
500 );
501
502 registry.register_builtin(
503 crate::executor::expression::functions::BuiltinFunction::Vector(
504 VectorFunction::ArraySquaredDistance,
505 ),
506 );
507
508 registry.register_builtin(
509 crate::executor::expression::functions::BuiltinFunction::Vector(
510 VectorFunction::ArrayInnerProduct,
511 ),
512 );
513
514 registry.register_builtin(
515 crate::executor::expression::functions::BuiltinFunction::Vector(
516 VectorFunction::ArrayDotProduct,
517 ),
518 );
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524 use linkrs_core::value::list::List;
525
526 #[test]
527 fn test_cosine_similarity() {
528 let vec1 = Value::vector(vec![1.0, 0.0, 0.0]);
529 let vec2 = Value::vector(vec![0.0, 1.0, 0.0]);
530
531 let result = VectorFunction::CosineSimilarity
532 .execute(&[vec1, vec2])
533 .unwrap();
534
535 assert!(matches!(result, Value::Double(_)));
536 if let Value::Double(score) = result {
537 assert!((score - 0.0).abs() < 1e-6);
538 }
539 }
540
541 #[test]
542 fn test_dot_product() {
543 let vec1 = Value::vector(vec![1.0, 2.0, 3.0]);
544 let vec2 = Value::vector(vec![4.0, 5.0, 6.0]);
545
546 let result = VectorFunction::DotProduct.execute(&[vec1, vec2]).unwrap();
547
548 assert!(matches!(result, Value::Double(_)));
549 if let Value::Double(dot) = result {
550 assert!((dot - 32.0).abs() < 1e-6);
551 }
552 }
553
554 #[test]
555 fn test_euclidean_distance() {
556 let vec1 = Value::vector(vec![0.0, 0.0]);
557 let vec2 = Value::vector(vec![3.0, 4.0]);
558
559 let result = VectorFunction::EuclideanDistance
560 .execute(&[vec1, vec2])
561 .unwrap();
562
563 assert!(matches!(result, Value::Double(_)));
564 if let Value::Double(dist) = result {
565 assert!((dist - 5.0).abs() < 1e-6);
566 }
567 }
568
569 #[test]
570 fn test_manhattan_distance() {
571 let vec1 = Value::vector(vec![0.0, 0.0]);
572 let vec2 = Value::vector(vec![3.0, 4.0]);
573
574 let result = VectorFunction::ManhattanDistance
575 .execute(&[vec1, vec2])
576 .unwrap();
577
578 assert!(matches!(result, Value::Double(_)));
579 if let Value::Double(dist) = result {
580 assert!((dist - 7.0).abs() < 1e-6);
581 }
582 }
583
584 #[test]
585 fn test_dimension() {
586 let vec = Value::vector(vec![0.1; 1536]);
587
588 let result = VectorFunction::Dimension.execute(&[vec]).unwrap();
589
590 assert!(matches!(result, Value::BigInt(_)));
591 if let Value::BigInt(dim) = result {
592 assert_eq!(dim, 1536);
593 }
594 }
595
596 #[test]
597 fn test_l2_norm() {
598 let vec = Value::vector(vec![3.0, 4.0]);
599
600 let result = VectorFunction::L2Norm.execute(&[vec]).unwrap();
601
602 assert!(matches!(result, Value::Double(_)));
603 if let Value::Double(norm) = result {
604 assert!((norm - 5.0).abs() < 1e-6);
605 }
606 }
607
608 #[test]
609 fn test_nnz() {
610 let vec = Value::vector(vec![1.0, 0.0, 2.0, 0.0, 3.0]);
611
612 let result = VectorFunction::Nnz.execute(&[vec]).unwrap();
613
614 assert!(matches!(result, Value::BigInt(_)));
615 if let Value::BigInt(n) = result {
616 assert_eq!(n, 3);
617 }
618 }
619
620 #[test]
621 fn test_normalize() {
622 let vec = Value::vector(vec![3.0, 4.0]);
623
624 let result = VectorFunction::Normalize.execute(&[vec]).unwrap();
625
626 assert!(matches!(result, Value::Vector(_)));
627 if let Value::Vector(normalized) = result {
628 let data = normalized.to_dense();
629 assert!((data[0] - 0.6).abs() < 1e-6); assert!((data[1] - 0.8).abs() < 1e-6); }
632 }
633
634 #[test]
635 fn test_dimension_mismatch() {
636 let vec1 = Value::vector(vec![1.0, 2.0]);
637 let vec2 = Value::vector(vec![1.0, 2.0, 3.0]);
638
639 let result = VectorFunction::CosineSimilarity.execute(&[vec1, vec2]);
640 assert!(result.is_err());
641 }
642
643 #[test]
644 fn test_array_value() {
645 let result = VectorFunction::ArrayValue
646 .execute(&[Value::Int(1), Value::Int(2), Value::Int(3)])
647 .unwrap();
648 assert!(matches!(result, Value::List(_)));
649 }
650
651 #[test]
652 fn test_array_cosine_similarity() {
653 let arr1 = Value::list(List {
654 values: vec![Value::Float(1.0), Value::Float(0.0), Value::Float(0.0)],
655 });
656 let arr2 = Value::list(List {
657 values: vec![Value::Float(0.0), Value::Float(1.0), Value::Float(0.0)],
658 });
659
660 let result = VectorFunction::ArrayCosineSimilarity
661 .execute(&[arr1, arr2])
662 .unwrap();
663
664 assert!(matches!(result, Value::Double(_)));
665 if let Value::Double(score) = result {
666 assert!((score - 0.0).abs() < 1e-6);
667 }
668 }
669
670 #[test]
671 fn test_array_distance() {
672 let arr1 = Value::list(List {
673 values: vec![Value::Float(0.0), Value::Float(0.0)],
674 });
675 let arr2 = Value::list(List {
676 values: vec![Value::Float(3.0), Value::Float(4.0)],
677 });
678
679 let result = VectorFunction::ArrayDistance
680 .execute(&[arr1, arr2])
681 .unwrap();
682
683 assert!(matches!(result, Value::Double(_)));
684 if let Value::Double(dist) = result {
685 assert!((dist - 5.0).abs() < 1e-6);
686 }
687 }
688
689 #[test]
690 fn test_array_squared_distance() {
691 let arr1 = Value::list(List {
692 values: vec![Value::Float(0.0), Value::Float(0.0)],
693 });
694 let arr2 = Value::list(List {
695 values: vec![Value::Float(3.0), Value::Float(4.0)],
696 });
697
698 let result = VectorFunction::ArraySquaredDistance
699 .execute(&[arr1, arr2])
700 .unwrap();
701
702 assert!(matches!(result, Value::Double(_)));
703 if let Value::Double(dist) = result {
704 assert!((dist - 25.0).abs() < 1e-6);
705 }
706 }
707
708 #[test]
709 fn test_array_inner_product() {
710 let arr1 = Value::list(List {
711 values: vec![Value::Float(1.0), Value::Float(2.0), Value::Float(3.0)],
712 });
713 let arr2 = Value::list(List {
714 values: vec![Value::Float(4.0), Value::Float(5.0), Value::Float(6.0)],
715 });
716
717 let result = VectorFunction::ArrayInnerProduct
718 .execute(&[arr1, arr2])
719 .unwrap();
720
721 assert!(matches!(result, Value::Double(_)));
722 if let Value::Double(dot) = result {
723 assert!((dot - 32.0).abs() < 1e-6);
724 }
725 }
726
727 #[test]
728 fn test_array_dot_product() {
729 let arr1 = Value::list(List {
730 values: vec![Value::Float(1.0), Value::Float(2.0), Value::Float(3.0)],
731 });
732 let arr2 = Value::list(List {
733 values: vec![Value::Float(4.0), Value::Float(5.0), Value::Float(6.0)],
734 });
735
736 let result = VectorFunction::ArrayDotProduct
737 .execute(&[arr1, arr2])
738 .unwrap();
739
740 assert!(matches!(result, Value::Double(_)));
741 if let Value::Double(dot) = result {
742 assert!((dot - 32.0).abs() < 1e-6);
743 }
744 }
745}