Repository: matchms/matchms
Pull request #788 (2025-03-31 07:29:31)

Update BaseSimilarity.py
- Editing docstrings to make API a bit clearer.
- add docstrings and make indentations more consistent


Patch:
diff --git a/matchms/similarity/BaseSimilarity.py b/matchms/similarity/BaseSimilarity.py
index f348ae63..2ed5895a 100644
--- a/matchms/similarity/BaseSimilarity.py
+++ b/matchms/similarity/BaseSimilarity.py
@@ -1,5 +1,5 @@
 from abc import abstractmethod
-from typing import Sequence, Tuple
+from typing import Optional, Sequence, Tuple
 import numpy as np
 from tqdm import tqdm
 from matchms.similarity.COOIndex import COOIndex
@@ -9,106 +9,177 @@ from matchms.typing import SpectrumType
 
 
 class BaseSimilarity:
-    """Similarity function base class.
-    When building a custom similarity measure, inherit from this class and implement
-    the desired methods.
+    """
+    Base class for similarity functions.
+
+    To implement a custom similarity measure, subclass BaseSimilarity and implement
+    the `pair` method, which calculates the similarity between two spectra.
 
     Attributes
     ----------
     is_commutative:
-       Whether similarity function is commutative, which means that the order of spectra
-       does not matter (similarity(A, B) == similarity(B, A)). Default is True.
+        Indicates whether the similarity function is commutative (i.e. similarity(A, B) == similarity(B, A)).
+        Defaults to True.
     score_datatype:
         Data type for the score output, e.g. "float" or [("score", "float"), ("matches", "int")].
         If multiple data types are set, the main score should be set to "score" (used as default for filtering).
     """
+
     # Set key characteristics as class attributes
     is_commutative = True
     score_datatype = np.float64
 
-    def __init__(self, score_filters: Tuple[FilterScoreByValue, ...]):
+    def __init__(self, score_filters: Optional[Tuple[FilterScoreByValue, ...]] = None):
         """
-
-        Attributes
+        Parameters
         ----------
-        score_filters:
-            Tuple of filters that should be applied to each score before scoring. If you want to run without filtering,
-            it is best to run matrix(). Filters can also be run after computing a matrix first, however, for strict
-            filtering this implementation can be more memory efficient. Only use matrix_with_filter if you expect to
-            filter out more than 90% of your data. Otherwise, it is more memory efficient to first run matrix, followed by
-            filtering the matrix.
+        score_filters: tuple, optional
+            A tuple of filter objects to apply to each similarity score.
+            - If you do not wish to apply any filtering, pass an empty tuple and use the dense matrix method (`matrix()`).
+            - For cases where you expect to filter out more than 90% of the computed scores, consider using
+              the sparse array methods (see `sparse_array()`).
         """
-        self.score_filters = score_filters
+        self.score_filters = score_filters if score_filters is not None else ()
 
     @abstractmethod
     def pair(self, reference: SpectrumType, query: SpectrumType) -> np.ndarray:
-        """Method to calculate the similarity for one input pair.
+        """
+        Compute the similarity score for a single pair of spectra.
 
         Parameters
         ----------
         reference
-            Single reference spectrum.
+            A single reference spectrum.
         query
-            Single query spectrum.
+            A single query spectrum.
 
         Returns
-            The similarity score as numpy array (using self.score_datatype). For instance returning
-            np.asarray(score, dtype=self.score_datatype)
+            The similarity score as numpy array (with dtype given by self.score_datatype).
+            For example: return np.asarray(score, dtype=self.score_datatype)
         """
         raise NotImplementedError
 
