1010
1111The tests cover:
1212
13- - **Dense Vector Operations**: Tests for `float64`, `float32`, `float16` data types using metrics like `inner`, `sqeuclidean`, and `cosine `.
13+ - **Dense Vector Operations**: Tests for `float64`, `float32`, `float16` data types using metrics like `inner`, `sqeuclidean`, and `angular `.
1414- **Brain Floating-Point Format (bfloat16)**: Tests for operations with the brain floating-point format not natively supported by NumPy.
1515- **Integer Operations**: Tests for `int8` data type, ensuring accuracy without overflow.
1616- **Bitwise Operations**: Tests for Hamming and Jaccard distances using bit arrays.
@@ -243,6 +243,14 @@ def is_running_under_qemu():
243243 return "SIMSIMD_IN_QEMU" in os .environ
244244
245245
246+ def scipy_metric_name (metric : str ) -> str :
247+ """Convert SimSIMD metric names to SciPy equivalents."""
248+ # SimSIMD uses 'angular' while SciPy uses 'cosine' for the same metric
249+ if metric == "angular" :
250+ return "cosine"
251+ return metric
252+
253+
246254def profile (callable , * args , ** kwargs ) -> tuple :
247255 before = time .perf_counter_ns ()
248256 result = callable (* args , ** kwargs )
@@ -450,7 +458,7 @@ def get_current_test():
450458 full_name = os .environ .get ("PYTEST_CURRENT_TEST" ).split (" " )[0 ]
451459 test_file = full_name .split ("::" )[0 ].split ("/" )[- 1 ].split (".py" )[0 ]
452460 test_name = full_name .split ("::" )[1 ]
453- # The `test_name` may look like: "test_dense_i8[cosine -1536-24-50]"
461+ # The `test_name` may look like: "test_dense_i8[angular -1536-24-50]"
454462 function_name = test_name .split ("[" )[0 ]
455463 return test_file , test_name , function_name
456464
@@ -586,23 +594,23 @@ def hex_array(arr):
586594def test_pointers_availability ():
587595 """Tests the availability of pre-compiled functions for compatibility with USearch."""
588596 assert simd .pointer_to_sqeuclidean ("float64" ) != 0
589- assert simd .pointer_to_cosine ("float64" ) != 0
597+ assert simd .pointer_to_angular ("float64" ) != 0
590598 assert simd .pointer_to_inner ("float64" ) != 0
591599
592600 assert simd .pointer_to_sqeuclidean ("float32" ) != 0
593- assert simd .pointer_to_cosine ("float32" ) != 0
601+ assert simd .pointer_to_angular ("float32" ) != 0
594602 assert simd .pointer_to_inner ("float32" ) != 0
595603
596604 assert simd .pointer_to_sqeuclidean ("float16" ) != 0
597- assert simd .pointer_to_cosine ("float16" ) != 0
605+ assert simd .pointer_to_angular ("float16" ) != 0
598606 assert simd .pointer_to_inner ("float16" ) != 0
599607
600608 assert simd .pointer_to_sqeuclidean ("int8" ) != 0
601- assert simd .pointer_to_cosine ("int8" ) != 0
609+ assert simd .pointer_to_angular ("int8" ) != 0
602610 assert simd .pointer_to_inner ("int8" ) != 0
603611
604612 assert simd .pointer_to_sqeuclidean ("uint8" ) != 0
605- assert simd .pointer_to_cosine ("uint8" ) != 0
613+ assert simd .pointer_to_angular ("uint8" ) != 0
606614 assert simd .pointer_to_inner ("uint8" ) != 0
607615
608616
@@ -684,7 +692,7 @@ def random_of_dtype(dtype, shape):
684692 (simd .cdist , TypeError , (to_array ([[1.0 ]]), to_array ([[1.0 ]]), "l2" ), {"metric" : "l2" }),
685693 # Applying real metric to complex numbers - missing kernel
686694 (simd .angular , LookupError , (to_array ([1 + 2j ]), to_array ([1 + 2j ])), {}),
687- # Test incompatible vectors for cosine
695+ # Test incompatible vectors for angular
688696 (simd .angular , ValueError , (to_array ([1.0 ]), to_array ([1.0 , 2.0 ])), {}), # Different number of dimensions
689697 (simd .angular , TypeError , (to_array ([1.0 ]), to_array ([1 ], "int8" )), {}), # Floats and integers
690698 (simd .angular , TypeError , (to_array ([1 ], "float32" ), to_array ([1 ], "float16" )), {}), # Different floats
@@ -1024,7 +1032,7 @@ def test_jensen_shannon(ndim, dtype, capability, stats_fixture):
10241032@pytest .mark .parametrize ("ndim" , [11 , 97 , 1536 ])
10251033@pytest .mark .parametrize ("dtype" , ["float32" , "float16" ])
10261034@pytest .mark .parametrize ("capability" , possible_capabilities )
1027- def test_cosine_zero_vector (ndim , dtype , capability ):
1035+ def test_angular_zero_vector (ndim , dtype , capability ):
10281036 """Tests the simd.angular() function with zero vectors, to catch division by zero errors."""
10291037 a = np .zeros (ndim , dtype = dtype )
10301038 b = (np .random .randn (ndim ) + 1 ).astype (dtype )
@@ -1039,8 +1047,8 @@ def test_cosine_zero_vector(ndim, dtype, capability):
10391047 result = simd .angular (b , b )
10401048 assert abs (result ) < SIMSIMD_ATOL , f"Expected 0 distance from itself, but got { result } "
10411049
1042- # For the cosine , the output must not be negative!
1043- assert np .all (result >= 0 ), f"Negative result for cosine distance"
1050+ # For the angular distance , the output must not be negative!
1051+ assert np .all (result >= 0 ), f"Negative result for angular distance"
10441052
10451053
10461054@pytest .mark .skip (reason = "Lacks overflow protection: https://github.com/ashvardanian/SimSIMD/issues/206" ) # TODO
@@ -1508,10 +1516,10 @@ def test_batch(ndim, dtype, capability):
15081516@pytest .mark .parametrize ("ndim" , [11 , 97 , 1536 ])
15091517@pytest .mark .parametrize ("input_dtype" , ["float32" , "float16" ])
15101518@pytest .mark .parametrize ("out_dtype" , [None , "float32" , "int32" ])
1511- @pytest .mark .parametrize ("metric" , ["cosine " , "sqeuclidean" ])
1519+ @pytest .mark .parametrize ("metric" , ["angular " , "sqeuclidean" ])
15121520@pytest .mark .parametrize ("capability" , possible_capabilities )
15131521def test_cdist (ndim , input_dtype , out_dtype , metric , capability ):
1514- """Compares the simd.cdist() function with scipy.spatial.distance.cdist(), measuring the accuracy error for f16, and f32 types using sqeuclidean and cosine metrics."""
1522+ """Compares the simd.cdist() function with scipy.spatial.distance.cdist(), measuring the accuracy error for f16, and f32 types using sqeuclidean and angular metrics."""
15151523
15161524 if input_dtype == "float16" and is_running_under_qemu ():
15171525 pytest .skip ("Testing low-precision math isn't reliable in QEMU" )
@@ -1529,27 +1537,28 @@ def test_cdist(ndim, input_dtype, out_dtype, metric, capability):
15291537
15301538 # Check if we need to round before casting to integer (to match SimSIMD's lround behavior)
15311539 is_integer_output = out_dtype in ("int32" , "int64" , "int16" , "int8" , "uint32" , "uint64" , "uint16" , "uint8" )
1540+ scipy_metric = scipy_metric_name (metric )
15321541
15331542 if out_dtype is None :
1534- expected = spd .cdist (A , B , metric )
1543+ expected = spd .cdist (A , B , scipy_metric )
15351544 result = simd .cdist (A , B , metric )
15361545 #! Same functions can be used in-place, but SciPy doesn't support misaligned outputs
15371546 expected_out = np .zeros ((M , N ))
15381547 result_out_extended = np .zeros ((M , N + 7 ))
15391548 result_out = result_out_extended [:, :N ]
1540- assert spd .cdist (A , B , metric , out = expected_out ) is not None
1549+ assert spd .cdist (A , B , scipy_metric , out = expected_out ) is not None
15411550 assert simd .cdist (A , B , metric , out = result_out ) is None
15421551 else :
15431552 #! SimSIMD rounds to the nearest integer before casting
1544- scipy_result = spd .cdist (A , B , metric )
1553+ scipy_result = spd .cdist (A , B , scipy_metric )
15451554 expected = np .round (scipy_result ).astype (out_dtype ) if is_integer_output else scipy_result .astype (out_dtype )
15461555 result = simd .cdist (A , B , metric , out_dtype = out_dtype )
15471556
15481557 #! Same functions can be used in-place, but SciPy doesn't support misaligned outputs
15491558 expected_out = np .zeros ((M , N ), dtype = np .float64 )
15501559 result_out_extended = np .zeros ((M , N + 7 ), dtype = out_dtype )
15511560 result_out = result_out_extended [:, :N ]
1552- assert spd .cdist (A , B , metric , out = expected_out ) is not None
1561+ assert spd .cdist (A , B , scipy_metric , out = expected_out ) is not None
15531562 assert simd .cdist (A , B , metric , out = result_out ) is None
15541563 #! Moreover, SciPy supports only double-precision outputs, so we need to downcast afterwards.
15551564 expected_out = np .round (expected_out ).astype (out_dtype ) if is_integer_output else expected_out .astype (out_dtype )
@@ -1566,9 +1575,9 @@ def test_cdist(ndim, input_dtype, out_dtype, metric, capability):
15661575@pytest .mark .parametrize ("ndim" , [11 , 97 , 1536 ])
15671576@pytest .mark .parametrize ("input_dtype" , ["float32" , "float16" ])
15681577@pytest .mark .parametrize ("out_dtype" , [None , "float32" , "int32" ])
1569- @pytest .mark .parametrize ("metric" , ["cosine " , "sqeuclidean" ])
1578+ @pytest .mark .parametrize ("metric" , ["angular " , "sqeuclidean" ])
15701579def test_cdist_itself (ndim , input_dtype , out_dtype , metric ):
1571- """Compares the simd.cdist(A, A) function with scipy.spatial.distance.cdist(A, A), measuring the accuracy error for f16, and f32 types using sqeuclidean and cosine metrics."""
1580+ """Compares the simd.cdist(A, A) function with scipy.spatial.distance.cdist(A, A), measuring the accuracy error for f16, and f32 types using sqeuclidean and angular metrics."""
15721581
15731582 if input_dtype == "float16" and is_running_under_qemu ():
15741583 pytest .skip ("Testing low-precision math isn't reliable in QEMU" )
@@ -1577,14 +1586,15 @@ def test_cdist_itself(ndim, input_dtype, out_dtype, metric):
15771586
15781587 # Check if we need to round before casting to integer (to match SimSIMD's lround behavior)
15791588 is_integer_output = out_dtype in ("int32" , "int64" , "int16" , "int8" , "uint32" , "uint64" , "uint16" , "uint8" )
1589+ scipy_metric = scipy_metric_name (metric )
15801590
15811591 A = np .random .randn (10 , ndim + 1 ).astype (input_dtype )
15821592 if out_dtype is None :
1583- expected = spd .cdist (A , A , metric )
1593+ expected = spd .cdist (A , A , scipy_metric )
15841594 result = simd .cdist (A , A , metric = metric )
15851595 else :
15861596 #! SimSIMD rounds to the nearest integer before casting
1587- scipy_result = spd .cdist (A , A , metric )
1597+ scipy_result = spd .cdist (A , A , scipy_metric )
15881598 expected = np .round (scipy_result ).astype (out_dtype ) if is_integer_output else scipy_result .astype (out_dtype )
15891599 result = simd .cdist (A , A , metric = metric , out_dtype = out_dtype )
15901600
@@ -1903,15 +1913,15 @@ def test_gil_free_threading():
19031913 distances = np .zeros (vectors_a .shape [0 ], dtype = np .float32 )
19041914
19051915 def compute_batch (start_idx , end_idx ) -> float :
1906- """Compute cosine distances for a batch."""
1916+ """Compute angular distances for a batch."""
19071917 slice_a = vectors_a [start_idx :end_idx ]
19081918 slice_b = vectors_b [start_idx :end_idx ]
19091919 slice_distances = distances [start_idx :end_idx ]
19101920 simd .angular (slice_a , slice_b , out = slice_distances )
19111921 return sum (slice_distances )
19121922
19131923 def compute_with_threads (threads : int ) -> float :
1914- """Compute cosine distances using multiple threads."""
1924+ """Compute angular distances using multiple threads."""
19151925 chunk_size = len (vectors_a ) // threads
19161926 futures = []
19171927 with concurrent .futures .ThreadPoolExecutor (max_workers = threads ) as executor :
0 commit comments