Repository: matchms/matchms

Issue:
Implement `sparse` array_type computation for fingerprint similarity
A naive implementation computing the dense matrix and then just returning it as sparse could be sufficient - or compute dense and then filter out 0 values.

Comments:

Patch:
diff --git a/matchms/similarity/FingerprintSimilarity.py b/matchms/similarity/FingerprintSimilarity.py
index a9af3628..09873c18 100644
--- a/matchms/similarity/FingerprintSimilarity.py
+++ b/matchms/similarity/FingerprintSimilarity.py
@@ -1,5 +1,6 @@
 from typing import List, Union
 import numpy as np
+from sparsestack import StackedSparseArray
 from matchms.typing import SpectrumType
 from .BaseSimilarity import BaseSimilarity
 from .vector_similarity_functions import (cosine_similarity,
@@ -141,8 +142,6 @@ class FingerprintSimilarity(BaseSimilarity):
                 similarity_matrix[:] = self.set_empty_scores
             return similarity_matrix
 
-        if array_type != "numpy":
-            raise NotImplementedError("Output array type other than numpy is not yet implemented.")
         fingerprints1, idx_fingerprints1 = collect_fingerprints(references)
         fingerprints2, idx_fingerprints2 = collect_fingerprints(queries)
         assert idx_fingerprints1.size > 0 and idx_fingerprints2.size > 0, ("Not enouth molecular fingerprints.",
@@ -162,4 +161,10 @@ class FingerprintSimilarity(BaseSimilarity):
             similarity_matrix[np.ix_(idx_fingerprints1,
                                         idx_fingerprints2)] = cosine_similarity_matrix(fingerprints1,
                                                                                        fingerprints2)
-        return similarity_matrix.astype(self.score_datatype)
+        if array_type == "sparse":
+            scores_array = StackedSparseArray(len(references), len(queries))
+            scores_array.add_dense_matrix(similarity_matrix.astype(self.score_datatype), "")
+            return scores_array
+        if array_type == "numpy":
+            return similarity_matrix.astype(self.score_datatype)
+        raise NotImplementedError("Output array type is not yet implemented.")


Test patch:
diff --git a/tests/test_fingerprint_similarity.py b/tests/test_fingerprint_similarity.py
index 37f895a8..97c8b9dc 100644
--- a/tests/test_fingerprint_similarity.py
+++ b/tests/test_fingerprint_similarity.py
@@ -1,4 +1,5 @@
 import numpy as np
+from sparsestack import StackedSparseArray
 import pytest
 from matchms import Spectrum, calculate_scores
 from matchms.similarity import FingerprintSimilarity
@@ -42,10 +43,10 @@ def test_fingerprint_similarity_parallel_empty_fingerprint(test_method):
                                                       [0, 1.]]), 0.001), "Expected different values."
 
 
-@pytest.mark.parametrize("test_method, expected_score", [("cosine", 0.84515425),
-                                                         ("jaccard", 0.71428571),
-                                                         ("dice", 0.83333333)])
-def test_fingerprint_similarity_parallel(test_method, expected_score):
+@pytest.mark.parametrize("test_method, expected_score, array_type, set_empty", [("cosine", 0.84515425, "numpy", np.nan),
+                                                         ("jaccard", 0.71428571, "sparse", np.nan),
+                                                         ("dice", 0.83333333, "numpy", 0)])
+def test_fingerprint_similarity_parallel(test_method, expected_score, array_type, set_empty):
     """Test score matrix with known values for the provided methods."""
     spectrum0 = Spectrum(mz=np.array([], dtype="float"),
                          intensities=np.array([], dtype="float"),
@@ -61,38 +62,15 @@ def test_fingerprint_similarity_parallel(test_method, expected_score):
                          intensities=np.array([], dtype="float"),
                          metadata={"fingerprint": fingerprint2})
 
-    similarity_measure = FingerprintSimilarity(similarity_measure=test_method)
-    score_matrix = similarity_measure.matrix([spectrum0, spectrum1, spectrum2],
-                                             [spectrum0, spectrum1, spectrum2])
-    expected_matrix = np.array([[1., expected_score],
-                                   [expected_score, 1.]])
-    assert score_matrix[1:, 1:] == pytest.approx(expected_matrix, 0.001), "Expected different values."
-    assert np.all(np.isnan(score_matrix[:, 0])), "Expected 'nan' entries."
-    assert np.all(np.isnan(score_matrix[0, :])), "Expected 'nan' entries."
-
-
-def test_fingerprint_similarity_parallel_cosine_set_empty_to_0():
-    """Test cosine score matrix with known values. Set non-exising values to 0."""
-    spectrum0 = Spectrum(mz=np.array([], dtype="float"),
-                         intensities=np.array([], dtype="float"),
-                         metadata={})
-
-    fingerprint1 = np.array([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0])
-    spectrum1 = Spectrum(mz=np.array([], dtype="float"),
-                         intensities=np.array([], dtype="float"),
-                         metadata={"fingerprint": fingerprint1})
-
-    fingerprint2 = np.array([0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1])
-    spectrum2 = Spectrum(mz=np.array([], dtype="float"),
-                         intensities=np.array([], dtype="float"),
-                         metadata={"fingerprint": fingerprint2})
-
-    similarity_measure = FingerprintSimilarity(set_empty_scores=0, similarity_measure="cosine")
+    similarity_measure = FingerprintSimilarity(set_empty_scores=set_empty, similarity_measure=test_method)
     score_matrix = similarity_measure.matrix([spectrum0, spectrum1, spectrum2],
-                                             [spectrum0, spectrum1, spectrum2])
-    assert score_matrix == pytest.approx(np.array([[0, 0, 0],
-                                                      [0, 1., 0.84515425],
-                                                      [0, 0.84515425, 1.]]), 0.001), "Expected different values."
+                                             [spectrum0, spectrum1, spectrum2], array_type=array_type)
+    expected_matrix = np.array([[set_empty, set_empty, set_empty],
+                                   [set_empty, 1, expected_score],
+                                   [set_empty, expected_score, 1]])
+    if isinstance(score_matrix, (StackedSparseArray)):
+        score_matrix = score_matrix.to_array()
+    assert np.allclose(score_matrix , expected_matrix, equal_nan=True) , "Expected different values."
 
 
 def test_fingerprint_similarity_with_scores_sorting():