-    def matrix(self, references: Sequence[SpectrumType],
-               queries: Sequence[SpectrumType],
-               is_symmetric: bool = False,
-               mask_indices: COOIndex = None,
-               ) -> np.ndarray:
+    def matrix(
+        self,
+        references: Sequence[SpectrumType],
+        queries: Sequence[SpectrumType],
+        is_symmetric: bool = False,
+        mask_indices: COOIndex = None,
+    ) -> np.ndarray:
         """
         Compute a dense similarity matrix for all pairs of reference and query spectra.
 
+        Use this method when you expect most scores to pass the filtering. For instance if you don't apply any
+        filtering. But also when you expect filtering to filter out less than 90% of all calculated scores.
+        `score_filters` can be set in `init`'.
         Parameters
         ----------
         references:
             Collection of reference spectra.
         queries:
             Collection of query spectra.
-        mask_indices:
-            Indices to calculate scores for the rest is set to 0.
         is_symmetric:
-            Indicates if the similarity matrix is symmetric (e.g., for all-vs-all comparisons).
-            When True, only the upper triangle of the matrix is computed and then mirrored,
-            which can reduce computation time.
+            If True, indicates that the similarity matrix is symmetric (i.e., references and queries are the same).
+            Only the upper triangle is computed and then mirrored. Defaults to False.
+        mask_indices:
+            A COOIndex instance specifying which pairs to compute.
+            If provided, only the specified index pairs will be computed (others remain zero).
+            This is helpful when a previous score already filters out many pairs, reducing computation time and memory footprint.
         """
         if mask_indices is None:
-            return self._matrix_without_mask_with_filter(references, queries, is_symmetric=is_symmetric)
-        return self._matrix_with_mask(references, queries,
-                                      mask_indices=mask_indices, is_symmetric=is_symmetric)
-
-    def sparse_array(self, references: Sequence[SpectrumType], queries: Sequence[SpectrumType],
-                     mask_indices: COOIndex = None, is_symmetric = False) -> COOMatrix:
-        if len(self.score_filters) == 0 and mask_indices is not None:
-            return self._sparse_array_with_mask_without_filter(references, queries, mask_indices=mask_indices)
+            return self._matrix_without_mask_with_filter(
+                references, queries, is_symmetric=is_symmetric
+            )
+        return self._matrix_with_mask_with_filter(
+            references, queries, mask_indices=mask_indices, is_symmetric=is_symmetric
+        )
+
+    def sparse_array(
+        self,
+        references: Sequence[SpectrumType],
+        queries: Sequence[SpectrumType],
+        mask_indices: COOIndex = None,
+        is_symmetric=False,
+    ) -> COOMatrix:
+        """
+        Compute a sparse array (in COO format) of similarity scores.
+
+        Use this method if you expect heavy filtering (i.e. many scores are dropped) or
+        if you want to compute scores only for a selected set of index pairs. By using `sparse_array()` this can reduce
+        the memory footprint if many scores are dropped.
+
+        Note:
+          - If no filtering is required (empty score_filters) and no mask is provided,
+            it is recommended to use `matrix()` and then convert the dense matrix to COO format.
+          - When a mask is provided, only the pairs specified in the mask are computed.
+
+        Parameters
+        ----------
+        references:
+            A collection of reference spectra.
+        queries:
+            A collection of query spectra.
+        mask_indices:
+            A COOIndex instance specifying the (row, column) pairs to compute. Defaults to None.
+        is_symmetric:
+            If True, assumes that the matrix is symmetric. Defaults to False.
+        """
+        if len(self.score_filters) == 0 and mask_indices:
+            return self._sparse_array_with_mask_without_filter(
+                references, queries, mask_indices=mask_indices
+            )
         if len(self.score_filters) != 0 and mask_indices is None:
