Repository: cogent3/cogent3
Pull request #2121 (2024-11-21 00:12:12)

DEV: remove deprecated code
[CHANGED] modified the deserialise_map_spans function to
     convert the return type to always be FeatureMap,
     drops attributes that are no longer supported on FeatureMap,
     such as termini_unknown and tidy.

## Summary by Sourcery

Remove deprecated code and update deserialise_map_spans to return FeatureMap consistently, eliminating unsupported attributes.

Enhancements:
- Modified the deserialise_map_spans function to ensure the return type is always FeatureMap and removed unsupported attributes like termini_unknown and tidy.

Chores:
- Removed deprecated code across multiple modules, including the Map class and several deprecated functions and methods.


Patch:
diff --git a/changelog.d/20241121_100849_Gavin.Huttley.md b/changelog.d/20241121_100849_Gavin.Huttley.md
new file mode 100644
index 000000000..a615940a5
--- /dev/null
+++ b/changelog.d/20241121_100849_Gavin.Huttley.md
@@ -0,0 +1,41 @@
+<!--
+A new scriv changelog fragment.
+
+Uncomment the section that is right (remove the HTML comment wrapper).
+-->
+
+<!--
+### Contributors
+
+- A bullet item for the Contributors category.
+
+-->
+<!--
+### ENH
+
+- A bullet item for the ENH category.
+
+-->
+<!--
+### BUG
+
+- A bullet item for the BUG category.
+
+-->
+<!--
+### DOC
+
+- A bullet item for the DOC category.
+
+-->
+<!--
+### Deprecations
+
+- A bullet item for the Deprecations category.
+
+-->
+### Discontinued
+
+- We no longer support python 3.9.
+
+-->
diff --git a/src/cogent3/core/annotation_db.py b/src/cogent3/core/annotation_db.py
index f4b1f0ecc..b8cb4eb1d 100644
--- a/src/cogent3/core/annotation_db.py
+++ b/src/cogent3/core/annotation_db.py
@@ -106,11 +106,6 @@ class SerialisableType(typing.Protocol):  # pragma: no cover
 @typing.runtime_checkable
 class SupportsQueryFeatures(typing.Protocol):  # pragma: no cover
     # should be defined centrally
-    @c3warn.deprecated_args(
-        "2024.9",
-        reason="replace usage of SQL keyword as column name",
-        old_new=[("end", "stop")],
-    )
     def get_features_matching(
         self,
         *,
@@ -124,11 +119,6 @@ class SupportsQueryFeatures(typing.Protocol):  # pragma: no cover
         on_alignment: OptionalBool = None,
     ) -> typing.Iterator[FeatureDataType]: ...
 
-    @c3warn.deprecated_args(
-        "2024.9",
-        reason="replace usage of SQL keyword as column name",
-        old_new=[("end", "stop")],
-    )
     def get_feature_children(
         self,
         *,
@@ -138,11 +128,6 @@ class SupportsQueryFeatures(typing.Protocol):  # pragma: no cover
         **kwargs,
     ) -> typing.List[FeatureDataType]: ...
 
-    @c3warn.deprecated_args(
-        "2024.9",
-        reason="replace usage of SQL keyword as column name",
-        old_new=[("end", "stop")],
-    )
     def get_feature_parent(
         self,
         *,
@@ -163,11 +148,6 @@ class SupportsQueryFeatures(typing.Protocol):  # pragma: no cover
         on_alignment: OptionalBool = None,
     ) -> int: ...
 
-    @c3warn.deprecated_args(
-        "2024.9",
-        reason="replace usage of SQL keyword as column name",
-        old_new=[("end", "stop")],
-    )
     def subset(
         self,
         *,
@@ -806,11 +786,6 @@ class SqliteAnnotationDbMixin:
                         )  # remove invalid field for the FeatureDataType
                         yield parent
 
