Repository: cogent3/cogent3
Pull request #1418 (2023-05-06 01:38:13)

Merge develop into branch


Patch:
diff --git a/noxfile.py b/noxfile.py
index 28b318220..0f49e773a 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -3,7 +3,7 @@ import pathlib
 import nox
 
 
-_py_versions = range(8, 11)
+_py_versions = range(8, 12)
 
 
 @nox.session(python=[f"3.{v}" for v in _py_versions])
diff --git a/pyproject.toml b/pyproject.toml
index 7a743f269..5ed95ea38 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -12,12 +12,13 @@ authors = [
 keywords = ["biology", "genomics", "statistics", "phylogeny", "evolution", "bioinformatics"]
 readme = "README.md"
 license = { file = "LICENSE" }
-requires-python = ">=3.8,<3.11"
+requires-python = ">=3.8,<3.12"
 dependencies = ["chardet",
         "numpy",
         "numba>0.48.0;python_version<'3.9'",
         "numba>0.53; python_version>='3.9'",
         "numba>0.54; python_version>='3.10'",
+        "numba>=0.57.0; python_version=='3.11'",
         "scipy",
         "scitrack",
         "tqdm",
diff --git a/requirements.txt b/requirements.txt
index 121517ab3..edd34120d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,12 +1,13 @@
 chardet==5.1.0
 pillow==9.5.0
 psutil==5.9.5
-numpy==1.24.2
+numpy==1.24.3
 numba>0.48.0;python_version<'3.9'
 numba>0.53; python_version>='3.9'
 numba>0.54; python_version>='3.10'
+numba>=0.57.0; python_version=='3.11'
 scipy==1.10.1
-nox==2022.11.21
+nox==2023.4.22
 plotly==5.14.1
 typing_extensions>=3.7
 .[dev]
diff --git a/src/cogent3/core/alphabet.py b/src/cogent3/core/alphabet.py
index 7c13b1eca..f58b8b413 100644
--- a/src/cogent3/core/alphabet.py
+++ b/src/cogent3/core/alphabet.py
@@ -280,23 +280,6 @@ class Enumeration(tuple):
         stack_level=4,
     )
     def counts(self, a):  # pragma: no cover
-        """Returns array containing counts of each item in a.
-
-        For example, on the enumeration 'UCAG', the sequence 'CCUG' would
-        return the array [1,2,0,1] reflecting one count for the first item
-        in the enumeration ('U'), two counts for the second item ('C'), no
-        counts for the third item ('A'), and one count for the last item ('G').
-
-        The result will always be a vector of Int with length equal to
-        the length of the enumeration. We return Int and non an unsigned
-        type because it's common to subtract counts, which produces surprising
-        results on unit types (i.e. wrapraround to maxint) unless the type
-        is explicitly coerced by the user.
-
-        Sliently ignores any unrecognized indices, e.g. if your enumeration
-        contains 'TCAG' and you get an 'X', the 'X' will be ignored because
-        it has no index in the enumeration.
-        """
         try:
             data = ravel(a)
         except ValueError:  # ravel failed; try coercing to array
diff --git a/src/cogent3/core/sequence.py b/src/cogent3/core/sequence.py
index cb43f3a66..ae3f46cbb 100644
--- a/src/cogent3/core/sequence.py
+++ b/src/cogent3/core/sequence.py
@@ -1377,19 +1377,28 @@ class Sequence(SequenceI):
         ambigs = self.moltype.ambiguities
         return [ambigs[motif] for motif in self._seq]
 
-    def iter_kmers(self, k: int) -> Generator[str, None, None]:
-        """generates all overlapping k-mers"""
+    def iter_kmers(self, k: int, strict: bool = False) -> Generator[str, None, None]:
+        """generates all overlapping k-mers.
+        When strict is True, the characters in the k-mer must be
+        a subset of the canonical characters for the moltype"""
         if k <= 0:
             raise ValueError(f"k must be an int > 0, not {k}")
 
         if not isinstance(k, int):
             raise ValueError(f"k must be an int, not {k}")
 
-        yield from (str(self[i : i + k]) for i in range(len(self) - k + 1))
+        canonical = set(self.moltype)
+        for i in range(len(self) - k + 1):
+            kmer = self[i : i + k]
+            if not strict:
+                yield kmer
+            else:
+                if all(char in canonical for char in kmer):
+                    yield kmer
 
-    def get_kmers(self, k: int) -> List[str]:
+    def get_kmers(self, k: int, strict: bool = False) -> List[str]:
         """return all overlapping k-mers"""
-        return list(self.iter_kmers(k))
+        return list(self.iter_kmers(k, strict))
 
     def sliding_windows(self, window, step, start=None, end=None):
         """Generator function that yield new sequence objects
diff --git a/src/cogent3/util/warning.py b/src/cogent3/util/warning.py
index 3954fdf7b..835be3468 100644
--- a/src/cogent3/util/warning.py
+++ b/src/cogent3/util/warning.py
@@ -131,11 +131,12 @@ def deprecated_args(
     """
 
     discontinued = [discontinued] if isinstance(discontinued, str) else discontinued
+    old_args = dict(old_new).keys() if old_new else set()
 
     def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
         @functools.wraps(func)
         def wrapper(*args: Any, **kwargs: Any) -> Callable[..., Any]:
-            if old_new:
+            if old_args & kwargs.keys():
                 for old, new in old_new:
                     if old in kwargs:
                         kwargs[new] = kwargs.pop(old)


Test patch:
diff --git a/.github/workflows/testing_develop.yml b/.github/workflows/testing_develop.yml
index 6c459d5c6..607e9326a 100644
--- a/.github/workflows/testing_develop.yml
+++ b/.github/workflows/testing_develop.yml
@@ -14,7 +14,7 @@ jobs:
     strategy:
       matrix:
         os: [macos-latest, ubuntu-latest, windows-latest]
-        python-version: ["3.8", "3.9", "3.10"]
+        python-version: ["3.8", "3.9", "3.10", "3.11"]
 
     steps:
       - uses: "actions/checkout@v2"
diff --git a/tests/test_core/test_alphabet.py b/tests/test_core/test_alphabet.py
index 8a1df9263..2eaad0613 100644
--- a/tests/test_core/test_alphabet.py
+++ b/tests/test_core/test_alphabet.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 """Tests of the Enumeration and Alphabet objects.
 
 Note: individual alphabets are typically in MolType and are tested there.
diff --git a/tests/test_core/test_sequence.py b/tests/test_core/test_sequence.py
index 7b8b3c31c..6ad456f86 100644
--- a/tests/test_core/test_sequence.py
+++ b/tests/test_core/test_sequence.py
@@ -1846,6 +1846,185 @@ def test_seqview_repr():
     assert repr(view) == expected
 
 
+def test_get_kmers_strict():
+    orig = "TCAGGAN"
+    r = DnaSequence(orig)
+
+    assert r.get_kmers(1, strict=True) == ["T", "C", "A", "G", "G", "A"]
+    assert r.get_kmers(2, strict=True) == ["TC", "CA", "AG", "GG", "GA"]
+    assert r.get_kmers(3, strict=True) == ["TCA", "CAG", "AGG", "GGA"]
+    assert r.get_kmers(4, strict=True) == ["TCAG", "CAGG", "AGGA"]
+    assert r.get_kmers(5, strict=True) == ["TCAGG", "CAGGA"]
+    assert r.get_kmers(6, strict=True) == ["TCAGGA"]
+    assert r.get_kmers(7, strict=True) == []
+
+    assert r.get_kmers(1, strict=False) == ["T", "C", "A", "G", "G", "A", "N"]
+    assert r.get_kmers(2, strict=False) == ["TC", "CA", "AG", "GG", "GA", "AN"]
+    assert r.get_kmers(3, strict=False) == ["TCA", "CAG", "AGG", "GGA", "GAN"]
+    assert r.get_kmers(4, strict=False) == ["TCAG", "CAGG", "AGGA", "GGAN"]
+    assert r.get_kmers(5, strict=False) == ["TCAGG", "CAGGA", "AGGAN"]
+    assert r.get_kmers(6, strict=False) == ["TCAGGA", "CAGGAN"]
+    assert r.get_kmers(7, strict=False) == ["TCAGGAN"]
+    assert r.get_kmers(8, strict=False) == []
+
+
+def test_get_kmers_strict_RNA():
+    orig = "UCAGGAN"
+    r = RnaSequence(orig)
+
+    assert r.get_kmers(1, strict=True) == ["U", "C", "A", "G", "G", "A"]
+    assert r.get_kmers(2, strict=True) == ["UC", "CA", "AG", "GG", "GA"]
+    assert r.get_kmers(3, strict=True) == ["UCA", "CAG", "AGG", "GGA"]
+    assert r.get_kmers(4, strict=True) == ["UCAG", "CAGG", "AGGA"]
+    assert r.get_kmers(5, strict=True) == ["UCAGG", "CAGGA"]
+    assert r.get_kmers(6, strict=True) == ["UCAGGA"]
+    assert r.get_kmers(7, strict=True) == []
+
+    assert r.get_kmers(1, strict=False) == ["U", "C", "A", "G", "G", "A", "N"]
+    assert r.get_kmers(2, strict=False) == ["UC", "CA", "AG", "GG", "GA", "AN"]
+    assert r.get_kmers(3, strict=False) == ["UCA", "CAG", "AGG", "GGA", "GAN"]
+    assert r.get_kmers(4, strict=False) == ["UCAG", "CAGG", "AGGA", "GGAN"]
+    assert r.get_kmers(5, strict=False) == ["UCAGG", "CAGGA", "AGGAN"]
+    assert r.get_kmers(6, strict=False) == ["UCAGGA", "CAGGAN"]
+    assert r.get_kmers(7, strict=False) == ["UCAGGAN"]
+    assert r.get_kmers(8, strict=False) == []
+
+
+def test_get_kmers_strict_protein():
+    orig = "CEFGMNX"
+    r = ProteinSequence(orig)
+
+    assert r.get_kmers(1, strict=True) == ["C", "E", "F", "G", "M", "N"]
+    assert r.get_kmers(2, strict=True) == ["CE", "EF", "FG", "GM", "MN"]
+    assert r.get_kmers(3, strict=True) == ["CEF", "EFG", "FGM", "GMN"]
+    assert r.get_kmers(4, strict=True) == ["CEFG", "EFGM", "FGMN"]
+    assert r.get_kmers(5, strict=True) == ["CEFGM", "EFGMN"]
+    assert r.get_kmers(6, strict=True) == ["CEFGMN"]
+
+    assert r.get_kmers(1, strict=False) == ["C", "E", "F", "G", "M", "N", "X"]
+    assert r.get_kmers(2, strict=False) == ["CE", "EF", "FG", "GM", "MN", "NX"]
+    assert r.get_kmers(3, strict=False) == ["CEF", "EFG", "FGM", "GMN", "MNX"]
+    assert r.get_kmers(4, strict=False) == ["CEFG", "EFGM", "FGMN", "GMNX"]
+    assert r.get_kmers(5, strict=False) == ["CEFGM", "EFGMN", "FGMNX"]
+    assert r.get_kmers(6, strict=False) == ["CEFGMN", "EFGMNX"]
+    assert r.get_kmers(7, strict=False) == ["CEFGMNX"]
+
+
+def test_get_kmers_strict_DNA_gaps():
+    orig = "TCA-GAT"
+    r = DnaSequence(orig)
+
+    assert r.get_kmers(1, strict=True) == ["T", "C", "A", "G", "A", "T"]
+    assert r.get_kmers(2, strict=True) == ["TC", "CA", "GA", "AT"]
+    assert r.get_kmers(3, strict=True) == ["TCA", "GAT"]
+    assert r.get_kmers(4, strict=True) == []
+
+    assert r.get_kmers(1, strict=False) == ["T", "C", "A", "-", "G", "A", "T"]
+    assert r.get_kmers(2, strict=False) == ["TC", "CA", "A-", "-G", "GA", "AT"]
+    assert r.get_kmers(3, strict=False) == ["TCA", "CA-", "A-G", "-GA", "GAT"]
+    assert r.get_kmers(4, strict=False) == ["TCA-", "CA-G", "A-GA", "-GAT"]
+    assert r.get_kmers(5, strict=False) == ["TCA-G", "CA-GA", "A-GAT"]
+    assert r.get_kmers(6, strict=False) == ["TCA-GA", "CA-GAT"]
+    assert r.get_kmers(7, strict=False) == ["TCA-GAT"]
+    assert r.get_kmers(8, strict=False) == []
+
+
+def test_get_kmers_strict_RNA_gaps():
+    orig = "UCA-GAU"
+    r = RnaSequence(orig)
+
+    assert r.get_kmers(1, strict=True) == ["U", "C", "A", "G", "A", "U"]
+    assert r.get_kmers(2, strict=True) == ["UC", "CA", "GA", "AU"]
+    assert r.get_kmers(3, strict=True) == ["UCA", "GAU"]
+    assert r.get_kmers(4, strict=True) == []
+
+    assert r.get_kmers(1, strict=False) == ["U", "C", "A", "-", "G", "A", "U"]
+    assert r.get_kmers(2, strict=False) == ["UC", "CA", "A-", "-G", "GA", "AU"]
+    assert r.get_kmers(3, strict=False) == ["UCA", "CA-", "A-G", "-GA", "GAU"]
+    assert r.get_kmers(4, strict=False) == ["UCA-", "CA-G", "A-GA", "-GAU"]
+    assert r.get_kmers(5, strict=False) == ["UCA-G", "CA-GA", "A-GAU"]
+    assert r.get_kmers(6, strict=False) == ["UCA-GA", "CA-GAU"]
+    assert r.get_kmers(7, strict=False) == ["UCA-GAU"]
+    assert r.get_kmers(8, strict=False) == []
+
+
+def test_get_kmers_strict_protein_gaps():
+    orig = "CEF-GMN"
+    r = ProteinSequence(orig)
+
+    assert r.get_kmers(1, strict=True) == ["C", "E", "F", "G", "M", "N"]
+    assert r.get_kmers(2, strict=True) == ["CE", "EF", "GM", "MN"]
+    assert r.get_kmers(3, strict=True) == ["CEF", "GMN"]
+    assert r.get_kmers(4, strict=True) == []
+
+    assert r.get_kmers(1, strict=False) == ["C", "E", "F", "-", "G", "M", "N"]
+    assert r.get_kmers(2, strict=False) == ["CE", "EF", "F-", "-G", "GM", "MN"]
+    assert r.get_kmers(3, strict=False) == ["CEF", "EF-", "F-G", "-GM", "GMN"]
+    assert r.get_kmers(4, strict=False) == ["CEF-", "EF-G", "F-GM", "-GMN"]
+    assert r.get_kmers(5, strict=False) == ["CEF-G", "EF-GM", "F-GMN"]
+    assert r.get_kmers(6, strict=False) == ["CEF-GM", "EF-GMN"]
+    assert r.get_kmers(7, strict=False) == ["CEF-GMN"]
+    assert r.get_kmers(8, strict=False) == []
+
+
+def test_get_kmers_strict_DNA_RNA_Protein_allgap():
+    orig = "-------"
+
+    r = DnaSequence(orig)
+    assert r.get_kmers(1, strict=True) == []
+    assert r.get_kmers(1, strict=False) == ["-", "-", "-", "-", "-", "-", "-"]
+
+    r = RnaSequence(orig)
+    assert r.get_kmers(1, strict=True) == []
+    assert r.get_kmers(1, strict=False) == ["-", "-", "-", "-", "-", "-", "-"]
+
+    r = ProteinSequence(orig)
+    assert r.get_kmers(1, strict=True) == []
+    assert r.get_kmers(1, strict=False) == ["-", "-", "-", "-", "-", "-", "-"]
+
+
+def test_get_kmers_strict_DNA_RNA_Protein_mixed_ambiguities():
+    r = DnaSequence("NGASTAH")
+    assert r.get_kmers(1, strict=True) == ["G", "A", "T", "A"]
+    assert r.get_kmers(2, strict=True) == ["GA", "TA"]
+    assert r.get_kmers(3, strict=True) == []
+
+    assert r.get_kmers(1, strict=False) == ["N", "G", "A", "S", "T", "A", "H"]
+    assert r.get_kmers(2, strict=False) == ["NG", "GA", "AS", "ST", "TA", "AH"]
+    assert r.get_kmers(3, strict=False) == ["NGA", "GAS", "AST", "STA", "TAH"]
+    assert r.get_kmers(4, strict=False) == ["NGAS", "GAST", "ASTA", "STAH"]
+    assert r.get_kmers(5, strict=False) == ["NGAST", "GASTA", "ASTAH"]
+    assert r.get_kmers(6, strict=False) == ["NGASTA", "GASTAH"]
+    assert r.get_kmers(7, strict=False) == ["NGASTAH"]
+    assert r.get_kmers(8, strict=False) == []
+
+    r = RnaSequence("RGAWUAD")
+    assert r.get_kmers(1, strict=True) == ["G", "A", "U", "A"]
+    assert r.get_kmers(2, strict=True) == ["GA", "UA"]
+    assert r.get_kmers(3, strict=True) == []
+    assert r.get_kmers(1, strict=False) == ["R", "G", "A", "W", "U", "A", "D"]
+    assert r.get_kmers(2, strict=False) == ["RG", "GA", "AW", "WU", "UA", "AD"]
+    assert r.get_kmers(3, strict=False) == ["RGA", "GAW", "AWU", "WUA", "UAD"]
+    assert r.get_kmers(4, strict=False) == ["RGAW", "GAWU", "AWUA", "WUAD"]
+    assert r.get_kmers(5, strict=False) == ["RGAWU", "GAWUA", "AWUAD"]
+    assert r.get_kmers(6, strict=False) == ["RGAWUA", "GAWUAD"]
+    assert r.get_kmers(7, strict=False) == ["RGAWUAD"]
+    assert r.get_kmers(8, strict=False) == []
+
+    r = ProteinSequence("BQMXNRZ")
+    assert r.get_kmers(1, strict=True) == ["Q", "M", "N", "R"]
+    assert r.get_kmers(2, strict=True) == ["QM", "NR"]
+    assert r.get_kmers(3, strict=True) == []
+    assert r.get_kmers(1, strict=False) == ["B", "Q", "M", "X", "N", "R", "Z"]
+    assert r.get_kmers(2, strict=False) == ["BQ", "QM", "MX", "XN", "NR", "RZ"]
+    assert r.get_kmers(3, strict=False) == ["BQM", "QMX", "MXN", "XNR", "NRZ"]
+    assert r.get_kmers(4, strict=False) == ["BQMX", "QMXN", "MXNR", "XNRZ"]
+    assert r.get_kmers(5, strict=False) == ["BQMXN", "QMXNR", "MXNRZ"]
+    assert r.get_kmers(6, strict=False) == ["BQMXNR", "QMXNRZ"]
+    assert r.get_kmers(7, strict=False) == ["BQMXNRZ"]
+    assert r.get_kmers(8, strict=False) == []
+
+
 @pytest.fixture(scope="function")
 def one_seq():
     from cogent3 import make_seq