-            return self._sparse_array_with_filter_without_mask(references, queries, is_symmetric=is_symmetric)
-        if len(self.score_filters) != 0 and mask_indices is not None:
-            return self._sparse_array_with_filter(references, queries, mask_indices)
-
-        # if score_filters is None and mask_indices is None:
-        # todo replace with using matrix followed by a conversion to COO array. (and a warning that this is not a
-        #  good idea) do this once we settle on a COO Array format (e.g. using the sparse package)
-        raise ValueError("If no masking or score filters is needed, please use matrix() instead")
-
-    def _matrix_without_mask_with_filter(self,
-                          references: Sequence[SpectrumType], queries: Sequence[SpectrumType],
-                          is_symmetric: bool = False):
-        sim_matrix = self._matrix_without_mask_without_filter(references, queries, is_symmetric=is_symmetric)
+            return self._sparse_array_without_mask_with_filter(
+                references, queries, is_symmetric=is_symmetric
+            )
+        if len(self.score_filters) != 0 and mask_indices:
+            return self._sparse_array_with_mask_with_filter(
+                references, queries, mask_indices
+            )
+
+        # TODO: replace with matrix computation followed by a conversion to COO array.
+        # (and a warning that this is not a good idea) do this once we settle on a COO Array format (e.g. using the sparse package)
+        raise ValueError(
+            "If no masking or score filters is needed, please use matrix() instead"
+        )
+
+    # --- Dense Matrix Computations ---
+
+    def _matrix_without_mask_with_filter(
+        self,
+        references: Sequence[SpectrumType],
+        queries: Sequence[SpectrumType],
+        is_symmetric: bool = False,
+    ) -> np.ndarray:
+        """
+        Compute a dense similarity matrix without a mask, then apply score filters (if any).
+
+        This method is used internally by `matrix()` when no mask is provided.
+
+        Parameters
+        ----------
+        references:
+            A collection of reference spectra.
+        queries:
+            A collection of query spectra.
+        is_symmetric:
+            If True, mirrors the computed score to the symmetric position. Defaults to False.
+        """
+        sim_matrix = self._matrix_without_mask_without_filter(
+            references, queries, is_symmetric=is_symmetric
+        )
+
         for score_filter in self.score_filters:
             sim_matrix = score_filter.filter_matrix(sim_matrix)
+
         return sim_matrix
 
-    def _matrix_without_mask_without_filter(self,
-                                            references: Sequence[SpectrumType], queries: Sequence[SpectrumType],
-                                            is_symmetric: bool = False
-                                            ) -> np.ndarray:
+    def _matrix_without_mask_without_filter(
+        self,
+        references: Sequence[SpectrumType],
+        queries: Sequence[SpectrumType],
+        is_symmetric: bool = False,
+    ) -> np.ndarray:
         """
         Compute a dense similarity matrix for all pairs of reference and query spectra.
 
@@ -123,14 +194,22 @@ class BaseSimilarity:
             When True, only the upper triangle of the matrix is computed and then mirrored,
             which can reduce computation time.
         """
-        sim_matrix = np.zeros((len(references), len(queries)), dtype=self.score_datatype)
+        sim_matrix = np.zeros(
+            (len(references), len(queries)), dtype=self.score_datatype
+        )
         if is_symmetric:
             if len(references) != len(queries):
-                raise ValueError(f"Found unequal number of spectra {len(references)} and {len(queries)} while `is_symmetric` is True.")
+                raise ValueError(
+                    f"Found unequal number of spectra {len(references)} and {len(queries)} while `is_symmetric` is True."
+                )
 
             # Compute pairwise similarities
-            for i_ref, reference in enumerate(tqdm(references, "Calculating similarities")):
-                for i_query, query in enumerate(queries[i_ref:], start=i_ref):  # Compute only upper triangle
+            for i_ref, reference in enumerate(
+                tqdm(references, "Calculating similarities")
+            ):
+                for i_query, query in enumerate(
+                    queries[i_ref:], start=i_ref
+                ):  # Compute only upper triangle
                     score = self.pair(reference, query)
                     sim_matrix[i_ref, i_query] = score
                     sim_matrix[i_query, i_ref] = score
@@ -142,28 +221,58 @@ class BaseSimilarity:
                     sim_matrix[i, j] = score
         return sim_matrix
 