-    @c3warn.deprecated_args(
-        "2024.9",
-        reason="replace usage of SQL keyword as column name",
-        old_new=[("end", "stop")],
-    )
     def get_records_matching(
         self,
         *,
@@ -835,11 +810,6 @@ class SqliteAnnotationDbMixin:
             for result in self._get_records_matching(table_name, **kwargs):
                 yield dict(zip(result.keys(), result))
 
-    @c3warn.deprecated_args(
-        "2024.9",
-        reason="replace usage of SQL keyword as column name",
-        old_new=[("end", "stop")],
-    )
     def get_features_matching(
         self,
         *,
@@ -1133,11 +1103,6 @@ class SqliteAnnotationDbMixin:
             self.db.backup(backup)
         backup.close()
 
-    @c3warn.deprecated_args(
-        "2024.9",
-        reason="replace usage of SQL keyword as column name",
-        old_new=[("end", "stop")],
-    )
     def subset(
         self,
         *,
@@ -1469,11 +1434,6 @@ class GenbankAnnotationDb(SqliteAnnotationDbMixin):
         self._num_fakeids += 1
         return name
 
-    @c3warn.deprecated_args(
-        "2024.9",
-        reason="replace usage of SQL keyword as column name",
-        old_new=[("end", "stop")],
-    )
     def get_feature_children(
         self,
         name: str,
@@ -1522,11 +1482,6 @@ class GenbankAnnotationDb(SqliteAnnotationDbMixin):
                 feat.pop("parent_id")  # remove invalid field for the FeatureDataType
                 yield feat
 
-    @c3warn.deprecated_args(
-        "2024.9",
-        reason="replace usage of SQL keyword as column name",
-        old_new=[("end", "stop")],
-    )
     def get_feature_parent(
         self,
         name: str,
@@ -1602,7 +1557,7 @@ def convert_annotation_to_annotation_db(data: dict) -> SupportsFeatures:
         ann = ann.pop("annotation_construction")
         m = deserialise_map_spans(ann.pop("map"))
         spans = m.get_coordinates()
-        strand = "-" if m.reverse else "+"
+        strand = "-" if any(s.reverse for s in m.spans) else "+"
         biotype = ann.pop("type")
         name = ann.pop("name")
         db.add_feature(
diff --git a/src/cogent3/core/location.py b/src/cogent3/core/location.py
index aefb1d4a0..c81b7660a 100644
--- a/src/cogent3/core/location.py
+++ b/src/cogent3/core/location.py
@@ -42,7 +42,7 @@ def as_map(slice, length, cls):
         for i in slice:
             spans.extend(as_map(i, length, cls).spans)
         return cls(spans=spans, parent_length=length)
-    elif isinstance(slice, (FeatureMap, IndelMap, Map)):
+    elif isinstance(slice, (FeatureMap, IndelMap)):
         return slice
     else:
         lo, hi, step = _norm_slice(slice, length)
@@ -503,356 +503,6 @@ SeqSpanTypes = Sequence[SpanTypes]
 SeqCoordTypes = Sequence[Sequence[IntTypes]]
 
 
-class Map:  # pragma: no cover
-    """A map holds a list of spans."""
-
-    @c3warn.deprecated_callable(
-        version="2024.6",
-        reason="Replaced by IndelMap and FeatureMap",
-        is_discontinued=True,
-    )
-    def __init__(
-        self,
-        locations=None,
-        spans=None,
-        tidy=False,
-        parent_length=None,
-        termini_unknown=False,
-    ):
-        assert parent_length is not None
-        d = locals()
-        exclude = ("self", "__class__", "__slots__")
-        self._serialisable = {k: v for k, v in d.items() if k not in exclude}
-
-        if spans is None:
-            spans = []
-            for start, end in locations:
-                diff = 0
-                reverse = start > end
-                if max(start, end) < 0 or min(start, end) > parent_length:
-                    raise RuntimeError(
-                        f"located outside sequence: {(start, end, parent_length)}"
-                    )
-                if max(start, end) > parent_length and min(start, end) < 0:
-                    l_diff = min(start, end)
-                    r_diff = max(start, end) - parent_length
-                    start, end = (
-                        (0, parent_length) if start < end else (parent_length, 0)
-                    )
-                    spans += [
-                        LostSpan(abs(l_diff)),
-                        Span(start, end, tidy, tidy, reverse=reverse),
-                        LostSpan(abs(r_diff)),
-                    ]
-                elif min(start, end) < 0:
-                    diff = min(start, end)
-                    start = 0 if start < 0 else start
-                    end = 0 if end < 0 else end
-                    spans += [
-                        LostSpan(abs(diff)),
-                        Span(start, end, tidy, tidy, reverse=reverse),
-                    ]
-                elif max(start, end) > parent_length:
-                    diff = max(start, end) - parent_length
-                    start = parent_length if start > parent_length else start
-                    end = parent_length if end > parent_length else end
-                    spans += [
-                        Span(start, end, tidy, tidy, reverse=reverse),
-                        LostSpan(abs(diff)),
-                    ]
-                else:
-                    spans += [Span(start, end, tidy, tidy, reverse=reverse)]
-
-        self.offsets = []
-        self.useful = False
-        self.complete = True
-        self.reverse = None
-        posn = 0
-        for span in spans:
-            self.offsets.append(posn)
-            posn += span.length
-            if span.lost:
-                self.complete = False
-            elif not self.useful:
-                self.useful = True
-                (self.start, self.end) = (span.start, span.end)
-                self.reverse = span.reverse
-            else:
-                self.start = min(self.start, span.start)
-                self.end = max(self.end, span.end)
-                if self.reverse is not None and (span.reverse != self.reverse):
-                    self.reverse = None
-
-        if termini_unknown:
-            spans = list(spans)
-            if spans[0].lost:
-                spans[0] = TerminalPadding(spans[0].length)
-            if spans[-1].lost:
-                spans[-1] = TerminalPadding(spans[-1].length)
-
-        self.spans = tuple(spans)
-        self.length = posn
-        self.parent_length = parent_length
-        self.__inverse = None
-
-    def __len__(self):
-        return self.length
-
-    def __repr__(self):
-        return repr(list(self.spans)) + f"/{self.parent_length}"
-
-    def __getitem__(self, slice):
-        # A possible shorter map at the same level
-        slice = as_map(slice, len(self), self.__class__)
-        new_parts = []
-        for span in slice.spans:
-            new_parts.extend(span.remap_with(self))
-        return Map(spans=new_parts, parent_length=self.parent_length)
-
-    def __mul__(self, scale):
-        # For Protein -> DNA
-        new_parts = []
-        for span in self.spans:
-            new_parts.append(span * scale)
-        return Map(spans=new_parts, parent_length=self.parent_length * scale)
-
-    def __div__(self, scale):
-        # For DNA -> Protein
-        new_parts = []
-        for span in self.spans:
-            new_parts.append(span / scale)
-        return Map(spans=new_parts, parent_length=self.parent_length // scale)
-
-    def __add__(self, other):
-        if other.parent_length != self.parent_length:
-            raise ValueError("Those maps belong to different sequences")
-        return Map(spans=self.spans + other.spans, parent_length=self.parent_length)
-
-    def with_termini_unknown(self):
-        return Map(
-            self,
-            spans=self.spans[:],
-            parent_length=self.parent_length,
-            termini_unknown=True,
-        )
-
-    def get_covering_span(self):
-        if self.reverse:
-            span = (self.end, self.start)
-        else:
-            span = (self.start, self.end)
-        return Map([span], parent_length=self.parent_length)
-
-    def covered(self):
-        """>>> Map([(10,20), (15, 25), (80, 90)]).covered().spans
-        [Span(10,25), Span(80, 90)]"""
-
-        delta = {}
-        for span in self.spans:
-            if span.lost:
-                continue
-            delta[span.start] = delta.get(span.start, 0) + 1
-            delta[span.end] = delta.get(span.end, 0) - 1
-        positions = list(delta.keys())
-        positions.sort()
-        last_y = y = 0
-        last_x = start = None
-        result = []
-        for x in positions:
-            y += delta[x]
-            if x == last_x:
-                continue
-            if y and not last_y:
-                assert start is None
-                start = x
-            elif last_y and not y:
-                result.append((start, x))
-                start = None
-            last_x = x
-            last_y = y
-        assert y == 0
-        return Map(locations=result, parent_length=self.parent_length)
-
-    def reversed(self):
-        """Reversed location on same parent"""
-        spans = [s.reversed() for s in self.spans]
-        spans.reverse()
-        return Map(spans=spans, parent_length=self.parent_length)
-
-    def nucleic_reversed(self):
-        """Same location on reversed parent"""
-        spans = [s.reversed_relative_to(self.parent_length) for s in self.spans]
-        return Map(spans=spans, parent_length=self.parent_length)
-
-    def get_gap_coordinates(self):
-        """returns [(gap pos, gap length), ...]"""
-        gap_pos = []
-        for i, span in enumerate(self.spans):
-            if not span.lost:
-                continue
-
-            pos = self.spans[i - 1].end if i else 0
-            gap_pos.append((pos, len(span)))
-
-        return gap_pos
-
-    def gaps(self):
-        """The gaps (lost spans) in this map"""
-        locations = []
-        offset = 0
-        for s in self.spans:
-            if s.lost:
-                locations.append((offset, offset + s.length))
-            offset += s.length
-        return Map(locations, parent_length=len(self))
-
-    def shadow(self):
-        """The 'negative' map of the spans not included in this map"""
-        return self.inverse().gaps()
-
-    def nongap(self):
-        locations = []
-        offset = 0
-        for s in self.spans:
-            if not s.lost:
-                locations.append((offset, offset + s.length))
-            offset += s.length
-        return Map(locations, parent_length=len(self))
-
-    def without_gaps(self):
-        return Map(
-            spans=[s for s in self.spans if not s.lost],
-            parent_length=self.parent_length,
-        )
-
-    def inverse(self):
-        if self.__inverse is None:
-            self.__inverse = self._inverse()
-        return self.__inverse
-
-    def _inverse(self):
-        # can't work if there are overlaps in the map
-        # tidy ends don't survive inversion
-        if self.parent_length is None:
-            raise ValueError("Uninvertable. parent length not known")
-        posn = 0
-        temp = []
-        for span in self.spans:
-            if not span.lost:
-                if span.reverse:
-                    temp.append((span.start, span.end, posn + span.length, posn))
-                else:
-                    temp.append((span.start, span.end, posn, posn + span.length))
-            posn += span.length
-
-        temp.sort()
-        new_spans = []
-        last_hi = 0
-        for lo, hi, start, end in temp:
-            if lo > last_hi:
-                new_spans.append(LostSpan(lo - last_hi))
-            elif lo < last_hi:
-                raise ValueError(f"Uninvertable. Overlap: {lo} < {last_hi}")
-            new_spans.append(Span(start, end, reverse=start > end))
-            last_hi = hi
-        if self.parent_length > last_hi:
-            new_spans.append(LostSpan(self.parent_length - last_hi))
-        return Map(spans=new_spans, parent_length=len(self))
-
-    def get_coordinates(self):
-        """returns span coordinates as [(v1, v2), ...]
-
-        v1/v2 are (start, end) unless the map is reversed, in which case it will
-        be (end, start)"""
-
-        if self.reverse:
-            order_func = lambda x: (max(x), min(x))
-        else:
-            order_func = lambda x: x
-
-        coords = list(
-            map(order_func, [(s.start, s.end) for s in self.spans if not s.lost])
-        )
-
-        return coords
-
-    def to_rich_dict(self):
-        """returns dicts for contained spans [dict(), ..]"""
-        spans = [s.to_rich_dict() for s in self.spans]
-        data = copy.deepcopy(self._serialisable)
-        data.pop("locations", None)
-        data["spans"] = spans
-        data["type"] = get_object_provenance(self)
-        data["version"] = __version__
-        return data
-
-    def zeroed(self):
-        """returns a new instance with the first span starting at 0
-
-        Note
-        ----
-
-        Useful when an Annotatable object is sliced, but the connection to
-        the original parent is being deliberately broken as in the
-        Sequence.deepcopy(sliced=True) case.
-        """
-        # todo there's probably a more efficient way to do this
-        # create the new instance
-        from cogent3.util.deserialise import deserialise_map_spans
-
-        data = self.to_rich_dict()
-        zeroed = deserialise_map_spans(data)
-        zeroed.parent_length = len(zeroed.get_covering_span())
-        shift = min(zeroed.start, zeroed.end)
-        new_end = 0
-        for span in zeroed.spans:
-            if span.lost:
-                continue
-            span.start -= shift
-            span.end -= shift
-            new_end = max(new_end, span.end)
-
-        zeroed.start = 0
-        zeroed.end = new_end
-
-        return zeroed
-
-    T = Union[numpy.ndarray, int]
-
-    def absolute_position(self, rel_pos: T) -> T:
-        """converts rel_pos into an absolute position
-
-        Raises
-        ------
-        raises ValueError if rel_pos < 0
-        """
-        check = (
-            numpy.array([rel_pos], dtype=int) if isinstance(rel_pos, int) else rel_pos
-        )
-        if check.min() < 0:
-            raise ValueError(f"must positive, not {rel_pos=}")
-
-        if len(self) == self.parent_length:
-            # handle case of reversed here?
-            return rel_pos
-
-        return self.start + rel_pos
-
-    def relative_position(self, abs_pos: T) -> T:
-        """converts abs_pos into an relative position
-
-        Raises
-        ------
-        raises ValueError if abs_pos < 0
-        """
-        check = (
-            numpy.array([abs_pos], dtype=int) if isinstance(abs_pos, int) else abs_pos
-        )
-        if check.min() < 0:
-            raise ValueError(f"must positive, not {abs_pos=}")
-        return abs_pos - self.start
-
-
 class MapABC(ABC):
     """base class for genomic map objects"""
 
@@ -1806,7 +1456,7 @@ class IndelMap(MapABC):
         )
 
     def to_feature_map(self) -> "FeatureMap":
-        """returns a Map type, suited to Features"""
+        """returns a FeatureMap type, suited to Features"""
         return FeatureMap(spans=list(self.spans), parent_length=self.parent_length)
 
     def make_seq_feature_map(self, align_feature_map: "FeatureMap") -> "FeatureMap":
@@ -2166,7 +1816,7 @@ class FeatureMap(MapABC):
         )
 
     def covered(self) -> "FeatureMap":
-        """>>> Map([(10,20), (15, 25), (80, 90)]).covered().spans
+        """>>> FeatureMap([(10,20), (15, 25), (80, 90)]).covered().spans
         [Span(10,25), Span(80, 90)]"""
 
         delta = {}
@@ -2310,11 +1960,7 @@ class FeatureMap(MapABC):
         return self.__class__(spans=new_spans, parent_length=len(self))
 
     def get_coordinates(self) -> SeqCoordTypes:
-        """returns span coordinates as [(v1, v2), ...]
-
-        v1/v2 are (start, end) unless the map is reversed, in which case it will
-        be (end, start)"""
-
+        """returns span coordinates as [(v1, v2), ...]"""
         return [(s.start, s.end) for s in self.spans if not s.lost]
 
     def to_rich_dict(self) -> dict[str, Any]:
@@ -2434,7 +2080,7 @@ def gap_coords_to_map(
 
     Returns
     -------
-    Map
+    IndelMap instance
     """
 
     if not gaps_lengths:
@@ -2458,6 +2104,10 @@ def deserialise_featuremap(data: dict) -> FeatureMap:
     return FeatureMap.from_rich_dict(data)
 
 
+@register_deserialiser("cogent3.core.location.Map")
+def deserialise_map(data: dict) -> FeatureMap: ...
+
+
 @functools.singledispatch
 def _norm_slice(index, length: int) -> tuple[int, int, Union[int, None]]:
     """_norm_slice(slice(1, -2, 3), 10) -> (1,8,3)"""
diff --git a/src/cogent3/core/new_alignment.py b/src/cogent3/core/new_alignment.py
index 5636362c8..deff17534 100644
--- a/src/cogent3/core/new_alignment.py
+++ b/src/cogent3/core/new_alignment.py
@@ -185,13 +185,19 @@ class SeqDataView(new_sequence.SeqView):
     @property
     def array_value(self) -> numpy.ndarray:
         """returns the sequence as a numpy array"""
+        # todo: kath, in ADV, the .get_seq_str method gets passed the step and
+        # the returned sequence is sliced by the step. In SDV, the step is not
+        # applied in the get_seq_str method, but is applied in this method here.
+        # can we make this consistent?
+
+        # also, keep using parent_start and parent_stop or to use start and stop?
+        # this is another inconsistency between SDV and ADV
         raw = self.parent.get_seq_array(
             seqid=self.seqid,
             start=self.slice_record.plus_start,
             stop=self.slice_record.plus_stop,
-            step=self.slice_record.plus_step,
         )
-        return raw[::-1] if self.slice_record.is_reversed else raw
+        return raw if self.slice_record.step == 1 else raw[:: self.slice_record.step]
 
     @property
     def bytes_value(self) -> bytes:
@@ -384,50 +390,21 @@ class SeqsData(SeqsDataABC):
         return self._data[seqid].shape[0]
 
     def get_seq_array(
-        self,
-        *,
-        seqid: str,
-        start: OptInt = None,
-        stop: OptInt = None,
-        step: OptInt = None,
+        self, *, seqid: str, start: OptInt = None, stop: OptInt = None
     ) -> numpy.ndarray:
-        start = start or 0
-        stop = stop if stop is not None else self.get_seq_length(seqid)
-        step = step or 1
-
-        assert (
-            start >= 0 and stop >= 0 and step > 0
-        ), "start, stop, and step must be >= 0"
-
-        out_len = (stop - start + step - 1) // step
-        out = numpy.empty(out_len, dtype=self.alphabet.dtype)
-
-        out[:] = self._data[seqid][start:stop:step]
-        return out
+        return self._data[seqid][start:stop]
 
     def get_seq_str(
-        self,
-        *,
-        seqid: str,
-        start: OptInt = None,
-        stop: OptInt = None,
-        step: OptInt = None,
+        self, *, seqid: str, start: OptInt = None, stop: OptInt = None
     ) -> str:
         return self._alphabet.from_indices(
-            self.get_seq_array(seqid=seqid, start=start, stop=stop, step=step)
+            self.get_seq_array(seqid=seqid, start=start, stop=stop)
         )
 
     def get_seq_bytes(
-        self,
-        *,
-        seqid: str,
-        start: OptInt = None,
-        stop: OptInt = None,
-        step: OptInt = None,
+        self, *, seqid: str, start: OptInt = None, stop: OptInt = None
     ) -> bytes:
-        return self.get_seq_str(seqid=seqid, start=start, stop=stop, step=step).encode(
-            "utf8"
-        )
+        return self.get_seq_str(seqid=seqid, start=start, stop=stop).encode("utf8")
 
     def get_view(self, seqid: str) -> SeqDataView:
         seq_len = len(self._data[seqid])
@@ -2901,9 +2878,8 @@ class Aligned:
 
 
 class AlignedSeqsDataABC(SeqsDataABC):
-    # all methods that are from SeqsDataABC should work in sequence coordinates,
-    # all methods unique to AlignedSeqsDataABC should work in aligned coordinates.
-    # all indices provided to AlignedSeqsDataABC should be on the plus strand.
+    # all methods that are from SeqsDataABC should work in sequence coordinates
+    # all methods unique to AlignedSeqsDataABC should work in aligned coordinates
     __slots__ = ()
 
     @classmethod
@@ -3326,20 +3302,8 @@ class AlignedSeqsData(AlignedSeqsDataABC):
         """Return ungapped sequence corresponding to seqid as an array of indices.
         assumes start/stop are in sequence coordinates. Excludes gaps.
         """
-        start = start or 0
-        stop = stop if stop is not None else self.get_seq_length(seqid)
-        step = step or 1
-
-        assert (
-            start >= 0 and stop >= 0 and step > 0
-        ), "start, stop, and step must be >= 0"
-
-        out_len = (stop - start + step - 1) // step
-
-        seq = numpy.empty(out_len, dtype=self.alphabet.dtype)
-        seq[:] = self._get_ungapped(seqid)[start:stop:step]
-
-        return seq
+        seq = self._get_ungapped(seqid)
+        return seq[start:stop:step]
 
     def get_gapped_seq_array(
         self,
@@ -3352,19 +3316,15 @@ class AlignedSeqsData(AlignedSeqsDataABC):
         """Return sequence data corresponding to seqid as an array of indices.
         start/stop are in alignment coordinates. Includes gaps.
         """
-        start = start or 0
-        stop = stop if stop is not None else self.align_len
-        step = step or 1
-        assert (
-            start >= 0 and stop >= 0 and step > 0
-        ), "start, stop, and step must be >= 0"
-
+        # reminder: design
+        # this is recreating the orignal seq, so will be memory and compute inefficient
+        # better to build the smallest gapped seq represented by the start/stop
+        # determine the seq coordinates for slicing the ungapped sequence then apply the step
+        # note with the latter approach, if the step is negative, need to apply the step as
+        # a positive integer than return the reversed array
         index = self._name_to_index[seqid]
-        out_len = (stop - start + step - 1) // step
-        gapped = numpy.empty(out_len, dtype=self.alphabet.dtype)
-        gapped[:] = self._gapped[index][start:stop:step]
-
-        return gapped
+        gapped = self._gapped[index]
+        return gapped[start:stop:step]
 
     def get_seq_str(
         self,
@@ -3665,39 +3625,57 @@ class AlignedDataView(new_sequence.SeqViewABC):
 
     @property
     def str_value(self) -> str:
-        return self.alphabet.from_indices(self.array_value)
+        return self.parent.get_seq_str(
+            seqid=self.seqid,
+            start=self.slice_record.start,
+            stop=self.slice_record.stop,
+            step=self.slice_record.step,
+        )
 
     @property
     def gapped_str_value(self) -> str:
-        return self.alphabet.from_indices(self.gapped_array_value)
+        return self.parent.get_gapped_seq_str(
+            seqid=self.seqid,
+            start=self.slice_record.start,
+            stop=self.slice_record.stop,
+            step=self.slice_record.step,
+        )
 
     @property
     def array_value(self) -> numpy.ndarray:
-        value = self.parent.get_seq_array(
+        return self.parent.get_seq_array(
             seqid=self.seqid,
-            start=self.map.get_seq_index(self.slice_record.plus_start),
-            stop=self.map.get_seq_index(self.slice_record.plus_stop),
-            step=self.map.get_seq_index(self.slice_record.plus_step),
+            start=self.slice_record.start,
+            stop=self.slice_record.stop,
+            step=self.slice_record.step,
         )
-        return value[::-1] if self.slice_record.is_reversed else value
 
     @property
     def gapped_array_value(self) -> numpy.ndarray:
-        value = self.parent.get_gapped_seq_array(
+        return self.parent.get_gapped_seq_array(
             seqid=self.seqid,
-            start=self.slice_record.plus_start,
-            stop=self.slice_record.plus_stop,
-            step=self.slice_record.plus_step,
+            start=self.slice_record.start,
+            stop=self.slice_record.stop,
+            step=self.slice_record.step,
         )
-        return value[::-1] if self.slice_record.is_reversed else value
 
     @property
     def bytes_value(self) -> bytes:
-        return self.str_value.encode("utf8")
+        return self.parent.get_seq_bytes(
+            seqid=self.seqid,
+            start=self.slice_record.start,
+            stop=self.slice_record.stop,
+            step=self.slice_record.step,
+        )
 
     @property
     def gapped_bytes_value(self) -> bytes:
-        return self.gapped_str_value.encode("utf8")
+        return self.parent.get_gapped_seq_bytes(
+            seqid=self.seqid,
+            start=self.slice_record.start,
+            stop=self.slice_record.stop,
+            step=self.slice_record.step,
+        )
 
     def __str__(self) -> str:
         return self.str_value
diff --git a/src/cogent3/core/new_sequence.py b/src/cogent3/core/new_sequence.py
index 265743935..ecd82a331 100644
--- a/src/cogent3/core/new_sequence.py
+++ b/src/cogent3/core/new_sequence.py
@@ -2113,17 +2113,9 @@ class SliceRecordABC(ABC):
     def plus_start(self) -> int:
         """start on plus strand"""
         if self.is_reversed:
-            # all indices are negative, so the stop becomes the start. To convert
-            # to positive indices, we add the length of the parent sequence to
-            # the stop. Note that when abs(self.step) > 1, we instead calculate
-            # the "true stop", i.e., the index that immediately follows (in the
-            # direction of the step) the last selected index in the slice. We then
-            # add the abs(self.step) to account for the fact that the stop uses
-            # exclusive indexing, and the start is inclusive.
-
+            # self.stop becomes the start, self.stop will be negative
             assert self.stop < 0, "expected stop on reverse strand SeqView < 0"
-            stop = self.stop if self.step == -1 else self.start + len(self) * self.step
-            start = self.parent_len + stop + abs(self.step)
+            start = self.stop + self.parent_len + 1
         else:
             start = self.start
         return start
@@ -2136,13 +2128,7 @@ class SliceRecordABC(ABC):
             assert self.start < 0, "expected start on reverse strand SeqView < 0"
             stop = self.start + self.parent_len + 1
         else:
-            # we ensure that the plus_stop is the index that immediately follows
-            # the last selected index in the slice.
-            stop = (
-                self.stop
-                if self.step == 1
-                else (self.start + len(self) * self.step - self.step) + 1
-            )
+            stop = self.stop
         return stop
 
     @property
@@ -2455,10 +2441,12 @@ class SliceRecordABC(ABC):
     def _get_reverse_slice_from_reverse_seqview_(
         self, slice_start: int, slice_stop: int, step: int, **kwargs
     ):
-        # refactor: simplify
-        # are there other places in slice_record where we can use plus_start/stop/step
+        # max start is "true stop" + abs(step), because stop is not inclusive
+        # "true stop" adjust for if abs(stop-start) % step != 0
         if slice_start >= len(self):
-            start = self.plus_start
+            start = (
+                self.parent_len + self.start + len(self) * self.step + abs(self.step)
+            )
         elif slice_start >= 0:
             start = self.parent_len + (self.start + slice_start * self.step)
         else:
diff --git a/src/cogent3/format/fasta.py b/src/cogent3/format/fasta.py
index eba3ccd54..c5e143a26 100644
--- a/src/cogent3/format/fasta.py
+++ b/src/cogent3/format/fasta.py
@@ -12,9 +12,6 @@ def _iter_in_block_size(series: str, block_size: int) -> Iterable[str]:
         yield series[i : i + block_size]
 
 
-@c3warn.deprecated_args(
-    "2024.9", reason="better name", old_new=[("alignment_dict", "seqs")]
-)
 def seqs_to_fasta(
     seqs: dict[str, str],
     block_size: int = 60,
@@ -50,64 +47,3 @@ def seqs_to_fasta(
     if result:
         result.append("")
     return "\n".join(result)
-
-
-@c3warn.deprecated_callable("2024.9", reason="better name", new="seqs_to_fasta")
-def alignment_to_fasta(
-    alignment_dict: dict[str, str],
-    block_size: int = 60,
-    order: Optional[list[str]] = None,
-) -> str:  # pragma: no cover
-    """use seqs_to_fasta() instead"""
-    return seqs_to_fasta(seqs=alignment_dict, block_size=block_size, order=order)
-
-
-@c3warn.deprecated_callable(
-    "2024.9", reason="inefficient", is_discontinued=True, new="seqs_to_fasta"
-)
-class FastaFormatter(_AlignmentFormatter):  # pragma: no cover
-    def format(
-        self,
-        alignment_dict: dict[str, str],
-        block_size: int = 60,
-        order: Optional[list[str]] = None,
-    ) -> str:
-        """Format the alignment to Fasta.
-
-        Parameters
-        ----------
-        alignment_dict
-            dict of seq_name
-        block_size
-            the sequence length to write to each line,
-            by default 60
-        order
-            optional list of sequence names, which order to print in.
-            Assumes complete and correct list of names,
-            by default None
-
-        Returns
-        -------
-        The alignment in the Fasta format.
-        """
-        # setup
-        if not order:
-            order = list(alignment_dict.keys())
-        self.set_align_info(alignment_dict, order)
-        self.set_block_size(block_size)
-
-        if len(alignment_dict) == 0:
-            return ""
-
-        return "".join(
-            [
-                ">%s\n%s"
-                % (
-                    seq,
-                    self.wrap_string_to_block_size(
-                        alignment_dict[seq], alt_block_size=block_size
-                    ),
-                )
-                for seq in self.align_order
-            ]
-        )
diff --git a/src/cogent3/maths/stats/distribution.py b/src/cogent3/maths/stats/distribution.py
index 9613e5d4d..63b5731a2 100644
--- a/src/cogent3/maths/stats/distribution.py
+++ b/src/cogent3/maths/stats/distribution.py
@@ -43,41 +43,6 @@ def tprob(x, df):
     return 2 * t.sf(abs(x), df)
 
 
-@c3warn.deprecated_callable(
-    version="2024.9",
-    reason="use scipy.stats.poisson.sf() instead",
-    is_discontinued=True,
-)
-def poisson_high(successes, mean):  # pragma: no cover
-    """being removed"""
-    return pdtrc(successes, mean)
-
-
-@c3warn.deprecated_callable(
-    version="2024.9",
-    reason="use scipy.stats.poisson.cdf() instead",
-    is_discontinued=True,
-)
-def poisson_low(successes, mean):  # pragma: no cover
-    """being removed"""
-    return pdtr(successes, mean)
-
-
-@c3warn.deprecated_callable(
-    version="2024.9",
-    reason="use scipy.stats.poisson.pmf() instead",
-    is_discontinued=True,
-)
-def poisson_exact(successes, mean):  # pragma: no cover
-    """being removed"""
-    if successes == 0:
-        return pdtr(0, mean)
-    elif successes < mean:  # use left tail
-        return pdtr(successes, mean) - pdtr(successes - 1, mean)
-    else:  # successes > mean: use right tail
-        return pdtrc(successes - 1, mean) - pdtrc(successes, mean)
-
-
 def binomial_exact(successes, trials, prob):
     """Returns binomial probability of exactly X successes.
 
diff --git a/src/cogent3/maths/stats/special.py b/src/cogent3/maths/stats/special.py
index 460141d4c..11f08c98b 100644
--- a/src/cogent3/maths/stats/special.py
+++ b/src/cogent3/maths/stats/special.py
@@ -59,122 +59,6 @@ def one_minus_exp(x):
         return 1 - exp(x)
 
 
-@c3warn.deprecated_callable(
-    version="2024.9", reason="use scipy.special.perm() instead", is_discontinued=True
-)
-def permutations(n, k):  # pragma: no cover
-    """Returns the number of ways of choosing k items from n, in order.
-
-    Defined as n!/(n-k)!.
-    """
-    # Validation: k must be be between 0 and n (inclusive), and n must be >=0.
-    if k > n:
-        raise IndexError(f"Can't choose {k} items from {n}")
-    elif k < 0:
-        raise IndexError("Can't choose negative number of items")
-    elif n < 0:
-        raise IndexError("Can't choose from negative number of items")
-    if min(n, k) < 20 and isinstance(n, int) and isinstance(k, int):
-        return permutations_exact(n, k)
-    else:
-        return exp(ln_permutations(n, k))
-
-
-@c3warn.deprecated_callable(
-    version="2024.9", reason="use scipy.special.perm() instead", is_discontinued=True
-)
-def permutations_exact(n, k):  # pragma: no cover
-    """Calculates permutations by integer division.
-
-    Preferred method for small permutations, but slow on larger ones.
-
-    Note: no error checking (expects to be called through permutations())
-    """
-    product = 1
-    for i in range(n - k + 1, n + 1):
-        product *= i
-    return product
-
-
-@c3warn.deprecated_callable(
-    version="2024.9", reason="use scipy.special.perm() instead", is_discontinued=True
-)
-def ln_permutations(n, k):  # pragma: no cover
-    """Calculates permutations by difference in log of gamma function.
-
-    Preferred method for large permutations, but slow on smaller ones.
-
-    Note: no error checking (expects to be called through permutations())
-    """
-    return lgam(n + 1) - lgam(n - k + 1)
-
-
-@c3warn.deprecated_callable(
-    version="2024.9", reason="Use scipy.special.comb() instead", is_discontinued=True
-)
-def combinations(n, k):  # pragma: no cover
-    """Returns the number of ways of choosing k items from n, in order.
-
-    Defined as n!/(k!(n-k)!).
-    """
-    # Validation: k must be be between 0 and n (inclusive), and n must be >=0.
-    if k > n:
-        raise IndexError(f"Can't choose {k} items from {n}")
-    elif k < 0:
-        raise IndexError("Can't choose negative number of items")
-    elif n < 0:
-        raise IndexError("Can't choose from negative number of items")
-    # if min(n, k) < 20:
-    if min(n, k) < 20 and isinstance(n, int) and isinstance(k, int):
-        return combinations_exact(n, k)
-    else:
-        return exp(ln_combinations(n, k))
-
-
-@c3warn.deprecated_callable(
-    version="2024.9", reason="Use scipy.special.comb() instead", is_discontinued=True
-)
-def combinations_exact(n, k):  # pragma: no cover
-    """Calculates combinations by integer division.
-
-    Preferred method for small combinations, but slow on larger ones.
-
-    Note: no error checking (expects to be called through combinations())
-    """
-    # permutations(n, k) = permutations(n, n-k), so reduce computation by
-    # figuring out which requires calculation of fewer terms.
-    if k > (n - k):
-        larger = k
-        smaller = n - k
-    else:
-        larger = n - k
-        smaller = k
-
-    product = 1
-    # compute n!/(n-larger)! by multiplying terms from n to (n-larger+1)
-    for i in range(larger + 1, n + 1):
-        product *= i
-
-    # divide by (smaller)! by multiplying terms from 2 to smaller
-    for i in range(2, smaller + 1):  # no need to divide by 1...
-        product /= i  # ok to use integer division: should always be factor
-
-    return product
-
-
-@c3warn.deprecated_callable(
-    version="2024.9", reason="Use scipy.special.comb() instead", is_discontinued=True
-)
-def ln_combinations(n, k):  # pragma: no cover
-    """Calculates combinations by difference in log of gamma function.
-
-    Preferred method for large combinations, but slow on smaller ones.
-
-    Note: no error checking (expects to be called through combinations())
-    """
-    return lgam(n + 1) - lgam(k + 1) - lgam(n - k + 1)
-
-
 def ln_binomial(successes, trials, prob):
     """Returns the natural log of the binomial distribution.
 
@@ -270,63 +154,6 @@ ZU = [
 ]
 
 
-@c3warn.deprecated_callable(
-    version="2024.9",
-    reason="use scipy.special.erf(x, out=None) instead",
-    is_discontinued=True,
-)
-def erf(a):  # pragma: no cover
-    """being removed"""
-    if abs(a) > 1:
-        return 1 - erfc(a)
-    z = a * a
-    return a * polevl(z, ZT) / polevl(z, ZU)
-
-
-@c3warn.deprecated_callable(
-    version="2024.9",
-    reason="use scipy.special.erfc(z, out=None) instead",
-    is_discontinued=True,
-)
-def erfc(a):  # pragma: no cover
-    """being removed"""
-    if a < 0:
-        x = -a
-    else:
-        x = a
-
-    if x < 1:
-        return 1 - erf(a)
-
-    z = -a * a
-    if z < -MAXLOG:  # underflow
-        if a < 0:
-            return 2
-        else:
-            return 0
-    z = exp(z)
-
-    if x < 8:
-        p = polevl(x, ZP)
-        q = polevl(x, ZQ)
-    else:
-        p = polevl(x, ZR)
-        q = polevl(x, ZS)
-
-    y = z * p / q
-
-    if a < 0:
-        y = 2 - y
-
-    if y == 0:  # underflow
-        if a < 0:
-            return 2
-        else:
-            return 0
-    else:
-        return y
-
-
 # Coefficients for Gamma follow:
 GA = [
     8.11614167470508450300e-4,
diff --git a/src/cogent3/parse/gff.py b/src/cogent3/parse/gff.py
index 10deca524..5cf619998 100644
--- a/src/cogent3/parse/gff.py
+++ b/src/cogent3/parse/gff.py
@@ -327,7 +327,6 @@ def _gff_parser(
         )
 
 
-@c3warn.deprecated_args("2024.9", reason="not being used", discontinued="span")
 def parse_attributes_gff2(attributes: str) -> dict:
     """Returns a dict with name and info keys"""
     name = attributes[attributes.find('"') + 1 :]
@@ -336,7 +335,6 @@ def parse_attributes_gff2(attributes: str) -> dict:
     return {"ID": name, "Info": attributes}
 
 
-@c3warn.deprecated_args("2024.9", reason="not being used", discontinued="span")
 def parse_attributes_gff3(attributes: str) -> dict:
     """Returns a dictionary containing all the attributes"""
     attributes = attributes.strip(";")
diff --git a/src/cogent3/parse/sequence.py b/src/cogent3/parse/sequence.py
index b77541a0e..43a1ec1df 100644
--- a/src/cogent3/parse/sequence.py
+++ b/src/cogent3/parse/sequence.py
@@ -23,58 +23,6 @@ from cogent3.util.io import get_format_suffixes, iter_splitlines, open_
 _lc_to_wc = "".join([[chr(x), "?"]["A" <= chr(x) <= "Z"] for x in range(256)])
 
 
-@c3warn.deprecated_callable(
-    "2024.9",
-    reason="allow more customised parser implementations",
-    is_discontinued=True,
-)
-def FromFilenameParser(filename, format=None, **kw):  # pragma: no cover
-    """Arguments:
-    - filename: name of the sequence alignment file
-    - format: the multiple sequence file format
-    """
-    if format is None:
-        format, _ = get_format_suffixes(filename)
-
-    with open_(filename, newline=None, mode="rt") as f:
-        data = f.read()
-
-    return FromFileParser(data.splitlines(), format, **kw)
-
-
-@c3warn.deprecated_callable(
-    "2024.9",
-    reason="allow more customised parser implementations",
-    is_discontinued=True,
-)
-def FromFileParser(f, format, dialign_recode=False, **kw):  # pragma: no cover
-    format = format.lower()
-    if format in XML_PARSERS:
-        doctype = format
-        format = "xml"
-    else:
-        doctype = None
-    if format == "xml":
-        source = dom = xml.dom.minidom.parse(f)
-        if doctype is None:
-            doctype = str(dom.doctype.name).lower()
-        if doctype not in XML_PARSERS:
-            raise FileFormatError(f"Unsupported XML doctype {doctype}")
-        parser = XML_PARSERS[doctype]
-    else:
-        if format not in PARSERS:
-            raise FileFormatError(f"Unsupported file format {format}")
-        parser = PARSERS[format]
-        source = f
-    for name, seq in parser(source, **kw):
-        if isinstance(seq, str):
-            if dialign_recode:
-                seq = seq.translate(_lc_to_wc)
-            if not seq.isupper():
-                seq = seq.upper()
-        yield name, seq
-
-
 ParserOutputType = typing.Iterable[typing.Tuple[str, str]]
 
 
diff --git a/src/cogent3/util/deserialise.py b/src/cogent3/util/deserialise.py
index 98b9d9fa1..dc63d7a59 100644
--- a/src/cogent3/util/deserialise.py
+++ b/src/cogent3/util/deserialise.py
@@ -111,7 +111,14 @@ def deserialise_not_completed(data):
 
 def deserialise_map_spans(map_element):
     map_element.pop("version", None)
-    map_klass = _get_class(map_element.pop("type"))
+    type_ = map_element.pop("type")
+    if type_.endswith(".Map"):
+        # old style Map
+        type_ = type_.replace(".Map", ".FeatureMap")
+        for k in ("tidy", "termini_unknown"):
+            map_element.pop(k, None)
+
+    map_klass = _get_class(type_)
     spans = []
     for element in map_element["spans"]:
         element.pop("version", None)


Test patch:
diff --git a/tests/test_core/test_new_alignment.py b/tests/test_core/test_new_alignment.py
index 501fb9ca8..97a7ae495 100644
--- a/tests/test_core/test_new_alignment.py
+++ b/tests/test_core/test_new_alignment.py
@@ -236,8 +236,8 @@ def test_seqs_data_get_seq_view(str_seqs_dict, dna_alphabet, seqid):
 
 
 @pytest.mark.parametrize("seq", ("seq1", "seq2"))
-@pytest.mark.parametrize("start", (None, 0, 1))
-@pytest.mark.parametrize("stop", (None, 3, 4))
+@pytest.mark.parametrize("start", (None, -1, 0, 1, 4))
+@pytest.mark.parametrize("stop", (None, -1, 0, 1, 4))
 def test_seqs_data_get_seq_str(str_seqs_dict, dna_alphabet, seq, start, stop):
     # slicing should be tested in test_get_seq_array
     expect = str_seqs_dict[seq][start:stop]
@@ -623,30 +623,6 @@ def test_make_seqs_from_sequences(mk_cls):
     assert coll.get_seq("s1") == seq1
 
 
-@pytest.mark.parametrize(
-    "mk_cls",
-    [new_alignment.make_unaligned_seqs, new_alignment.make_aligned_seqs],
-)
-@pytest.mark.parametrize(
-    "seq_name, parent_name", [("seq_1", "parent_1"), ("seq_2", "parent_2")]
-)
-def test_make_seqs_renamed_seqs(mk_cls, seq_name, parent_name, dna_alphabet):
-    # the parent_name should persist from sequence object to SequenceCollection
-    seq_view_1 = new_sequence.SeqView(
-        parent="AAAA", parent_len=4, seqid="parent_1", alphabet=dna_alphabet
-    )
-    seq_view_2 = new_sequence.SeqView(
-        parent="TTTT", parent_len=4, seqid="parent_2", alphabet=dna_alphabet
-    )
-
-    seq_1 = new_moltype.DNA.make_seq(seq=seq_view_1, name="seq_1")
-    seq_2 = new_moltype.DNA.make_seq(seq=seq_view_2, name="seq_2")
-
-    seqs = mk_cls([seq_1, seq_2], moltype="dna")
-    assert set(seqs.names) == {"seq_1", "seq_2"}
-    assert seqs.get_seq(seq_name)._seq.seqid == parent_name
-
-
 @pytest.mark.parametrize(
     "mk_cls, data_cls",
     [
@@ -655,7 +631,7 @@ def test_make_seqs_renamed_seqs(mk_cls, seq_name, parent_name, dna_alphabet):
     ],
 )
 @pytest.mark.parametrize("seq", ("a", "b"))
-def test_make_seqs_offset(mk_cls, data_cls, seq, dna_alphabet):
+def test_make_seqs_offset(mk_cls, data_cls, seq):
     """SequenceCollection and Alignment constructor functions should handle
     offset argument"""
     data = {"a": "AGGCCC", "b": "AGAAAA"}
@@ -669,24 +645,6 @@ def test_make_seqs_offset(mk_cls, data_cls, seq, dna_alphabet):
     with pytest.raises(ValueError):
         _ = mk_cls(data, moltype="dna", offset=offset)
 
-    # if provided with sequence objects with offsets, they should be propogated
-    seq_view_1 = new_sequence.SeqView(
-        parent="AAAA", parent_len=4, alphabet=dna_alphabet, offset=1
-    )
-    seq_view_2 = new_sequence.SeqView(
-        parent="TTTT", parent_len=4, alphabet=dna_alphabet, offset=2
-    )
-    seq_1 = new_moltype.DNA.make_seq(seq=seq_view_1, name="seq_1")
-    seq_2 = new_moltype.DNA.make_seq(seq=seq_view_2, name="seq_2")
-
-    seqs = mk_cls([seq_1, seq_2], moltype="dna")
-
-    got = seqs.get_seq("seq_1")
-    assert got._seq.offset == 1
-
-    got = seqs.get_seq("seq_2")
-    assert got._seq.offset == 2
-
 
 @pytest.mark.parametrize(
     "mk_cls",
@@ -1089,8 +1047,7 @@ def test_sequence_collection_degap(mk_cls, gap_ambig_seqs, rced):
 
     # Test empty sequences case
     empty_seqs = mk_cls({"empty1": "", "empty2": ""}, moltype="dna")
-    empty_seqs = empty_seqs.degap()
-    got_empty = empty_seqs.to_dict()
+    got_empty = empty_seqs.degap().to_dict()
     expect_empty = {"empty1": "", "empty2": ""}
     assert got_empty == expect_empty
 
diff --git a/tests/test_core/test_new_sequence.py b/tests/test_core/test_new_sequence.py
index e583c46b4..15579526d 100644
--- a/tests/test_core/test_new_sequence.py
+++ b/tests/test_core/test_new_sequence.py
@@ -2670,7 +2670,7 @@ def test_seqview_seq_len_init(start, stop, step, length, dna_alphabet):
 @pytest.mark.parametrize("step", (1, 3))
 @pytest.mark.parametrize("rev", (False, True))
 def test_seqview_plus_attrs(rev, step):
-    # always equal values from plus strand
+    # always equal values from plkus strand
     start = 2
     stop = 12
     sr = new_sequence.SliceRecord(start=start, stop=stop, step=step, parent_len=30)
@@ -2678,65 +2678,6 @@ def test_seqview_plus_attrs(rev, step):
     assert (sr.plus_start, sr.plus_stop, sr.plus_step) == (start, stop, step)
 
 
-@pytest.mark.parametrize("rev", (False, True))
-def test_seqview_attrs_non_modulo(rev):
-    # for when the stop value does not align with the step, the plus_[stop/start]
-    # is adjusted
-
-    # start = 1, stop = 10, step = 2
-    #   [  -->  )
-    # 0123456789
-    #   * * * *
-    # we expect the plus_stop to be 9, as this is the index immediately following
-    # the last selected index
-    sr = new_sequence.SliceRecord(start=2, stop=10, step=2, parent_len=10)
-    sr = sr[::-1] if rev else sr
-    assert sr.plus_start == 2
-    assert sr.plus_stop == 9
-
-    # start = -4, stop = -10, step = -2
-    # ( <-- ]
-    # 0123456789
-    #   * * *
-    # although a stop of -10 would be a start of 1 for step == 1, we expect
-    # in this case the plus_start to be 2
-    sr = new_sequence.SliceRecord(start=-4, stop=-10, step=-2, parent_len=10)
-    sr = sr[::-1] if rev else sr
-    assert sr.plus_start == 2
-    assert sr.plus_stop == 7
-
-    # start = 1, stop = 7, step = 3
-    #  [ --> )
-    # 0123456789
-    #  *  *
-    sr = new_sequence.SliceRecord(start=1, stop=7, step=3, parent_len=10)
-    sr = sr[::-1] if rev else sr
-    assert sr.plus_start == 1
-    assert sr.plus_stop == 5
-
-    # start = -3, stop = -9, step = -3
-    #  (     ]
-    # 0123456789
-    #     *  *
-    sr = new_sequence.SliceRecord(start=-3, stop=-9, step=-3, parent_len=10)
-    sr = sr[::-1] if rev else sr
-    assert sr.plus_start == 4
-    assert sr.plus_stop == 8
-
-    sr = new_sequence.SliceRecord(start=0, stop=0, step=1, parent_len=10)
-    sr = sr[::-1] if rev else sr
-    assert sr.plus_start == 0
-    assert sr.plus_stop == 0
-
-
-@pytest.mark.parametrize("rev", (False, True))
-def test_seqview_attrs_zero_slice(rev):
-    sr = new_sequence.SliceRecord(start=0, stop=0, step=1, parent_len=0)
-    sr = sr[::-1] if rev else sr
-    assert sr.plus_start == 0
-    assert sr.plus_stop == 0
-
-
 def test_seqview_copy_propagates_seq_len(dna_alphabet):
     seq = "ACGGTGGGAC"
     sv = new_sequence.SeqView(parent=seq, parent_len=len(seq), alphabet=dna_alphabet)
diff --git a/tests/test_maths/test_stats/test_distribution.py b/tests/test_maths/test_stats/test_distribution.py
index 3dd7c6d1a..ccd44c42b 100644
--- a/tests/test_maths/test_stats/test_distribution.py
+++ b/tests/test_maths/test_stats/test_distribution.py
@@ -20,8 +20,6 @@ from cogent3.maths.stats.distribution import (
     pdtr,
     pdtrc,
     pdtri,
-    poisson_high,
-    poisson_low,
     probability_points,
     stdtr,
     stdtri,
diff --git a/tests/test_util/test_deserialise.py b/tests/test_util/test_deserialise.py
index b3f9f375f..e4a0c892b 100644
--- a/tests/test_util/test_deserialise.py
+++ b/tests/test_util/test_deserialise.py
@@ -542,18 +542,14 @@ def test_convert_annotation_to_annotation_db():
                             {
                                 "start": 2,
                                 "end": 5,
-                                "tidy_start": False,
-                                "tidy_end": False,
                                 "value": None,
                                 "reverse": False,
                                 "type": "cogent3.core.location.Span",
                                 "version": "2023.2.12a1",
                             }
                         ],
-                        "tidy": False,
                         "parent_length": 5,
-                        "termini_unknown": False,
-                        "type": "cogent3.core.location.Map",
+                        "type": "cogent3.core.location.FeatureMap",
                         "version": "2023.2.12a1",
                     },
                 },
@@ -617,8 +613,6 @@ def test_deser_annotated_aln():
                             {
                                 "start": 0,
                                 "end": 3,
-                                "tidy_start": False,
-                                "tidy_end": False,
                                 "value": None,
                                 "reverse": False,
                                 "type": "cogent3.core.location.Span",
@@ -627,18 +621,14 @@ def test_deser_annotated_aln():
                             {
                                 "start": 5,
                                 "end": 9,
-                                "tidy_start": False,
-                                "tidy_end": False,
                                 "value": None,
                                 "reverse": False,
                                 "type": "cogent3.core.location.Span",
                                 "version": "2023.2.12a1",
                             },
                         ],
-                        "tidy": False,
                         "parent_length": 12,
-                        "termini_unknown": False,
-                        "type": "cogent3.core.location.Map",
+                        "type": "cogent3.core.location.FeatureMap",
                         "version": "2023.2.12a1",
                     },
                 },