-    def _matrix_with_mask(self,
-                          references: Sequence[SpectrumType], queries: Sequence[SpectrumType],
-                          mask_indices: COOIndex,
-                          is_symmetric: bool = False
-                          ) -> np.ndarray:
-        sim_matrix = np.zeros((len(references), len(queries)), dtype=self.score_datatype)
+    def _matrix_with_mask_with_filter(
+        self,
+        references: Sequence[SpectrumType],
+        queries: Sequence[SpectrumType],
+        mask_indices: COOIndex,
+        is_symmetric: bool = False,
+    ) -> np.ndarray:
+        """
+        Compute a dense similarity matrix using a provided mask.
+
+        Only the (row, column) pairs specified in mask_indices are computed. All pairs not in mask_indices are set to 0.
+        Score filters are applied to each computed score.
+
+        Parameters
+        ----------
+        references:
+            Collection of reference spectra.
+        queries:
+            Collection of query spectra.
+        mask_indices:
+            Specifies which index pairs to compute.
+        is_symmetric:
+            If True, mirrors the computed score to the symmetric position. Defaults to False.
+        """
+        sim_matrix = np.zeros(
+            (len(references), len(queries)), dtype=self.score_datatype
+        )
         for i_row, i_col in tqdm(mask_indices, desc="Calculating sparse similarities"):
             score = self.pair(references[i_row], queries[i_col])
-            if np.all([score_filter.keep_score(score) for score_filter in self.score_filters]):
+            if np.all(
+                [score_filter.keep_score(score) for score_filter in self.score_filters]
+            ):
                 # if not all filters pass the score is not added (so remains 0)
                 sim_matrix[i_row, i_col] = score
                 if is_symmetric:
                     sim_matrix[i_col, i_row] = score
         return sim_matrix
 
+    # --- Sparse Matrix Computations ---
 
-    def _sparse_array_with_filter_without_mask(self, references: Sequence[SpectrumType], queries: Sequence[SpectrumType],
-                           is_symmetric: bool = False, ) -> COOMatrix:
-        """Optional: Provide optimized method to calculate a sparse matrix with filtering applied directly.
-        This is helpfull if the filter function is removing most scores. Important note, per score this takes about 12x
-        the amount of memory. Therefore, doing filtering during compute is only worth it if you keep less than 1/12th of
-        the scores. Otherwise, it is best to just use matrix, followed by a filter.
+    def _sparse_array_without_mask_with_filter(
+        self,
+        references: Sequence[SpectrumType],
+        queries: Sequence[SpectrumType],
+        is_symmetric: bool = False,
+    ) -> COOMatrix:
+        """
+        Compute a sparse similarity matrix (COO format) with filtering, without using a mask.
+
+        This method is optimized for cases where filtering drops most scores.
+        Important to note: Memory-wise, this method is only worth using if less than 1/12th of the scores are kept.
+        Otherwise, it is generally better to use the `matrix()` method, followed by a filter.
 
         Parameters
         ----------
@@ -172,26 +281,33 @@ class BaseSimilarity:
         queries
             List of query objects
         is_symmetric
-            Set to True when *references* and *queries* are identical (as for instance for an all-vs-all
-            comparison). By using the fact that score[i,j] = score[j,i] the calculation will be about
-            2x faster.
+            If True, leverages commutativity to compute only half the matrix. Defaults to False.
         """
         n_rows = len(references)
         n_cols = len(queries)
 
         if is_symmetric and n_rows != n_cols:
-            raise ValueError(f"Found unequal number of spectra {n_rows} and {n_cols} while `is_symmetric` is True.")
+            raise ValueError(
+                f"Found unequal number of spectra {n_rows} and {n_cols} while `is_symmetric` is True."
+            )
 
         idx_row = []
         idx_col = []
         scores = []
         # Wrap the outer loop with tqdm to track progress
-        for i_ref, reference in enumerate(tqdm(references[:n_rows], desc="Calculating similarities")):
+        for i_ref, reference in enumerate(
+            tqdm(references[:n_rows], desc="Calculating similarities")
+        ):
             if is_symmetric and self.is_commutative:
                 for i_query, query in enumerate(queries[i_ref:n_cols], start=i_ref):
                     score = self.pair(reference, query)
                     # Check if the score passes the filter before storing.
-                    if np.all([score_filter.keep_score(score) for score_filter in self.score_filters]):
+                    if np.all(
+                        [
+                            score_filter.keep_score(score)
+                            for score_filter in self.score_filters
+                        ]
+                    ):
                         idx_row += [i_ref, i_query]
                         idx_col += [i_query, i_ref]
                         scores += [score, score]
@@ -199,15 +315,28 @@ class BaseSimilarity:
                 for i_query, query in enumerate(queries[:n_cols]):
                     score = self.pair(reference, query)
                     # Check if the score passes the filter before storing.
-                    if np.all([score_filter.keep_score(score) for score_filter in self.score_filters]):
+                    if np.all(
+                        [
+                            score_filter.keep_score(score)
+                            for score_filter in self.score_filters
+                        ]
+                    ):
                         idx_row.append(i_ref)
                         idx_col.append(i_query)
                         scores.append(score)
-        return COOMatrix(row_idx=idx_row, column_idx=idx_col, scores=scores, scores_dtype=self.score_datatype)
+        return COOMatrix(
+            row_idx=idx_row,
+            column_idx=idx_col,
+            scores=scores,
+            scores_dtype=self.score_datatype,
+        )
 
-
-    def _sparse_array_with_mask_without_filter(self, references: Sequence[SpectrumType], queries: Sequence[SpectrumType],
-                     mask_indices: COOIndex) -> COOMatrix:
+    def _sparse_array_with_mask_without_filter(
+        self,
+        references: Sequence[SpectrumType],
+        queries: Sequence[SpectrumType],
+        mask_indices: COOIndex,
+    ) -> COOMatrix:
         """Optional: Provide optimized method to calculate a sparse matrix of similarity scores.
 
         Compute similarity scores for pairs of reference and query spectra as given by the indices
@@ -224,20 +353,36 @@ class BaseSimilarity:
             The row column index pairs for which a score should be calculated.
         """
         if len(self.score_filters) > 0:
-            raise ValueError("Don't run _sparse_array_with_mask_without_filter if score_filters are set. "
-                             "Instead run _sparse_array_with_filter")
+            raise ValueError(
+                "Don't run _sparse_array_with_mask_without_filter if score_filters are set. "
+                "Instead run _sparse_array_with_mask_with_filter"
+            )
         scores = np.zeros((len(mask_indices)), dtype=self.score_datatype)
-        for i, (i_row, i_col) in enumerate(tqdm(mask_indices, desc="Calculating sparse similarities")):
+        for i, (i_row, i_col) in enumerate(
+            tqdm(mask_indices, desc="Calculating sparse similarities")
+        ):
             scores[i] = self.pair(references[i_row], queries[i_col])
-        return COOMatrix(row_idx=mask_indices.idx_row, column_idx=mask_indices.idx_col, scores=scores, scores_dtype=self.score_datatype)
+        return COOMatrix(
+            row_idx=mask_indices.idx_row,
+            column_idx=mask_indices.idx_col,
+            scores=scores,
+            scores_dtype=self.score_datatype,
+        )
 
-    def _sparse_array_with_filter(self, references: Sequence[SpectrumType], queries: Sequence[SpectrumType],
-                                 mask_indices: COOIndex) -> COOMatrix:
-        """Uses a mask to compute only the required scores and filters scores that do not pass the filter.
+    def _sparse_array_with_mask_with_filter(
+        self,
+        references: Sequence[SpectrumType],
+        queries: Sequence[SpectrumType],
+        mask_indices: COOIndex,
+    ) -> COOMatrix:
+        """
+        Compute a sparse similarity matrix (COO format) using a mask and applying score filters.
 
-        This method most of the time does not make sense. It is only worth it if you want to store less than 1/12th
-        of the computed scores. This is not the case most of the time, so sparse_array is most of the time the most
-        memory efficient.
+        Only the (row, column) pairs specified in mask_indices are computed, and only scores that pass
+        all filters are stored.
+
+        Please note: If the mask_indices contain more than 1/12th of all indices, this method could become less memory efficient
+        than `_sparse_array_without_mask_with_filter`.
 
         Parameters
         ----------
@@ -246,7 +391,7 @@ class BaseSimilarity:
         queries
             List of query objects
         mask_indices
-            The row column index pairs for which a score should be calculated.
+            Specifies the (row, column) pairs for which to compute scores.
         """
         scores = []
         idx_row = []
@@ -254,14 +399,19 @@ class BaseSimilarity:
         for row, col in tqdm(mask_indices, desc="Calculating sparse similarities"):
             score = self.pair(references[row], queries[col])
             # Check if the score passes the filter before storing.
-            if np.all([score_filter.keep_score(score) for score_filter in self.score_filters]):
+            if np.all(
+                [score_filter.keep_score(score) for score_filter in self.score_filters]
+            ):
                 idx_row.append(row)
                 idx_col.append(col)
                 scores.append(score)
-        return COOMatrix(row_idx=idx_row, column_idx=idx_col, scores=scores, scores_dtype=self.score_datatype)
-
+        return COOMatrix(
+            row_idx=idx_row,
+            column_idx=idx_col,
+            scores=scores,
+            scores_dtype=self.score_datatype,
+        )
 
     def to_dict(self) -> dict:
         """Return a dictionary representation of a similarity function."""
-        return {"__Similarity__": self.__class__.__name__,
-                **self.__dict__}
+        return {"__Similarity__": self.__class__.__name__, **self.__dict__}
diff --git a/matchms/similarity/CosineGreedy.py b/matchms/similarity/CosineGreedy.py
index 1be3eead..e684cea7 100644
--- a/matchms/similarity/CosineGreedy.py
+++ b/matchms/similarity/CosineGreedy.py
@@ -1,4 +1,4 @@
-from typing import Tuple
+from typing import Optional, Tuple
 import numpy as np
 from matchms.typing import SpectrumType
 from .BaseSimilarity import BaseSimilarity
@@ -53,7 +53,7 @@ class CosineGreedy(BaseSimilarity):
     score_datatype = [("score", np.float64), ("matches", "int")]
 
     def __init__(self, tolerance: float = 0.1, mz_power: float = 0.0,
-                 intensity_power: float = 1.0, score_filters: Tuple[FilterScoreByValue, ...] = ()):
+                 intensity_power: float = 1.0, score_filters: Optional[Tuple[FilterScoreByValue, ...]] = None):
         """
         Parameters
         ----------
diff --git a/matchms/similarity/CosineHungarian.py b/matchms/similarity/CosineHungarian.py
index 7c4f8ef6..1ff4f6c8 100644
--- a/matchms/similarity/CosineHungarian.py
+++ b/matchms/similarity/CosineHungarian.py
@@ -1,4 +1,4 @@
-from typing import Tuple
+from typing import Optional, Tuple
 import numpy as np
 from scipy.optimize import linear_sum_assignment
 from matchms.similarity.spectrum_similarity_functions import collect_peak_pairs
@@ -25,7 +25,7 @@ class CosineHungarian(BaseSimilarity):
     score_datatype = [("score", np.float64), ("matches", "int")]
 
     def __init__(self, tolerance: float = 0.1, mz_power: float = 0.0,
-                 intensity_power: float = 1.0, score_filters: Tuple[FilterScoreByValue, ...] = ()):
+                 intensity_power: float = 1.0, score_filters: Optional[Tuple[FilterScoreByValue, ...]] = None):
         """
         Parameters
         ----------
diff --git a/matchms/similarity/FingerprintSimilarity.py b/matchms/similarity/FingerprintSimilarity.py
index 042ea26f..d48167b2 100644
--- a/matchms/similarity/FingerprintSimilarity.py
+++ b/matchms/similarity/FingerprintSimilarity.py
@@ -1,4 +1,4 @@
-from typing import List, Tuple, Union
+from typing import List, Optional, Tuple, Union
 import numpy as np
 from matchms.Spectrum import Spectrum
 from .BaseSimilarity import BaseSimilarity
@@ -63,7 +63,7 @@ class FingerprintSimilarity(BaseSimilarity):
     score_datatype = np.float64
 
     def __init__(self, similarity_measure: str = "jaccard",
-                 set_empty_scores: Union[float, int, str] = "nan", score_filters: Tuple[FilterScoreByValue, ...] = ()):
+                 set_empty_scores: Union[float, int, str] = "nan", score_filters: Optional[Tuple[FilterScoreByValue, ...]] = None):
         """
 
         Parameters
diff --git a/matchms/similarity/IntersectMz.py b/matchms/similarity/IntersectMz.py
index c00cc1d3..1adede46 100644
--- a/matchms/similarity/IntersectMz.py
+++ b/matchms/similarity/IntersectMz.py
@@ -1,4 +1,4 @@
-from typing import Tuple
+from typing import Optional, Tuple
 import numpy as np
 from .. import Spectrum
 from .BaseSimilarity import BaseSimilarity
@@ -39,7 +39,7 @@ class IntersectMz(BaseSimilarity):
 
     """
 
-    def __init__(self, scaling: float = 1.0, score_filters: Tuple[FilterScoreByValue, ...] = ()):
+    def __init__(self, scaling: float = 1.0, score_filters: Optional[Tuple[FilterScoreByValue, ...]] = None):
         """Constructor. Here, function parameters are defined.
 
         Parameters
diff --git a/matchms/similarity/MetadataMatch.py b/matchms/similarity/MetadataMatch.py
index 5a61e0ef..8325cff7 100644
--- a/matchms/similarity/MetadataMatch.py
+++ b/matchms/similarity/MetadataMatch.py
@@ -1,5 +1,5 @@
 import logging
-from typing import List, Tuple
+from typing import List, Optional, Tuple
 import numpy as np
 from matchms.similarity.spectrum_similarity_functions import (
     number_matching, number_matching_symmetric)
@@ -65,7 +65,7 @@ class MetadataMatch(BaseSimilarity):
     score_datatype = bool
 
     def __init__(self, field: str, matching_type: str = "equal_match",
-                 tolerance: float = 0.1, score_filters: Tuple[FilterScoreByValue, ...] = ()):
+                 tolerance: float = 0.1, score_filters: Optional[Tuple[FilterScoreByValue, ...]] = None):
         """
         Parameters
         ----------
diff --git a/matchms/similarity/ModifiedCosine.py b/matchms/similarity/ModifiedCosine.py
index 9223f296..274b91fd 100644
--- a/matchms/similarity/ModifiedCosine.py
+++ b/matchms/similarity/ModifiedCosine.py
@@ -1,5 +1,5 @@
 import logging
-from typing import Tuple
+from typing import Optional, Tuple
 import numpy as np
 from matchms.filtering.metadata_processing.add_precursor_mz import \
     _convert_precursor_mz
@@ -60,7 +60,7 @@ class ModifiedCosine(BaseSimilarity):
     score_datatype = [("score", np.float64), ("matches", "int")]
 
     def __init__(self, tolerance: float = 0.1, mz_power: float = 0.0, intensity_power: float = 1.0,
-                 score_filters: Tuple[FilterScoreByValue, ...] = ()):
+                 score_filters: Optional[Tuple[FilterScoreByValue, ...]] = None):
         """
         Parameters
         ----------
diff --git a/matchms/similarity/NeutralLossesCosine.py b/matchms/similarity/NeutralLossesCosine.py
index 4b521b82..feaaf4a1 100644
--- a/matchms/similarity/NeutralLossesCosine.py
+++ b/matchms/similarity/NeutralLossesCosine.py
@@ -1,5 +1,5 @@
 import logging
-from typing import Tuple
+from typing import Optional, Tuple
 import numpy as np
 from matchms.filtering.metadata_processing.add_precursor_mz import \
     _convert_precursor_mz
@@ -31,7 +31,7 @@ class NeutralLossesCosine(BaseSimilarity):
     score_datatype = [("score", np.float64), ("matches", "int")]
 
     def __init__(self, tolerance: float = 0.1, mz_power: float = 0.0, intensity_power: float = 1.0,
-                 ignore_peaks_above_precursor: bool = True, score_filters: Tuple[FilterScoreByValue, ...] = ()):
+                 ignore_peaks_above_precursor: bool = True, score_filters: Optional[Tuple[FilterScoreByValue, ...]] = None):
         """
         Parameters
         ----------
diff --git a/matchms/similarity/ParentMassMatch.py b/matchms/similarity/ParentMassMatch.py
index fe2f19ad..7170bc2d 100644
--- a/matchms/similarity/ParentMassMatch.py
+++ b/matchms/similarity/ParentMassMatch.py
@@ -1,4 +1,4 @@
-from typing import List, Tuple
+from typing import List, Optional, Tuple
 import numpy as np
 from matchms.similarity.spectrum_similarity_functions import (
     number_matching, number_matching_symmetric)
@@ -54,7 +54,7 @@ class ParentMassMatch(BaseSimilarity):
     # Set output data type, e.g.  "float" or [("score", "float"), ("matches", "int")]
     score_datatype = bool
 
-    def __init__(self, tolerance: float = 0.1, score_filters: Tuple[FilterScoreByValue, ...] = ()):
+    def __init__(self, tolerance: float = 0.1, score_filters: Optional[Tuple[FilterScoreByValue, ...]] = None):
         """
         Parameters
         ----------
diff --git a/matchms/similarity/PrecursorMzMatch.py b/matchms/similarity/PrecursorMzMatch.py
index ea9e1beb..7d5f5d0e 100644
--- a/matchms/similarity/PrecursorMzMatch.py
+++ b/matchms/similarity/PrecursorMzMatch.py
@@ -1,4 +1,4 @@
-from typing import List, Tuple
+from typing import List, Optional, Tuple
 import numpy as np
 from matchms.similarity.spectrum_similarity_functions import (
     number_matching, number_matching_ppm, number_matching_symmetric,
@@ -57,7 +57,7 @@ class PrecursorMzMatch(BaseSimilarity):
     score_datatype = bool
 
     def __init__(self, tolerance: float = 0.1,
-                 tolerance_type: str = "Dalton", score_filters: Tuple[FilterScoreByValue, ...] = ()):
+                 tolerance_type: str = "Dalton", score_filters: Optional[Tuple[FilterScoreByValue, ...]] = None):
         """
         Parameters
         ----------


Test patch:
diff --git a/tests/test_scores.py b/tests/test_scores.py
index 2dbf012a..43d8911e 100644
--- a/tests/test_scores.py
+++ b/tests/test_scores.py
@@ -12,8 +12,9 @@ class DummySimilarityFunction(BaseSimilarity):
     """Simple dummy score, only contain pair-wise implementation."""
     score_datatype = [("score", np.str_, 16), ("len", np.int32)]
 
-    def __init__(self, score_filters = ()):
+    def __init__(self, score_filters = None):
         """constructor"""
+        # pylint: disable=useless-parent-delegation
         super().__init__(score_filters)
 
     def pair(self, reference, query):
@@ -26,8 +27,9 @@ class DummySimilarityFunctionParallel(BaseSimilarity):
     """Simple dummy score, contains pair-wise and matrix implementation."""
     score_datatype = [("score", np.str_, 16), ("len", "int")]
 
-    def __init__(self, score_filters =()):
+    def __init__(self, score_filters = None):
         """constructor"""
+        # pylint: disable=useless-parent-delegation
         super().__init__(score_filters)
 
     def pair(self, reference, query):
diff --git a/tests/test_scoring_method_consistency.py b/tests/test_scoring_method_consistency.py
index 994e64a3..40f3414e 100644
--- a/tests/test_scoring_method_consistency.py
+++ b/tests/test_scoring_method_consistency.py
@@ -155,7 +155,7 @@ def test_sparse_array_masking_and_filtering(spectra, similarity_measure):
     scoring_method = similarity_measure[0](**similarity_measure[1], score_filters=filters)
     computed_scores_matrix_with_mask_with_filters = scoring_method.sparse_array(spectra, spectra, is_symmetric=False, mask_indices=mask_indices)
     print(computed_scores_matrix_with_mask_with_filters)
-    scoring_method = similarity_measure[0](**similarity_measure[1], score_filters=())
+    scoring_method = similarity_measure[0](**similarity_measure[1], score_filters=None)
     computed_scores_matrix_with_mask_without_filters = scoring_method.sparse_array(spectra, spectra, is_symmetric=False, mask_indices=mask_indices)
     print(computed_scores_matrix_with_mask_without_filters)
     # todo add a test to check that the values are correct