Repository: cogent3/cogent3 (language: python)

Issue:
Prep for release

Thinking of the documentation of apps
- It is unclear in [the model App](https://cogent3.org/doc/app/evo-model-timehet.html#specify-a-distinct-rate-matrix-for-the-great-apes ) doc about how to specify a totally time-heterogeneous model. 
 
  **Suggestion:** it could be better to add more description about the argument ```time_het``` in `get_app("model",
    ...,
    time_het=...
)`.

- For the doc of [Sqlite based](https://cogent3.org/doc/app/dstore.html#sqlitedb-data-stores-for-serialised-data) ```DataStore```, it is not clear to me how to load the ```.sqlitedb``` file. By intuition, I tried to find the relevant apps in ```available_apps()```. But I got lost when I tried ```load_db``` as a loader since its docstring said "Loads serialised cogent3 objects from a db", but it seems we cannot pass ```DataStore``` type directly to this app. Then, I realised that  ```DataStore``` type is a collection. So, I tried to use ```.apply_to()``` but ```load_db``` has no that method.
  
  **Suggestion:** good to give an example of loading ```.sqlitedb``` file under the entry of [Sqlitedb data stores](https://cogent3.org/doc/app/dstore.html#sqlitedb-data-stores-for-serialised-data).

- I'm not clear on what type of argument I should define when I write my own app learnt from [this page](https://cogent3.org/doc/app/user_function.html#turn-your-functions-into-composable-apps). From a couple of examples here, I feel like the Type hint is the type my function will tackle with. My app attempts to take a ```model_result``` and do some calculations. However, I failed to import ```model_result``` from ```cogent3.app.typing```.   

  **Suggestion:** possibly give a table of available types in ```cogent3.app.typing```? 





document using a dotplot to evaluate alignment quality
**Describe the solution you'd like**
The dotplot algorithm can show both matching segments between sequences AND the path an alignment algorithm has chosen.

Write a gallery document that shows this feature, explaining how it's useful.

The document should demonstrate using an aligner with bad parameter values. Apply it to the brca1.fasta file. Embellish the following with explanatory text of alignment parameters, what a dotplot shows, how to modify the dotplot display, etc...
```python
from cogent3 import load_aligned_seqs
from cogent3.app.align import progressive_align

seqs = load_aligned_seqs("/Users/gavin/repos/Cogent3/doc/data/brca1.fasta", moltype="dna").degap()
seqs = seqs.take_seqs(['LeafNose',
 'FalseVamp',
 'RoundEare',
 'Sloth',
 'Anteater',
 'HairyArma'])

# bad idea! make gap extension unlikely
aligner = progressive_align("nucleotide", indel_length=1e-9, indel_rate=1e-2)
aln = aligner(seqs)
aln[2200:2500].dotplot("HairyArma", "RoundEare").show()
aln[2270:2500] # showing poorly aligned segment

# run with aligner defaults
aligner = progressive_align("nucleotide")
aln = aligner(seqs)
aln[2200:2500].dotplot("HairyArma", "RoundEare").show()
aln[2270:2500] # showing poorly aligned segment
```
BUG: Aligned.get_seq() returns SeqView, fixes #1639
[CHANGED] slight change in `get_seq()` behaviour: now, it returns a new `Sequence` instance (because `Sequences` are immutable!). This affects tests that expect `get_seq()` to return the original `Sequence` used to construct the `Alignment`.
Add Table.to_pandas() method
This is used by plotly express 5.15.

Deprecate the `Table.to_dataframe()` method (check [deprecating code docs](https://github.com/cogent3/c3dev/wiki/Deprecating-code) and replace it with `Table.to_pandas()`. Set the deprecation date as 2024.3 (which corresponds to the Q1 release).
MAINT: simplify _SequenceCollectionBase.__init__
This is the base class for SequenceCollection, Alignment and ArrayAlignment. There's a lot of processing within the constructor, which makes that method unnecessarily complex. Specifically, the class attribute `_input_handlers` is a mapping to functions that handle different input types. Many different arguments are not well suited to this class (e.g. `is_array`).

This pre-processing should be handled by a `prep_seqs(data: Any) -> tuple[Sequence]` function, which takes as input  returns a tuple of Sequence instances that are passed to `_SequenceCollectionBase`. (`prep_seqs()` probably needs a `singledispatch` approach to discriminate between the major input types (e.g. `dict`, `ndarray`, etc...)

First step is to refactor private methods that could be functions:

- [ ] `_names_seqs_order`
- [x] `_guess_input_type` (probably addressed by using `singledispatch` on input values)
- [x] `_coerce_seqs`
- [x] `_make_named_seqs`
- [x] `_strip_duplicates`
- [x] `_get_container_item`
DOC: docs for using a dotplot to evaluate alignment quality, fixes #1063
@GavinHuttley I am not sure if there is any sphynx magic I need to do to put an image from this section in the image gallery?


Discussion / hints:

Thanks so much for the feedback @YapengLang ! We will modify the docs, and or the code behaviour.
I have addressed some of this, but more work needs to be done. So I'm changing the milestone of this to Q3 to give us more time to address that.






Fix patch:
diff --git a/.github/ISSUE_TEMPLATE/deprecation.md b/.github/ISSUE_TEMPLATE/deprecation.md
index 4ace11417..dd774623e 100644
--- a/.github/ISSUE_TEMPLATE/deprecation.md
+++ b/.github/ISSUE_TEMPLATE/deprecation.md
@@ -9,6 +9,8 @@ assignees: ''
 
 **What should be changed**
 
+Deprecated means a function, method or argument is going to be replaced in the code base.
+
 What function, method or argument should be marked for change?
 
 What is the full module path to the attribute?
@@ -23,5 +25,5 @@ The number of months (*x*) long before the capability will be removed? This shou
 
 **Guidelines for deprecation**
 
-As this is a discontinued function, see [dev guidelines for deprecation](https://github.com/cogent3/c3dev/wiki/Deprecating-Code) of code. Indicate the limiting version (for when the old function will be removed) as 3 months from when you make the change.
+As this is a function being deprecated, see [dev guidelines for deprecation](https://github.com/cogent3/c3dev/wiki/Deprecating-Code) of code. Indicate the limiting version (for when the old function will be removed) as 3 months from when you make the change.
 
diff --git a/.github/ISSUE_TEMPLATE/discontinue.md b/.github/ISSUE_TEMPLATE/discontinue.md
index 8cd9bfa94..28740a010 100644
--- a/.github/ISSUE_TEMPLATE/discontinue.md
+++ b/.github/ISSUE_TEMPLATE/discontinue.md
@@ -9,7 +9,9 @@ assignees: ''
 
 **What should be changed**
 
-What function, method or argument should be marked for change?
+Discontinued means a function, method or argument is going to be deleted entirely from the code base and is not being replaced. 
+
+What function, method or argument should be marked for removal?
 
 What is the full module path to the attribute?
 
@@ -23,5 +25,5 @@ The number of months (*x*) long before the capability will be removed? This shou
 
 **Guidelines for deprecation**
 
-As this is a discontinued function, see [dev guidelines for deprecation](https://github.com/cogent3/c3dev/wiki/Deprecating-Code) of code. Indicate the limiting version (for when the old function will be removed) as 3 months from when you make the change.
+As this is a function that will be removed, see [dev guidelines for deprecation](https://github.com/cogent3/c3dev/wiki/Deprecating-Code) of code. Indicate the limiting version (for when the old function will be removed) as 3 months from when you make the change.
 
diff --git a/.gitignore b/.gitignore
index 0b60b315b..3bf432410 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,7 +17,7 @@ develop-eggs
 .installed.cfg
 lib
 lib64
-doc/draw
+doc/_build
 
 # Installer logs
 pip-log.txt
@@ -43,6 +43,7 @@ lcov*.info
 .DS_Store
 __pycache__
 *.code-workspace
+*.wpr
 *.wpu
 .vscode/*
 venv/
diff --git a/.hgtags b/.hgtags
index ef7fe1bd2..e6b23afc6 100644
--- a/.hgtags
+++ b/.hgtags
@@ -35,3 +35,4 @@ a82da4d3716db1c03ddf7d4bb2e3f5e2a5a143d1 develop
 088fba384849b26e97c418dc60796d69a722129e develop
 91ccb723aa378107f80ac0fe8cb6f65e94f7833a 2023.7.18a1
 d77104f69e616ed3b077188540901cdb5d968966 2023.9.22a1
+0201d844deb847beceedefd288d82d235766c617 2023.12.15a1
diff --git a/.sourcery.yaml b/.sourcery.yaml
index 75524cd8e..e43736f34 100644
--- a/.sourcery.yaml
+++ b/.sourcery.yaml
@@ -1,2 +1,2 @@
 refactor:
-  python_version: '3.8'
\ No newline at end of file
+  python_version: '3.9'
\ No newline at end of file
diff --git a/README.md b/README.md
index a4ff414f2..2cd8742ef 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,10 @@
 
 [![CodeQL](https://github.com/cogent3/cogent3/actions/workflows/codeql.yml/badge.svg)](https://github.com/cogent3/cogent3/actions/workflows/codeql.yml)
 
-## `cogent3`
+<p align="left">
+  <img src="https://raw.githubusercontent.com/cogent3/cogent3.github.io/e72df8c155c100f502b6a7009347d1821ab3adef/doc/_static/c3-logo.svg" width="300">
+</p>
+
 
 `cogent3` is a mature python library for analysis of genomic sequence data. We endeavour to provide a first-class experience within Jupyter notebooks, but the algorithms also support parallel execution on compute systems with 1000's of processors.
 
@@ -83,14 +86,18 @@ $ pip install git+https://github.com/cogent3/cogent3.git@develop#egg=cogent3
 
 `cogent3` is a descendant of [PyCogent](https://github.com/pycogent/pycogent.github.com). While there is much in common with PyCogent, the amount of change has been substantial, motivating the name change to `cogent3`. This name has been chosen because `cogent` was always the import name (dating back to [PyEvolve in 2004](https://www.ncbi.nlm.nih.gov/pubmed/14706121)) and it's Python 3 only.
 
-Given this history, we are grateful to the multitude of individuals who have made contributions over the years. These individuals are explicitly acknowledged in all the files they contributed to and were co-authors on the original [PyEvolve](https://www.ncbi.nlm.nih.gov/pubmed/14706121) and [PyCogent](https://www.ncbi.nlm.nih.gov/pubmed/17708774) publications.
+Given this history, we are grateful to the multitude of individuals who have made contributions over the years. Many of these contributors were also co-authors on the original [PyEvolve](https://www.ncbi.nlm.nih.gov/pubmed/14706121) and [PyCogent](https://www.ncbi.nlm.nih.gov/pubmed/17708774) publications. Individual contributions can be seen by using "view git blame" on individual lines of code on GitHub , through git log in the terminal, and more recently the changelog.
 
 Compared to PyCogent version 1.9, there has been a massive amount of changes. These include integration of many of the new developments on algorithms and modelling published by the [Huttley lab](https://biology.anu.edu.au/research/groups/huttley-group-bioinformatics-molecular-evolution-genomes) over the last decade. We have also modernised our dependencies. For example, we now use `plotly` for visualisation, `tqdm` for progress bar display, `concurrent.futures` and `mpi4py.futures` for parallel process execution, `nox` and `pytest` for unit testing.
 
 ## Funding
 
-Cogent3 is funded by a [Essential Open Source Software for Science Grant](https://chanzuckerberg.com/eoss/proposals/cogent3-python-apis-for-iq-tree-and-graphbin-via-a-plug-in-architecture/) from the Chan Zuckerberg Initiative.
+Cogent3 has received funding support from the Australian National University and an [Essential Open Source Software for Science Grant](https://chanzuckerberg.com/eoss/proposals/cogent3-python-apis-for-iq-tree-and-graphbin-via-a-plug-in-architecture/) from the Chan Zuckerberg Initiative.
 
-<p align="left">
-  <img src="https://chanzuckerberg.com/wp-content/themes/czi/img/logo.svg" width="300">
+
+<p align="center">
+  &nbsp;&nbsp;&nbsp;&nbsp;
+  <img src="https://webstyle.anu.edu.au/_anu/4/images/logos/2x_anu_logo_small.svg" height="100">
+  &nbsp;&nbsp;&nbsp;&nbsp;
+  <img src="https://chanzuckerberg.com/wp-content/themes/czi/img/logo.svg" height="110">
 </p>
diff --git a/changelog.md b/changelog.md
index 87a685db1..0071bbf6f 100755
--- a/changelog.md
+++ b/changelog.md
@@ -1,4 +1,70 @@
 
+<a id='changelog-2023.12.15a1'></a>
+# Changes in release "2023.12.15a1"
+
+## Contributors
+
+- @wjjmjh (aka Stephen Ma) fixed an error in the seq features gallery drawing example. Turns out, if you want a thumbnail, you actually have to write it out 🤦‍♂️!!
+- Multiple PRs from first-time contributor Fred Jaya. Thanks @fredjaya 🚀!!
+- Kath Caley for the sweet new cogent3 logo 🥳!
+- Kath Caley for numerous other commits (bug fixes, enhancements, etc..). Thanks @KatherineCaley 🚀!
+- First time contribution from @cosmastech 🎉
+- Important new tree metrics from Robert McArthur. Thanks @rmcar17 🚀!
+
+## ENH
+
+- Added from cogent3.app.typing import defined_types function. This displays
+  the cogent3 defined type hints and the objects they represent.
+
+- added available_apps(name_filter) which allows filtering apps by
+  their name (thanks to @cosmastech)
+
+- The new AnnotationDb.subset() method creates a subset of the instance
+  with records matching the provided conditions.
+
+- app.as_completed() and app.apply_to() no longer raise a
+  ValueError if there's no work to be done.
+
+- Added the ever popular Robinson-Fould tree topology measure, but
+  you shouldn't use it except for comparison to the far superior
+  (statistically at least) matching distance measures. These are all
+  available on the tree objects via a new PhyloNode.tree_distance()
+  method (thanks to @rmcar17).
+
+- Added methods `min_pair()` and `max_pair()` to `DistanceMatrix`, to return the names of the sequences with minimum and maximum pairwise distance, respectively (thanks to @KatherineCaley).
+
+## BUG
+
+- The sequence returned from Alignment.get_seq() now reflects
+  slice operations applied to the Alignment.
+
+## DOC
+
+- The sequence features drawing example now has a gallery thumbnail.
+
+- Doc showing how the dotplot can be used to highlight alignment errors.
+
+- Added acknowledgement that this project has received funding support from
+  the Australian National University. We are grateful!
+
+## Deprecations
+
+- Table.tolist() is being replaced by Table.to_list()
+
+- Reverse slicing of Alignment and ArrayAlignment are now consistent
+  with reverse slicing of a string. Previously reverse slicing an
+  Alignment instance, e.g. `aln[5:1]` would reverse complement a
+  nucleic acid object, but fail if it was any other molecular type.
+  This behaviour was different to ArrayAlignment. For both objects, use
+  a normal slice and reverse complement, e.g. `aln[1:5].rc()`.
+
+## Discontinued
+
+- Began a major refactor of the sequence collection classes. The major change
+  is they no longer accept fasta formatted string as input. As we have simplified
+  the data conversion functions, all previously used public functions have now been
+  marked as being discontinued 2024.3.
+
 <a id='changelog-2023.9.22a1'></a>
 # Changes in release "2023.9.22a1"
 
diff --git a/doc/app/available-apps.rst b/doc/app/available-apps.rst
index f6a0a8ca6..c61bcc76b 100644
--- a/doc/app/available-apps.rst
+++ b/doc/app/available-apps.rst
@@ -11,4 +11,8 @@ Apps perform functions ranging from multiple sequence alignment (e.g. ``progress
 
     available_apps()
 
+The ``name_filter`` argument can be used to display only the apps that match a string. For example, to display all the loader apps
 
+.. jupyter-execute::
+
+    available_apps(name_filter="load")
diff --git a/doc/app/user_function.rst b/doc/app/user_function.rst
index ccb4f82df..cf0eb098b 100644
--- a/doc/app/user_function.rst
+++ b/doc/app/user_function.rst
@@ -10,6 +10,19 @@ You need two things:
 
 .. note:: At present, your function can have only one required argument.
 
+Supported cogent3 types
+-----------------------
+
+If you function takes or returns cogent3 types, you can use the existing type hints. To see what these are, use the ``defined_types()`` function.
+
+.. jupyter-execute::
+
+    from cogent3.app.typing import defined_types
+
+    defined_types()
+
+.. note:: You don't have to use cogent3 types, you can also use standard python types.
+
 A simple example
 ----------------
 
diff --git a/doc/general.rst b/doc/community.rst
similarity index 100%
rename from doc/general.rst
rename to doc/community.rst
diff --git a/doc/conf.py b/doc/conf.py
index 741eda7ec..585b4795d 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -37,7 +37,7 @@ def make_nbsphinx_thumbnails():
     "sphinx.ext.mathjax",
     "sphinx.ext.todo",
     "sphinxcontrib.bibtex",
-    "sphinx_panels",
+    "sphinx_design",
 ]
 
 # stop sphinx-panels from including css
diff --git a/doc/cookbook/annotation_db.rst b/doc/cookbook/annotation_db.rst
index d4678af51..e1183b1ad 100644
--- a/doc/cookbook/annotation_db.rst
+++ b/doc/cookbook/annotation_db.rst
@@ -10,7 +10,6 @@ Annotation Databases
 
 This guide shows you how to use ``cogent3``'s annotation databases, which are in-memory SQLite databases, to store, query and manipulate the :ref:`howto-features` (also known as annotations) of one or more biological sequences.
 
-
 What are the different types of ``AnnotationDb``?
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
@@ -28,7 +27,7 @@ Achieved by creating an ``BasicAnnotationDb`` instance. This is an empty databas
 
     anno_db = BasicAnnotationDb()
     anno_db
-    
+
 How to load an standalone ``AnnotationDb`` from a data file
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
@@ -84,12 +83,12 @@ We can add a feature to the empty ``BasicAnnotationDb`` we created above. Now th
     :raises:
 
     anno_db.add_feature(
-               seqid="NC_000908",
-               biotype="gene",
-               name="interesting_gene",
-               spans=[(1, 4)],
-               strand="+",
-                )
+        seqid="NC_000908",
+        biotype="gene",
+        name="interesting_gene",
+        spans=[(1, 4)],
+        strand="+",
+    )
     anno_db.describe
 
 We can also add a feature to our ``GffAnnotationDb`` or ``GenbankAnnotationDb``. Below, the previously empty "user" table now has a row count of one, indicating that our feature has been successfully added to the database.
@@ -104,7 +103,7 @@ We can also add a feature to our ``GffAnnotationDb`` or ``GenbankAnnotationDb``.
         spans=[(1, 4)],
         strand="+",
     )
-    gff_db.describe[-2:, :] # showing just last two rows
+    gff_db.describe[-2:, :]  # showing just last two rows
 
 How to write an ``AnnotationDb`` to disk for efficient re-loading
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -162,7 +161,7 @@ For example, querying the ``GffAnnotationDb`` for all pseudogenes:
     :raises:
 
     pseudogenes = list(gff_db.get_features_matching(biotype="pseudogene"))
-    pseudogenes[:2] # showing just the first two
+    pseudogenes[:2]  # showing just the first two
 
 Querying via region of interest
 """""""""""""""""""""""""""""""
@@ -192,7 +191,7 @@ For example, we can query for all CDS related to replication:
     replication_records = list(
         gff_db.get_records_matching(attributes="replication", biotype="CDS")
     )
-    replication_records[0] # showing just the first match
+    replication_records[0]  # showing just the first match
 
 .. note:: Extended attribute querying only works for GFF databases!
 
@@ -320,10 +319,26 @@ You can assign a compatible database to the ``db`` argument in the ``AnnotationD
     :raises:
 
     from cogent3.core.annotation_db import GenbankAnnotationDb
-    
+
     new_gb_db = GenbankAnnotationDb(source="m-genitalium-database.gbdb", db=anno_db)
     new_gb_db
 
+How to get a subset of an ``AnnotationDb``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you want a subset of a db, use the same arguments as you would for ``db.get_records_matching()``.
+
+.. jupyter-execute::
+    :raises:
+
+    from cogent3 import load_annotations
+
+    gff_db = load_annotations(path="data/mycoplasma-genitalium.gff")
+    just_cds = gff_db.subset(biotype="CDS")
+    just_cds.describe
+
+.. note:: The result is an in-memory database by default. To have this written to disk, assign a path to the source argument, e.g. ``gff_db.subset(source="som/path/subset.gff3db", biotype="CDS")``.
+
 .. _assign_db_to_seq:
 
 How to assign an ``AnnotationDb`` to a sequence
@@ -393,9 +408,7 @@ In the above example, the sequence name in the fasta file does not match any rec
         label_to_name=lambda x: x.split()[0],
     )
     seq.annotation_db
-    
-    
-    
+
 .. jupyter-execute::
     :hide-code:
 
diff --git a/doc/cookbook/calc_genetic_distance.rst b/doc/cookbook/calc_genetic_distance.rst
index ab79bfacf..0058acfa3 100644
--- a/doc/cookbook/calc_genetic_distance.rst
+++ b/doc/cookbook/calc_genetic_distance.rst
@@ -72,3 +72,49 @@ The following will use the F81 nucleotide substitution model and perform numeric
     d.run(show_progress=False)
     dists = d.get_pairwise_distances()
     dists
+
+*****************************************************
+Get the names of sequences with max pairwise distance
+*****************************************************
+
+Given a ``DistanceMatrix`` object, finding the sequences that have the maximum pairwise distance is achieved through the ``max_pair`` method. 
+
+.. jupyter-execute::
+    :raises:
+    
+    from cogent3 import load_aligned_seqs
+
+    aln = load_aligned_seqs("data/primate_brca1.fasta", moltype="dna")
+    dists = aln.distance_matrix(calc="tn93", show_progress=False)
+    dists.max_pair()
+    
+To find the maximum distance, index the ``DistanceMatrix`` with the result of ``max_pair``.
+
+.. jupyter-execute::
+    :raises:
+    
+    dists[dists.max_pair()]
+
+*****************************************************
+Get the names of sequences with min pairwise distance
+*****************************************************
+
+Given a ``DistanceMatrix`` object, finding the sequences that have the minimum pairwise distance is achieved through the ``min_pair`` method. 
+
+.. note:: As the distance between a sequence and itself is zero, and this is not informative, ``min_pair`` will return the smallest distance not on the diagonal.
+
+.. jupyter-execute::
+    :raises:
+    
+    from cogent3 import load_aligned_seqs
+
+    aln = load_aligned_seqs("data/primate_brca1.fasta", moltype="dna")
+    dists = aln.distance_matrix(calc="tn93", show_progress=False)
+    dists.min_pair()
+
+To find the minimum distance, index the ``DistanceMatrix`` with the result of ``min_pair``.
+
+.. jupyter-execute::
+    :raises:
+
+    dists[dists.min_pair()]
diff --git a/doc/cookbook/loading_sequences.rst b/doc/cookbook/loading_sequences.rst
index cf2851cfb..2cea78cda 100755
--- a/doc/cookbook/loading_sequences.rst
+++ b/doc/cookbook/loading_sequences.rst
@@ -13,7 +13,7 @@ It's also possible to load a sequence from a :ref:`url <load_url>`.
 .. jupyter-execute::
 
     from cogent3 import load_seq
-    
+
     seq = load_seq("data/mycoplasma-genitalium.fa", moltype="dna")
     seq
 
@@ -76,8 +76,10 @@ The ``cogent3`` load functions support loading from a url. We load the above fas
 
     from cogent3 import load_aligned_seqs
 
-
-    aln = load_aligned_seqs("https://raw.githubusercontent.com/cogent3/cogent3/develop/doc/data/long_testseqs.fasta", moltype="dna")
+    aln = load_aligned_seqs(
+        "https://raw.githubusercontent.com/cogent3/cogent3/develop/doc/data/long_testseqs.fasta",
+        moltype="dna",
+    )
 
 Specifying the file format
 ^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -100,7 +102,7 @@ Simple case of loading a ``list`` of aligned amino acid sequences in FASTA forma
 
     from cogent3 import make_aligned_seqs
 
-    protein_seqs = [">seq1", "DEKQL-RG", ">seq2", "DDK--SRG"]
+    protein_seqs = {"seq1": "DEKQL-RG", "seq2": "DDK--SRG"}
     proteins_loaded = make_aligned_seqs(protein_seqs)
     proteins_loaded.moltype
     proteins_loaded
@@ -132,7 +134,7 @@ From a series of strings
 
     from cogent3 import make_aligned_seqs
 
-    seqs = [">seq1", "AATCG-A", ">seq2", "AATCGGA"]
+    seqs = {"seq1": "AATCG-A", "seq2": "AATCGGA"}
     seqs_loaded = make_aligned_seqs(seqs)
     seqs_loaded
 
@@ -145,12 +147,10 @@ Load a list of aligned nucleotide sequences, while specifying the DNA molecule t
 
     from cogent3 import make_aligned_seqs
 
-    DNA_seqs = [
-        ">sample1 Mus musculus",
-        "AACCTGC--C",
-        ">sample2 Gallus gallus",
-        "AAC-TGCAAC",
-    ]
+    DNA_seqs = {
+        "sample1 Mus musculus": "AACCTGC--C",
+        "sample2 Gallus gallus": "AAC-TGCAAC",
+    }
     loaded_seqs = make_aligned_seqs(
         DNA_seqs, moltype="dna", label_to_name=lambda x: x.split()[0]
     )
diff --git a/doc/cookbook/simple_trees.rst b/doc/cookbook/simple_trees.rst
index 1a08d670e..97b13644f 100644
--- a/doc/cookbook/simple_trees.rst
+++ b/doc/cookbook/simple_trees.rst
@@ -400,7 +400,7 @@ Add internal nodes so that every node has 2 or fewer children.
 
 .. jupyter-execute::
 
-    from cogent3 import load_tree
+    from cogent3 import make_tree
 
     tree_string = "(B:0.2,H:0.2,(C:0.3,D:0.4,E:0.1)F:0.5)G;"
     tr = make_tree(tree_string)
@@ -437,12 +437,61 @@ Branch lengths don't matter.
 
 .. jupyter-execute::
 
-    from cogent3 import load_tree
+    from cogent3 import make_tree
 
     tr1 = make_tree("(B:0.2,(C:0.2,D:0.2)F:0.2)G;")
     tr2 = make_tree("((C:0.1,D:0.1)F:0.1,B:0.1)G;")
     tr1.same_topology(tr2)
 
+Measure topological distances between two trees
+"""""""""""""""""""""""""""""""""""""""""""""""
+
+A number of topological tree distance metrics are available. They include:
+
+* The Robinson-Foulds Distance for rooted trees.
+* The Matching Cluster Distance for rooted trees.
+* The Robinson-Foulds Distance for unrooted trees.
+* The Lin-Rajan-Moret Distance for unrooted trees.
+
+There are several variations of the Robinson-Foulds metric in the literature. The definition used by ``cogent3`` is the
+cardinality of the symmetric difference of the sets of clades/splits in the two rooted/unrooted trees. Other definitions sometimes
+divide this by two, or normalise it to the unit interval. 
+
+The Robinson-Foulds distance is quick to compute, but is known to saturate quickly. Moving a single leaf in a tree can maximise this metric.
+
+The Matching Cluster and Lin-Rajan-Moret are two matching-based distances that are more statistically robust. 
+Unlike the Robinson-Foulds distance which counts how many of the splits/clades are not exactly same, the matching-based distances
+measures the degree by which the splits/clades are different. The matching-based distances solve a min-weight matching problem,
+which for large trees may take longer to compute.
+
+.. jupyter-execute::
+
+    # Distance metrics for rooted trees
+    from cogent3 import make_tree
+
+    tr1 = make_tree(treestring="(a,(b,(c,(d,e))));")
+    tr2 = make_tree(treestring="(e,(d,(c,(b,a))));")
+    
+    mc_distance = tr1.tree_distance(tr2, method="matching_cluster") # or method="mc" or method="matching"
+    rooted_rf_distance = tr1.tree_distance(tr2, method="rooted_robinson_foulds") # or method="rrf" or method="rf"
+
+    print("Matching Cluster Distance:", mc_distance)
+    print("Rooted Robinson Foulds Distance:", rooted_rf_distance)
+
+.. jupyter-execute::
+
+    # Distance metrics for unrooted trees
+    from cogent3 import make_tree
+    
+    tr1 = make_tree(treestring="(a,b,(c,(d,e)));")
+    tr2 = make_tree(treestring="((a,c),(b,d),e);")
+    
+    lrm_distance = tr1.tree_distance(tr2, method="lin_rajan_moret") # or method="lrm" or method="matching"
+    unrooted_rf_distance = tr1.tree_distance(tr2, method="unrooted_robinson_foulds") # or method="urf" or method="rf"
+    
+    print("Lin-Rajan-Moret Distance:", lrm_distance)
+    print("Unrooted Robinson Foulds Distance:", unrooted_rf_distance)
+
 Calculate each node's maximum distance to a tip
 """""""""""""""""""""""""""""""""""""""""""""""
 
@@ -451,7 +500,7 @@ the distance from that node to its most distant tip.
 
 .. jupyter-execute::
 
-    from cogent3 import load_tree
+    from cogent3 import make_tree
 
     tr = make_tree("(B:0.2,(C:0.3,D:0.4)F:0.5)G;")
     print(tr.ascii_art())
@@ -467,7 +516,7 @@ Scale branch lengths in place to integers for ascii output
 
 .. jupyter-execute::
 
-    from cogent3 import load_tree
+    from cogent3 import make_tree
 
     tr = make_tree("(B:0.2,(C:0.3,D:0.4)F:0.5)G;")
     print(tr)
@@ -484,7 +533,7 @@ and a list of the tip nodes.
 
 .. jupyter-execute::
 
-    from cogent3 import load_tree
+    from cogent3 import make_tree
 
     tr = make_tree("(B:3,(C:2,D:4)F:5)G;")
     d, tips = tr.tip_to_tip_distances()
@@ -504,7 +553,7 @@ Note: automatically strips out the names that don't match.
 
 .. jupyter-execute::
 
-    from cogent3 import load_tree
+    from cogent3 import make_tree
 
     tr1 = make_tree("(B:2,(C:3,D:4)F:5)G;")
     tr2 = make_tree("(C:2,(B:3,D:4)F:5)G;")
diff --git a/doc/cookbook/tables.rst b/doc/cookbook/tables.rst
index dc154597a..9e9d82f43 100644
--- a/doc/cookbook/tables.rst
+++ b/doc/cookbook/tables.rst
@@ -287,12 +287,12 @@ Get table data as a numpy array
 Get a table column as a list
 ============================
 
-Via the ``Table.tolist()`` method.
+Via the ``Table.to_list()`` method.
 
 .. jupyter-execute::
 
     table = load_table("data/stats.tsv")
-    locus = table.tolist("Locus")
+    locus = table.to_list("Locus")
     locus
 
 Or directly from the column array object.
@@ -301,6 +301,8 @@ Or directly from the column array object.
 
     table.columns["Locus"].tolist()
 
+.. note:: ``table.columns["Locus"]`` is a ``numpy.ndarray``, hence the different method call.
+
 Get multiple table columns as a list
 ====================================
 
@@ -309,7 +311,7 @@ This returns a row oriented list.
 .. jupyter-execute::
 
     table = load_table("data/stats.tsv")
-    rows = table.tolist(["Region", "Locus"])
+    rows = table.to_list(["Region", "Locus"])
     rows
 
 .. note:: column name order dictates the element order per row
@@ -340,14 +342,14 @@ Get the table as a ``pandas.DataFrame``
 .. jupyter-execute::
 
     table = load_table("data/stats.tsv")
-    df = table.to_dataframe()
+    df = table.to_pandas()
     df
 
 You can also specify column(s) are categories
 
 .. jupyter-execute::
 
-    df = table.to_dataframe(categories="Region")
+    df = table.to_pandas(categories="Region")
 
 Get a table of counts as a contingency table
 ============================================
@@ -528,7 +530,7 @@ Getting raw data for a single column
 .. jupyter-execute::
 
     table = load_table("data/stats.tsv")
-    raw = table.tolist("Region")
+    raw = table.to_list("Region")
     raw
 
 Getting raw data for multiple columns
@@ -537,7 +539,7 @@ Getting raw data for multiple columns
 .. jupyter-execute::
 
     table = load_table("data/stats.tsv")
-    raw = table.tolist(["Locus", "Region"])
+    raw = table.to_list(["Locus", "Region"])
     raw
 
 Getting distinct values
diff --git a/doc/draw/aln/plot_aln-dotplot-1.rst b/doc/draw/aln/plot_aln-dotplot-1.rst
index 38aaad1d3..1ba54e734 100644
--- a/doc/draw/aln/plot_aln-dotplot-1.rst
+++ b/doc/draw/aln/plot_aln-dotplot-1.rst
@@ -14,8 +14,6 @@ The method returns a drawable, as demonstrated below between unaligned sequences
 
 .. jupyter-execute::
 
-    import os
-
     from cogent3 import load_unaligned_seqs
 
     seqs = load_unaligned_seqs("data/SCA1-cds.fasta", moltype="dna")
diff --git a/doc/draw/aln/plot_aln-dotplot-3.rst b/doc/draw/aln/plot_aln-dotplot-3.rst
new file mode 100644
index 000000000..3fe1fe449
--- /dev/null
+++ b/doc/draw/aln/plot_aln-dotplot-3.rst
@@ -0,0 +1,75 @@
+.. jupyter-execute::
+    :hide-code:
+
+    import set_working_directory
+
+Assess alignment quality via dotplots
+=====================================
+
+The dotplot algorithm within ``cogent3`` offers a powerful tool to visualise the quality of a sequence alignment, as it shows both the matching segments between two sequences and the path chosen by the alignment algorithm. 
+
+Visualising an alignment with poor parameter choices
+----------------------------------------------------
+
+Let's begin by importing the functions and loading sequences for this example. Note that although we load aligned sequences, we use the ``degap`` method to remove all gap characters from the sequences. This produces a set of unaligned sequences, for which we can align with different parameters to demonstrate the use of dotplots to visualise alignment quality. 
+
+.. jupyter-execute::
+    :raises:
+
+    from cogent3 import load_aligned_seqs, get_app
+
+    seqs = load_aligned_seqs("data/brca1.fasta", moltype="dna").degap()
+    seqs = seqs.take_seqs(
+        ["LeafNose", "FalseVamp", "RoundEare", "Sloth", "Anteater", "HairyArma"]
+    )
+
+The choice of parameters, such as gap insertion and gap extension parameters, can dramatically impact the quality of an alignment. An example of a **bad choice** of parameters is a high probability of opening a gap, but a low probability of extending a gap. 
+
+Let's align using such parameters, and take a look at a dotplot between two of the sequences. 
+
+.. jupyter-execute::
+    :raises:
+
+    aligner = get_app("progressive_align", "nucleotide", indel_rate=1e-2, indel_length=1e-9)
+    aln = aligner(seqs)
+    dp = aln[2200:2500].dotplot("HairyArma", "RoundEare")
+    dp.show()
+
+.. jupyter-execute::
+    :hide-code:
+
+    outpath = set_working_directory.get_thumbnail_dir() / "plot_aln-dotplot-3.png"
+
+    dp.write(outpath)
+
+The dotplot clearly shows the misalignment between the two sequences. Towards the second half of the alignment, there is a section of the alignment path (the dashed line), which does not line up with any matching segments between the sequences (the blue line). 
+
+Taking a closer look at the poorly aligned section, we can see multiple small gaps in many of the sequences. The likelihood of this reflecting the true history of the sequences is low. It requires many indel events in many species and instead is likely to be an artefact of the alignment algorithm.
+
+.. jupyter-execute::
+    :raises:
+
+    aln[2300:2500] 
+
+Visualising an alignment with good parameter choices
+----------------------------------------------------
+
+Let's align the same sequences, but with more biologically realistic parameters. The default parameters for the progressive aligner are a better choice as they have a low probability of opening a gap, but a high probability of extending a gap.
+
+.. note:: The default parameters (of any program) are not always the best choice, but they are a good starting point for many alignments.
+
+.. jupyter-execute::
+    :raises:
+
+    aligner = get_app("progressive_align", "nucleotide")
+    aln = aligner(seqs)
+    aln[2200:2500].dotplot("HairyArma", "RoundEare").show()
+
+The dotplot shows a much better alignment between the two sequences. The alignment path (the dashed line) now follows the matching segments between the sequences (the blue line). 
+
+Taking a closer look at the same section, we can see a single large gap in two of the sequences. The likelihood of this reflecting the true history of the sequences is much higher than in the previous example, as it requires far fewer independent indel events to explain the alignment.
+
+.. jupyter-execute::
+    :raises:
+
+    aln[2300:2500]
diff --git a/doc/draw/aln/plot_seq-features.rst b/doc/draw/aln/plot_seq-features.rst
index 154d5bd5c..4bbbafbac 100644
--- a/doc/draw/aln/plot_seq-features.rst
+++ b/doc/draw/aln/plot_seq-features.rst
@@ -44,3 +44,10 @@ We specify what biotypes we want to display.
 
     fig = seq.get_drawable(biotype=("gene", "CDS", "mRNA"))
     fig.show(height=300, width=650)
+
+.. jupyter-execute::
+    :hide-code:
+
+    outpath = set_working_directory.get_thumbnail_dir() / "plot_seq-features.png"
+
+    fig.write(outpath)
diff --git a/doc/examples/codon_models.rst b/doc/examples/codon_models.rst
index e6fe9f02e..7a0e020bd 100644
--- a/doc/examples/codon_models.rst
+++ b/doc/examples/codon_models.rst
@@ -217,11 +217,20 @@ The following implements a modification of the approach of Zhang, Nielsen and Ya
 
     from cogent3 import make_table
 
-    header = ['Site Class', 'Proportion', 'Background Edges', 'Foreground Edges']
-    data = {'Site Class': array(['0', '1', '2a', '2b'], dtype='<U2'), 'Proportion': array(['p0', 'p1', 'p2', 'p3'], dtype='<U2'), 'Background Edges': array(['0 < omega0 < 1', 'omega1 = 1', '0 < omega0 < 1', 'omega1 = 1'],
-      dtype='<U14'), 'Foreground Edges': array(['0 < omega0 < 1', 'omega1 = 1', '0 < omega2 > 1', '0 < omega0 < 1'],
-      dtype='<U14')}
-    data = {k: array(data[k], dtype='U') for k in data}
+    header = ["Site Class", "Proportion", "Background Edges", "Foreground Edges"]
+    data = {
+        "Site Class": array(["0", "1", "2a", "2b"], dtype="<U2"),
+        "Proportion": array(["p0", "p1", "p2", "p3"], dtype="<U2"),
+        "Background Edges": array(
+            ["0 < omega0 < 1", "omega1 = 1", "0 < omega0 < 1", "omega1 = 1"],
+            dtype="<U14",
+        ),
+        "Foreground Edges": array(
+            ["0 < omega0 < 1", "omega1 = 1", "0 < omega2 > 1", "0 < omega0 < 1"],
+            dtype="<U14",
+        ),
+    }
+    data = {k: array(data[k], dtype="U") for k in data}
     table = make_table(header, data=data)
     HTML(table.set_repr_policy(show_shape=False))
 
@@ -246,12 +255,12 @@ We're also going to use the MLEs from the ``rate_lf`` model, since that nests wi
 .. jupyter-execute::
 
     globals = [t for t in tables if "global" in t.title][0]
-    globals = dict(zip(globals.header, globals.tolist()[0]))
+    globals = dict(zip(globals.header, globals.to_list()[0]))
     bin_params = [t for t in tables if "bin" in t.title][0]
-    rate_class_omegas = dict(bin_params.tolist(["bin", "omega"]))
-    rate_class_probs = dict(bin_params.tolist(["bin", "bprobs"]))
+    rate_class_omegas = dict(bin_params.to_list(["bin", "omega"]))
+    rate_class_probs = dict(bin_params.to_list(["bin", "bprobs"]))
     lengths = [t for t in tables if "edge" in t.title][0]
-    lengths = dict(lengths.tolist(["edge", "length"]))
+    lengths = dict(lengths.to_list(["edge", "length"]))
 
 We now create the more complex model,
 
diff --git a/doc/examples/demo-mpi-parallel.py b/doc/examples/demo-mpi-parallel.py
index ed70b634f..15920ea89 100644
--- a/doc/examples/demo-mpi-parallel.py
+++ b/doc/examples/demo-mpi-parallel.py
@@ -2,6 +2,8 @@
 import os
 import time
 
+from collections import Counter
+
 from cogent3.util import parallel
 
 
@@ -17,7 +19,6 @@ def is_prime(n):
     # Postprocess the processor MPI rank to check your job got the resources
     # you requested
     r = parallel.get_rank()
-    print(f"MPI Rank: {r}")
 
     if n % 2 == 0:
         return False
@@ -27,34 +28,43 @@ def is_prime(n):
         if n % i == 0:
             return False
 
-    return True
+    return r
+
+
+PRIMES = (
+    [
+        112272535095293,
+        112582705942171,
+        112272535095293,
+        115280095190773,
+        115797848077099,
+        117450548693743,
+        993960000099397,
+    ]
+    * PBS_NCPUS
+    * 20
+)
 
 
 def main():
+    print(f"World size: {parallel.SIZE}\n")
+
     # Each worker will evaluate 20 prime numbers. This is just to slow the
     # script down!
-    PRIMES = (
-        [
-            112272535095293,
-            112582705942171,
-            112272535095293,
-            115280095190773,
-            115797848077099,
-            117450548693743,
-            993960000099397,
-        ]
-        * PBS_NCPUS
-        * 20
-    )
 
-    print(f"MPI World size: {parallel.size}\n")
     start = time.time()
 
-    result = parallel.map(is_prime, PRIMES, use_mpi=True, max_workers=PBS_NCPUS)
-    if result != [True] * len(PRIMES):
-        print(" failed\n")
+    result = Counter(
+        parallel.as_completed(is_prime, PRIMES, use_mpi=True, max_workers=PBS_NCPUS)
+    )
+    if sum(result.values()) != len(PRIMES):
+        print(f" failed: {len(result)} != {len(PRIMES)} : {result=}")
     else:
-        print(f"{time.time() - start:.2f} seconds\n")
+        print(
+            f"Time taken = {time.time() - start:.2f} seconds",
+            f"CPU rank by number of jobs: {result}",
+            sep="\n",
+        )
 
 
 if __name__ == "__main__":
diff --git a/doc/examples/demo-multiprocess-parallel.py b/doc/examples/demo-multiprocess-parallel.py
index e5cbdedd3..7f5ec1f49 100644
--- a/doc/examples/demo-multiprocess-parallel.py
+++ b/doc/examples/demo-multiprocess-parallel.py
@@ -1,13 +1,13 @@
 import math
-import os
 import time
 
+from collections import Counter
+
 from cogent3.util import parallel
 
 
 def is_prime(n):
     r = parallel.get_rank()
-    print(f"Rank: {r}")
 
     if n % 2 == 0:
         return False
@@ -17,23 +17,39 @@ def is_prime(n):
         if n % i == 0:
             return False
 
-    return True
+    return r
+
+
+PRIMES = (
+    [
+        112272535095293,
+        112582705942171,
+        112272535095293,
+        115280095190773,
+        115797848077099,
+        117450548693743,
+        993960000099397,
+    ]
+    * 4
+    * 20
+)  # multiplying just to increase the amount of data to calculate
 
 
-PRIMES = [
-    112272535095293,
-    112582705942171,
-    112272535095293,
-    115280095190773,
-    115797848077099,
-    117450548693743,
-    993960000099397,
-] * 4  # multiplying just to increase the amount of data to calculate
+def main():
+    print(f"World size: {parallel.SIZE}\n")
 
+    start = time.time()
+    result = Counter(parallel.as_completed(is_prime, PRIMES, max_workers=4))
 
-print(f"World size: {parallel.size}\n")
+    if sum(result.values()) != len(PRIMES):
+        print(f" failed: {len(result)} != {len(PRIMES)} : {result=}")
+    else:
+        print(
+            f"Time taken = {time.time() - start:.2f} seconds",
+            f"CPU rank by number of jobs: {result}",
+            sep="\n",
+        )
 
-start = time.time()
-result = parallel.map(is_prime, PRIMES, max_workers=4)
 
-print(f"{time.time() - start:.2f} seconds\n")
+if __name__ == "__main__":
+    main()
diff --git a/doc/examples/parallel.rst b/doc/examples/parallel.rst
index b249031f8..daa49ed9a 100644
--- a/doc/examples/parallel.rst
+++ b/doc/examples/parallel.rst
@@ -31,14 +31,18 @@ If you are using ``cogent3`` :ref:`composable apps <apps>`, then the simplest ap
 
 .. note:: If you are using mpi, set ``par_kw=dict(max_workers=4, use_mpi=True)``.
 
-Directly using ``cogent3.util.parallel.map()``
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Directly using ``cogent3.util.parallel.as_completed()``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-This function behaves like the standard python builtin ``map()`` except it enables distribution of calculations across CPUs.  The demo script shown below calculates a small number of prime numbers by splitting chunks of numbers across the provided cores. The key line is
+This function enables distribution of calculations across CPUs.
+
+.. warning:: This function delivers results in the order completed! This is different to ``cogent3.util.parallel.map()`` which delivers results in the order provided.
+
+The demo script shown below calculates a small number of prime numbers by splitting chunks of numbers across the provided cores. The key line is
 
 .. code-block:: python
 
-    result = parallel.map(is_prime, PRIMES, max_workers=4)
+    result = parallel.as_completed(is_prime, PRIMES, max_workers=4)
 
 The first argument, ``is_prime``, is the function to be called with values from the data, ``PRIMES``. The ``max_workers`` argument indicates how many worker processes to use. The elements of ``PRIMES`` will be broken into ``max_workers`` number of equal sized chunks. Each such chunk is applied to ``is_prime`` on a separate CPU. In this case, the returned results will be a series of ``bool`` values.
 
diff --git a/doc/index.rst b/doc/index.rst
index 3c125be7f..7aaf53050 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -4,64 +4,49 @@ User Guides
 
 Our documentation shows code examples that have been computed within Jupyter notebooks during the documentation build. This shows what you would see if you were to do these examples yourself, interactively, in Jupyter_. To do that, you will need to :ref:`install cogent3 <install>` and download the individual data files used in the User Guides from :ref:`here <data_links>`.
 
-.. panels::
-    :header: text-white bg-primary font-weight-bold
+.. grid::
 
-    ---
+    .. grid-item-card::
+        :link: app/index.html
+        :link-type: url
+        :class-header: bg-info
 
-    cogent3 apps
-    ^^^^^^^^^^^^
+        cogent3 apps
+        ^^^^^^^^^^^^
 
-    Pre-defined "apps" simplify complex tasks. They provide capabilities that can be used by themselves, or added together to define a pipeline.
+        Pre-defined "apps" simplify complex tasks. They provide capabilities that can be used by themselves, or added together to define a pipeline.
 
-    +++++++++++
+    .. grid-item-card::
+        :link: cookbook/index.html
+        :link-type: url
+        :class-header: bg-info
 
-    .. link-button:: app/index
-        :type: ref
-        :text:
-        :classes: stretched-link
+        Cookbook
+        ^^^^^^^^
 
-    ---
+        Short "how-to" code recipes targeted at specific problems.
 
-    Cookbook
-    ^^^^^^^^
+.. grid::
 
-    Short "how-to" code recipes targeted at specific problems.
+    .. grid-item-card::
+        :link: examples/index.html
+        :link-type: url
+        :class-header: bg-info
 
-    +++++++++++
+        Tutorials
+        ^^^^^^^^^
 
-    .. link-button:: cookbook/index
-        :type: ref
-        :text:
-        :classes: stretched-link
+        Code for solving more extensive problems.
 
-    ---
+    .. grid-item-card::
+        :link: api/index.html
+        :link-type: url
+        :class-header: bg-info
 
-    Tutorials
-    ^^^^^^^^^
+        API
+        ^^^
 
-    Code for solving more extensive problems.
-
-    +++++++++++
-
-    .. link-button:: examples/index
-        :type: ref
-        :text:
-        :classes: stretched-link
-
-    ---
-
-    API
-    ^^^
-
-    The API for major ``cogent3`` objects.
-
-    +++++++++++
-
-    .. link-button:: api/index
-        :type: ref
-        :text:
-        :classes: stretched-link
+        The API for major ``cogent3`` objects.
 
 .. toctree::
     :hidden:
diff --git a/noxfile.py b/noxfile.py
index 7309928de..ea45369a8 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -3,7 +3,7 @@
 import nox
 
 
-_py_versions = range(8, 12)
+_py_versions = range(9, 12)
 
 
 @nox.session(python=[f"3.{v}" for v in _py_versions])
diff --git a/pyproject.toml b/pyproject.toml
index 18083d7d4..e72cac35d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,7 +6,7 @@ build-backend = "flit_core.buildapi"
 name = "cogent3"
 description = """COmparative GENomics Toolkit 3: genomic sequence analysis within notebooks or on compute systems with 1000s of CPUs."""
 # remember to update cogent3/_version.py too!
-version = "2023.9.22a1"
+version = "2023.12.15a1"
 authors = [
     { name = "Gavin Huttley", email = "Gavin.Huttley@anu.edu.au"},
 ]
@@ -15,7 +15,6 @@ readme = "README.md"
 license = { file = "LICENSE" }
 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'",
@@ -24,7 +23,7 @@ dependencies = ["chardet",
         "tqdm",
         "typing_extensions"]
 # remember to update version in requires-python and classifiers
-requires-python = ">=3.8,<3.12"
+requires-python = ">=3.9,<3.12"
 classifiers = [
         "Development Status :: 5 - Production/Stable",
         "Intended Audience :: Science/Research",
@@ -32,7 +31,6 @@ classifiers = [
         "Topic :: Scientific/Engineering :: Bio-Informatics",
         "Topic :: Software Development :: Libraries :: Python Modules",
         "Operating System :: OS Independent",
-        "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
         "Programming Language :: Python :: 3.10",
         "Programming Language :: Python :: 3.11",
@@ -79,7 +77,7 @@ doc  = ["click==8.1.7",
         "sphinx",
         "sphinx-autobuild",
         "pydata_sphinx_theme",
-        "sphinx_panels",
+        "sphinx_design",
         "sphinxcontrib-bibtex",
         "sphinxcontrib-video"]
 extra = ["pandas", "plotly", "psutil", "kaleido"]
@@ -112,7 +110,7 @@ dev = ["black==23.9.1",
        "sphinx",
        "sphinx-autobuild",
        "pydata_sphinx_theme",
-       "sphinx_panels",
+       "sphinx_design",
        "sphinxcontrib-bibtex",
        "sphinxcontrib-video",
        "tinydb"
@@ -126,9 +124,9 @@ exclude = ["doc/*.html"]
 markers = [
     "slow: marks tests as slow (deselect with '-m \"not slow\"')",
     ]
-
-addopts = ["--strict-config", "-ra"]
 testpaths = "tests"
+addopts = ["--strict-config"]
+norecursedirs = ["doc", ".nox"]
 filterwarnings = [
     'ignore:FORCED EXIT from optimiser',
     'ignore:Motif probs overspecified',
@@ -137,7 +135,7 @@ filterwarnings = [
 
 [tool.black]
 line-length = 88
-target-version = ["py38", "py39", "py310"]
+target-version = ["py39", "py310", "py311"]
 exclude = '''
 /(
     \.eggs
diff --git a/requirements.txt b/requirements.txt
index fa0f3c4e6..5da4f1f52 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,13 +1,13 @@
 chardet==5.2.0
-pillow==10.0.1
-psutil==5.9.5
-numpy==1.26.0
+pillow==10.1.0
+psutil==5.9.6
+numpy==1.26.2
 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.11.2
+scipy==1.11.4
 nox==2023.4.22
-plotly==5.17.0
+plotly==5.18.0
 typing_extensions>=3.7
 .[dev]
diff --git a/src/cogent3/__init__.py b/src/cogent3/__init__.py
index 94c6af108..ac4d511a7 100644
--- a/src/cogent3/__init__.py
+++ b/src/cogent3/__init__.py
@@ -52,7 +52,7 @@
 __license__ = "BSD-3"
 
 
-_min_version = (3, 8)
+_min_version = (3, 9)
 if (sys.version_info.major, sys.version_info.minor) < _min_version:
     PY_VERSION = ".".join([str(n) for n in sys.version_info])
     _min_version = ".".join(str(e) for e in _min_version)
diff --git a/src/cogent3/_version.py b/src/cogent3/_version.py
index ebe52a4e3..f53842dae 100644
--- a/src/cogent3/_version.py
+++ b/src/cogent3/_version.py
@@ -1,2 +1,2 @@
 # remember to update version in pyproject.toml too!
-__version__ = "2023.9.22a1"
+__version__ = "2023.12.15a1"
diff --git a/src/cogent3/align/align.py b/src/cogent3/align/align.py
index 53ab1d51d..6222f5c68 100644
--- a/src/cogent3/align/align.py
+++ b/src/cogent3/align/align.py
@@ -48,7 +48,10 @@ def _align_pairwise(
     s1, s2, mprobs, psub, TM, local, return_alignment=True, return_score=False, **kw
 ):
     """Generic alignment with any substitution model and indel model"""
-    [p1, p2] = [make_likelihood_tree_leaf(seq) for seq in [s1, s2]]
+    [p1, p2] = [
+        make_likelihood_tree_leaf(seq, seq.moltype.alphabet, seq.name)
+        for seq in [s1, s2]
+    ]
     [p1, p2] = [pairwise.AlignableSeq(leaf) for leaf in [p1, p2]]
     pair = pairwise.Pair(p1, p2)
     EP = pair.make_simple_emission_probs(mprobs, [psub])
diff --git a/src/cogent3/align/progressive.py b/src/cogent3/align/progressive.py
index 1498c85a8..d3f167b5d 100644
--- a/src/cogent3/align/progressive.py
+++ b/src/cogent3/align/progressive.py
@@ -14,8 +14,8 @@ def tree_align(
     model: str,
     seqs: SequenceCollection,
     tree: Optional[TreeNode] = None,
-    indel_rate: float = 0.01,
-    indel_length: float = 0.01,
+    indel_rate: float = 1e-10,
+    indel_length: float = 1e-1,
     ui=None,
     params_from_pairwise: bool = True,
     param_vals: dict = None,
@@ -66,7 +66,7 @@ def tree_align(
     param_vals = dict(param_vals) if param_vals else {}
 
     model = get_model(model)
-    moltype = model.alphabet.moltype
+    moltype = model.moltype
 
     num_states = len(model.alphabet)
 
@@ -193,17 +193,3 @@ def _progressive_hmm(indel_length, indel_rate, model, param_vals, seqs, tree):
     )
     align.info["align_params"] = param_vals
     return align
-
-
-def TreeAlign(*args, **kwargs):  # pragma: no cover
-    """deprecated, used tree_align()"""
-    from cogent3.util.warning import deprecated
-
-    deprecated(
-        "function",
-        "TreeAlign",
-        "tree_align",
-        "2023.9",
-    )
-
-    return tree_align(*args, **kwargs)
diff --git a/src/cogent3/align/pycompare.py b/src/cogent3/align/pycompare.py
index ac68fe348..4abcacdb4 100644
--- a/src/cogent3/align/pycompare.py
+++ b/src/cogent3/align/pycompare.py
@@ -488,7 +488,7 @@ def plotly_trace(
         return trace
 
 
-def _calc_seed_size(w: int, t: int) -> int:
+def _calc_seed_size(w: int, t: int, min_val: int = 5) -> int:
     """computes k-mer size
 
     Parameters
@@ -497,17 +497,29 @@ def _calc_seed_size(w: int, t: int) -> int:
         window size
     t : int
         threshold for minimum number of matches
+    min_val : int
+        minimum k-mer size
 
     Returns
     -------
     int
-        returns the larger of (w // k) * k <= t OR w - t + 1
+        k-mer size
+
+    Notes
+    -----
+    This seeks to return the maximum value of k that would guarantee the
+    detection of a w sized segment with t matches. Evenly spaced mismatches
+    represent the hardest case. We set a min_val for k to ensure performance
+    is maintained at the cost of some loss of sensitivity.
     """
     assert 0 < t <= w, f"threshold={t} > window size={w}"
-
-    # k should be such that (w % k) * k < threshold
-    denom = w - t + 1
-    return max(min(w // denom, t), denom)
+    d = w - t
+    if w == t:
+        return w
+    elif d > t:
+        return t
+    k = t // d
+    return max(k, min_val)
 
 
 def find_matched_paths(
@@ -530,7 +542,8 @@ def find_matched_paths(
     window : int
         size of sequence segment to be compared
     threshold : int
-        Minimum number of positions that must be equal within the window.Less than or equal to window.
+        Minimum number of positions that must be equal within the window.
+        Less than or equal to window.
 
     Returns
     -------
@@ -572,4 +585,16 @@ def find_matched_paths(
 
         paths.append(ref_coord, other_coord)
 
+    for y_intercept in paths.paths:
+        if len(paths.paths[y_intercept]) == 1:
+            continue
+        merged = [paths.paths[y_intercept][0]]
+        for x2, y2 in paths.paths[y_intercept][1:]:
+            x1, y1 = merged[-1]
+            if x1.overlap(x2):
+                merged[-1] = (x1 | x2, y1 | y2)
+            else:
+                merged.append((x2, y2))
+        paths.paths[y_intercept] = merged
+
     return paths
diff --git a/src/cogent3/app/__init__.py b/src/cogent3/app/__init__.py
index f7631c4c7..c126c3eca 100644
--- a/src/cogent3/app/__init__.py
+++ b/src/cogent3/app/__init__.py
@@ -6,6 +6,8 @@
 import re
 import textwrap
 
+from cogent3.util.table import Table
+
 from .io import open_data_store
 
 
@@ -50,8 +52,14 @@ def _make_types(app) -> dict:
     return _types
 
 
-def available_apps():
-    """returns Table listing the available apps"""
+def available_apps(name_filter: str | None = None) -> Table:
+    """returns Table listing the available apps
+
+    Parameters
+    ----------
+    name_filter
+        include apps whose name includes name_filter
+    """
     from cogent3.util.table import Table
 
     from .composable import __app_registry
@@ -68,6 +76,9 @@ def available_apps():
         if any(app.startswith(d) for d in deprecated):
             continue
 
+        if name_filter and name_filter not in app:
+            continue
+
         with contextlib.suppress(AttributeError):
             # probably a local scope issue in testing!
             rows.append(_get_app_attr(app, is_comp))
@@ -144,7 +155,7 @@ def _get_app_matching_name(name: str):
         raise NameError(f"no app matching name {name!r}")
     elif table.shape[0] > 1:
         raise NameError(f"too many apps matching name {name!r},\n{table}")
-    modname, _ = table.tolist(columns=["module", "name"])[0]
+    modname, _ = table.to_list(columns=["module", "name"])[0]
     mod = importlib.import_module(modname)
     return getattr(mod, name)
 
diff --git a/src/cogent3/app/align.py b/src/cogent3/app/align.py
index b6b7ada6d..34c83c3e9 100644
--- a/src/cogent3/app/align.py
+++ b/src/cogent3/app/align.py
@@ -470,7 +470,7 @@ def __init__(
         )
         kwargs = {} if gc is None else dict(gc=gc)
         sm = get_model(sm, **kwargs)
-        moltype = sm.alphabet.moltype
+        moltype = sm.moltype
         self._model = sm
         self._scalar = sm.word_length
         self._indel_length = indel_length
diff --git a/src/cogent3/app/composable.py b/src/cogent3/app/composable.py
index 8ad162d42..8ec880811 100644
--- a/src/cogent3/app/composable.py
+++ b/src/cogent3/app/composable.py
@@ -680,7 +680,7 @@ def _as_completed(self, dstore, parallel=False, par_kw=None, **kwargs) -> Genera
         dstore = [dstore]
     mapped = _proxy_input(dstore)
     if not mapped:
-        raise ValueError("dstore is empty")
+        return mapped
 
     if parallel:
         par_kw = par_kw or {}
diff --git a/src/cogent3/app/dist.py b/src/cogent3/app/dist.py
index 4c019cbdd..3f28ad04c 100644
--- a/src/cogent3/app/dist.py
+++ b/src/cogent3/app/dist.py
@@ -159,11 +159,12 @@ def jaccard_dist(seq_coll: UnalignedSeqsType, k: int = 10) -> PairwiseDistanceTy
     seq_coll: UnalignedSeqsType
         a collection of unaligned sequences
     k: int
-        size of kmer to use for
+        size of kmer. Default is 10.
 
     Returns
     -------
-    Pairwise Jaccard distance between sequences in the collection.
+    PairwiseDistanceType
+        Pairwise Jaccard distance between sequences in the collection.
     """
 
     kmers = {
@@ -194,22 +195,34 @@ def jaccard_dist(seq_coll: UnalignedSeqsType, k: int = 10) -> PairwiseDistanceTy
 
 @define_app
 def approx_pdist(jaccard_dists: PairwiseDistanceType) -> PairwiseDistanceType:
-    """Approximate the proportion sites different from Jaccard distances
+    """Calculates an approximation of the p-distance between sequences based
+    on Jaccard distances (see Notes for details).
 
     Parameters
     ----------
-    jaccard_dists
-        The pairwise Jaccard distance matrix
+    jaccard_dists : DistanceMatrix
+        A DistanceMatrix containing pairwise Jaccard distances between sequences.
 
     Returns
     -------
-    DistanceMatrix of approximated proportion sites different
+    DistanceMatrix
+        Pairwise approximated p-distance between sequences.
 
     Notes
     -----
-    Coefficients were derived from a polynomial fit between Jaccard distance
-    of kmers with k=10 and the proportion of sites different using mammalian
-    106 protein coding gene DNA sequence alignments.
+    This approximation assumes the input DistanceMatrix contains Jaccard
+    distances. See the ``jaccard_dist`` app for calculating Jaccard distances.
+
+    The true p-distance measures the proportion of differing sites between two
+    aligned sequences. For the true p-distance, 0 indicates identical sequences,
+    and 1 indicates completely different sequences.
+
+    This approximation does not guarantee bounds of 0 and 1.
+
+    The method approximates the p-distance using coefficients derived from a
+    polynomial fit between Jaccard distance and p-distance. The coefficients
+    were fitted using data from 106 DNA sequences of mammalian protein coding
+    genes, with kmers of size k=10.
     """
     upper_indices = triu_indices(n=jaccard_dists.shape[0], k=1)
     result = deepcopy(jaccard_dists)  # so the original matrix not modified
diff --git a/src/cogent3/app/sample.py b/src/cogent3/app/sample.py
index c6c53e685..a335f944e 100644
--- a/src/cogent3/app/sample.py
+++ b/src/cogent3/app/sample.py
@@ -329,7 +329,7 @@ def _set_names(self, data):
             self._names = data.names[: self._number]
             return
 
-        self._names = np_random.choice(data.names, self._number, replace=False)
+        self._names = np_random.choice(data.names, self._number, replace=False).tolist()
 
     T = Union[SerialisableType, SeqsCollectionType]
 
diff --git a/src/cogent3/app/typing.py b/src/cogent3/app/typing.py
index 035a0c725..fa5c42712 100644
--- a/src/cogent3/app/typing.py
+++ b/src/cogent3/app/typing.py
@@ -3,11 +3,14 @@
 from __future__ import annotations
 
 import inspect
+import re
 
 from typing import ForwardRef, Iterable, TypeVar, Union
 
 from typing_extensions import get_args, get_origin
 
+from cogent3.util import warning as c3warn
+
 
 AlignedSeqsType = TypeVar("AlignedSeqsType", "Alignment", "ArrayAlignment")
 UnalignedSeqsType = TypeVar("UnalignedSeqsType", bound="SequenceCollection")
@@ -44,6 +47,18 @@
 # todo when move to python 3.8 define protocols for IdentifierType and SerialisableType
 IdentifierType = TypeVar("IdentifierType")
 
+
+def _is_type(text):
+    p = re.compile("[A-Z][a-z]+")
+    matches = list(p.finditer(text))
+    if len(matches) <= 1 or matches[0].start() != 0:
+        return False
+
+    return matches[-1].group() == "Type"
+
+
+_all_types = {n: t for n, t in locals().items() if _is_type(n)}
+
 # the following constants are deprecated
 ALIGNED_TYPE = "aligned"
 IDENTIFIER_TYPE = "identifier"
@@ -58,18 +73,6 @@
 RESULT_TYPE = "result"
 TABULAR_RESULT_TYPE = "tabular_result"
 
-_mappings = {
-    TABULAR_TYPE: TabularType,
-    ALIGNED_TYPE: AlignedSeqsType,
-    SEQUENCE_TYPE: SeqsCollectionType,
-    IDENTIFIER_TYPE: IdentifierType,
-    PAIRWISE_DISTANCE_TYPE: PairwiseDistanceType,
-    SERIALISABLE_TYPE: SerialisableType,
-    TREE_TYPE: TreeType,
-    BOOTSTRAP_RESULT_TYPE: BootstrapResultType,
-    HYPOTHESIS_RESULT_TYPE: HypothesisResultType,
-}
-
 
 def get_constraint_names(*hints) -> set[str, ...]:
     """returns the set of named constraints of a type hint"""
@@ -103,17 +106,6 @@ def get_constraint_names(*hints) -> set[str, ...]:
     return all_hints
 
 
-def hints_from_strings(*strings: Iterable[str]) -> list:
-    """returns list of type hints corresponding to string values"""
-    types = []
-    for string in strings:
-        string = string.lower()
-        if string not in _mappings:
-            raise ValueError(f"{string!r} not a known type constant")
-        types.append(_mappings[string])
-    return types
-
-
 def type_tree(hint, depth=0) -> tuple:
     """compute the order of types"""
     level_type = get_origin(hint)
@@ -137,3 +129,29 @@ def type_tree(hint, depth=0) -> tuple:
         levels = (levels,)
 
     return depth, (level_type, levels)
+
+
+def defined_types():
+    """returns a table of the type hints and the cogent3 classes they represent
+
+    Notes
+    -----
+    These (or standard Python) types are required to annotate argument and
+    return values from cogent3 apps. They define the compatability of apps.
+    """
+    from cogent3.util.table import Table
+
+    rows = [[n, ", ".join(get_constraint_names(t))] for n, t in _all_types.items()]
+    title = "To use a type hint, from cogent3.app import typing"
+    legend = (
+        "An app which uses one of these hints is compatible with the indicated types."
+    )
+    table = Table(
+        header=["type hint", "includes"],
+        data=rows,
+        title=title,
+        legend=legend,
+        index_name="type hint",
+    )
+    table.set_repr_policy(show_shape=False)
+    return table
diff --git a/src/cogent3/core/alignment.py b/src/cogent3/core/alignment.py
index 21c84f821..ae3337312 100644
--- a/src/cogent3/core/alignment.py
+++ b/src/cogent3/core/alignment.py
@@ -24,6 +24,7 @@
 import json
 import os
 import re
+import typing
 import warnings
 
 from collections import Counter, defaultdict
@@ -82,6 +83,7 @@
 from cogent3.util.misc import (
     bytes_to_string,
     extend_docstring_from,
+    get_first_value,
     get_object_provenance,
     get_setting_from_environ,
 )
@@ -248,7 +250,10 @@ def coerce_to_string(s):
         return "".join(map(str, s))
 
 
-def seqs_from_array(a, alphabet=None):
+@c3warn.deprecated_args(
+    "2024.3", reason="replaced by different loading mechanism", discontinued=True
+)
+def seqs_from_array(a, alphabet=None):  # pragma: no cover
     """SequenceCollection from array of pos x seq: names are integers.
 
     This is an InputHandler for SequenceCollection. It converts an arbitrary
@@ -257,7 +262,10 @@ def seqs_from_array(a, alphabet=None):
     return list(transpose(a)), None
 
 
-def seqs_from_array_seqs(seqs, alphabet=None):
+@c3warn.deprecated_args(
+    "2024.3", reason="replaced by different loading mechanism", discontinued=True
+)
+def seqs_from_array_seqs(seqs, alphabet=None):  # pragma: no cover
     """Alignment from ArraySequence objects: seqs -> array, names from seqs.
 
     This is an InputHandler for SequenceCollection. It converts a list of
@@ -267,7 +275,10 @@ def seqs_from_array_seqs(seqs, alphabet=None):
     return seqs, [s.name for s in seqs]
 
 
-def seqs_from_generic(seqs, alphabet=None):
+@c3warn.deprecated_args(
+    "2024.3", reason="replaced by different loading mechanism", discontinued=True
+)
+def seqs_from_generic(seqs, alphabet=None):  # pragma: no cover
     """returns seqs, names"""
     names = []
     for s in seqs:
@@ -278,7 +289,10 @@ def seqs_from_generic(seqs, alphabet=None):
     return seqs, names
 
 
-def seqs_from_fasta(seqs, alphabet=None):
+@c3warn.deprecated_args(
+    "2024.3", reason="replaced by different loading mechanism", discontinued=True
+)
+def seqs_from_fasta(seqs, alphabet=None):  # pragma: no cover
     """SequenceCollection from FASTA-format string or lines.
 
     This is an InputHandler for SequenceCollection. It converts a FASTA-format
@@ -291,7 +305,10 @@ def seqs_from_fasta(seqs, alphabet=None):
     return list(seqs), list(names)
 
 
-def seqs_from_dict(seqs, alphabet=None):
+@c3warn.deprecated_args(
+    "2024.3", reason="replaced by different loading mechanism", discontinued=True
+)
+def seqs_from_dict(seqs, alphabet=None):  # pragma: no cover
     """SequenceCollection from dict of {label:seq_as_str}.
 
     This is an InputHandler for SequenceCollection. It converts a dict in
@@ -304,7 +321,10 @@ def seqs_from_dict(seqs, alphabet=None):
     return seqs, names
 
 
-def seqs_from_kv_pairs(seqs, alphabet=None):
+@c3warn.deprecated_args(
+    "2024.3", reason="replaced by different loading mechanism", discontinued=True
+)
+def seqs_from_kv_pairs(seqs, alphabet=None):  # pragma: no cover
     """SequenceCollection from list of (key, val) pairs.
 
     This is an InputHandler for SequenceCollection. It converts a dict in
@@ -317,7 +337,10 @@ def seqs_from_kv_pairs(seqs, alphabet=None):
     return seqs, names
 
 
-def seqs_from_aln(seqs, alphabet=None):
+@c3warn.deprecated_args(
+    "2024.3", reason="replaced by different loading mechanism", discontinued=True
+)
+def seqs_from_aln(seqs, alphabet=None):  # pragma: no cover
     """SequenceCollection from existing SequenceCollection object: copies data.
 
     This is relatively inefficient: you should really use the copy() method
@@ -326,7 +349,10 @@ def seqs_from_aln(seqs, alphabet=None):
     return seqs.seqs, seqs.names
 
 
-def seqs_from_empty(obj, *args, **kwargs):
+@c3warn.deprecated_args(
+    "2024.3", reason="replaced by different loading mechanism", discontinued=True
+)
+def seqs_from_empty(obj, *args, **kwargs):  # pragma: no cover
     """SequenceCollection from empty data: raise exception."""
     raise ValueError("Cannot create empty SequenceCollection.")
 
@@ -377,6 +403,59 @@ def _(seqs: dict) -> SupportsFeatures:
     return merged_db_collection(seqs.values())
 
 
+@functools.singledispatch
+def _moltype_from_data(data):
+    """returns moltype if data has one"""
+    if not data:
+        return None
+
+    if hasattr(data, "moltype"):
+        return data.moltype
+    elif hasattr(data[1], "moltype"):
+        return data[1].moltype
+
+
+@_moltype_from_data.register
+def _(data: dict):
+    return _moltype_from_data(get_first_value(data))
+
+
+@_moltype_from_data.register
+def _(data: list):
+    return _moltype_from_data(get_first_value(data))
+
+
+@_moltype_from_data.register
+def _(data: tuple):
+    return _moltype_from_data(get_first_value(data))
+
+
+@_moltype_from_data.register
+def _(data: set):
+    return _moltype_from_data(get_first_value(data))
+
+
+@_moltype_from_data.register
+def _(data: str):
+    return None
+
+
+@_moltype_from_data.register
+def _(data: bytes):
+    return None
+
+
+@_moltype_from_data.register
+def _(data: ndarray):
+    return None
+
+
+@_moltype_from_data.register
+def _(data: None):
+    return None
+
+
+# GAH edit the docstrings on class and constructor
 @total_ordering
 class _SequenceCollectionBase:
     """
@@ -394,37 +473,29 @@ class _SequenceCollectionBase:
     - moltype: specifies what kind of sequences are in the collection
     """
 
-    _input_handlers = {
-        "array": seqs_from_array,
-        "array_seqs": seqs_from_array_seqs,
-        "generic": seqs_from_generic,
-        "fasta": seqs_from_fasta,
-        "collection": seqs_from_aln,
-        "aln": seqs_from_aln,
-        "array_aln": seqs_from_aln,
-        "dict": seqs_from_dict,
-        "empty": seqs_from_empty,
-        "kv_pairs": seqs_from_kv_pairs,
-    }
-
     is_array = set(["array", "array_seqs"])
 
-    _make_names = assign_sequential_names
-
+    @c3warn.deprecated_args(
+        "2024.3",
+        reason="simplifying API",
+        discontinued=(
+            "remove_duplicate_names",
+            "alphabet",
+            "suppress_named_seqs",
+            "force_same_data",
+        ),
+    )
     def __init__(
         self,
         data,
         names=None,
-        alphabet=None,
         moltype=None,
         name=None,
         info=None,
         conversion_f=None,
         is_array=False,
-        force_same_data=False,
-        remove_duplicate_names=False,
         label_to_name=None,
-        suppress_named_seqs=False,
+        **kwargs,  # delete me when deprecation takes effect
     ):
         """Initialize self with data and optionally info.
 
@@ -435,7 +506,7 @@ def __init__(
 
         The handling of sequence names requires special attention. Depending
         on the input data, we might get the names from the sequences themselves,
-        or we might add them from Names that are passed in. However, the Names
+        or we might add them from names that are passed in. However, the names
         attribute controls the order that we examine the sequences in, so if
         it is passed in it should override the order that we got from the
         input data (e.g. you might pass in unlabeled sequences with the names
@@ -456,115 +527,70 @@ def __init__(
         is already labeled (e.g. a list of Sequence objects) you _must_ have
         unique names beforehand.
 
-        It's possible that this additional handling should be moved to a
-        separate object; the motivation for having it on Alignment __init__
-        is that it's easy for users to construct Alignment objects directly.
 
         Parameters
         ----------
 
         data
             Data to convert into a SequenceCollection
-        Names
-            Order of Names in the alignment. Should match the
+        names
+            Order of names in the alignment. Should match the
             names of the sequences (after processing by
             label_to_name if present).
-        alphabet
-            Alphabet to use for the alignment (primarily important
-            for ArrayAlignment)
         moltype
-            moltype to be applied to the Alignment and to each seq.
+            moltype to be applied to the collection and to each seq. The alphabet
+            is derived from this.
         name
             name of the SequenceCollection.
         info
             info object to be attached to the alignment itself.
         conversion_f
-            Function to convert string into sequence.
+            Function to convert input sequence data. These are automatically
+            specified for the different sublcasses.
         is_array
             True if input is an array, False otherwise.
-        force_same_data
-            True if data will be used as the same object.
-        remove_duplicate_names
-            True if duplicate names are to be silently
-            deleted instead of raising errors.
         label_to_name
             if present, converts name into f(name).
-        suppress_named_seqs
-            create name_seqs attribute if True (default)
         """
+        if isinstance(data, (typing.Generator, typing.Iterator)):
+            data = tuple(data)
 
-        # read all the data in if we were passed a generator
-        if isinstance(data, GeneratorType):
-            data = list(data)
-
-        input_type = self._guess_input_type(data)
-        if input_type in ("aln", "collection"):
-            anno_db = merged_db_collection(data.seqs)
-        elif input_type == "kv_pairs":
-            anno_db = merged_db_collection(dict(data))
-        elif isinstance(data, (tuple, list, dict)):
-            anno_db = merged_db_collection(data)
-        else:
-            anno_db = None
+        if moltype is None:
+            moltype = _moltype_from_data(data)
+        moltype = cogent3.get_moltype(moltype or self.moltype)
+        seqs, names = conversion_f(
+            data, names, moltype=moltype, label_to_name=label_to_name or str
+        )
+
+        self.moltype = moltype
+        try:
+            alphabet = moltype.alphabets.degen_gapped
+        except AttributeError:
+            alphabet = moltype.alphabet
+
+        self.alphabet = alphabet
+        self.names = list(names)
+        self._named_seqs = None
         # set the name
         self.name = name
-        names = list(names) if names is not None else names
-        # figure out alphabet and moltype
-        self.alphabet, self.moltype = self._get_alphabet_and_moltype(
-            alphabet, moltype, data
-        )
+        self._repr_policy = dict(num_seqs=10, num_pos=60, ref_name="longest", wrap=60)
+
         if not isinstance(info, InfoClass):
             if info:
                 info = InfoClass(info)
             else:
                 info = InfoClass()
         self.info = info
-        # if we're forcing the same data, skip the validation
-        if force_same_data:
-            self._force_same_data(data, names)
-            if isinstance(data, dict):
-                curr_seqs = list(data.values())
-            else:
-                curr_seqs = data
-        # otherwise, figure out what we got and coerce it into the right type
-        else:
-            per_seq_names, curr_seqs, name_order = self._names_seqs_order(
-                conversion_f,
-                data,
-                names,
-                is_array,
-                label_to_name,
-                remove_duplicate_names,
-                alphabet=self.alphabet,
-            )
-            self.names = list(name_order)
-
-            # will take only the seqs and names that are in name_order
-            if per_seq_names != name_order:
-                good_indices = []
-                for n in name_order:
-                    good_indices.append(per_seq_names.index(n))
-                if hasattr(curr_seqs, "astype"):  # it's an array
-                    # much faster to check than to raise exception in this case
-                    curr_seqs = take(curr_seqs, good_indices, axis=0)
-                else:
-                    curr_seqs = [curr_seqs[i] for i in good_indices]
-
-            # create named_seqs dict for fast lookups
-            if not suppress_named_seqs:
-                self.named_seqs = self._make_named_seqs(self.names, curr_seqs)
-        # Sequence objects behave like sequences of chars, so no difference
-        # between seqs and seq_data. Note that this differs for Alignments,
-        # so be careful which you use if writing methods that should work for
-        # both SequenceCollections and Alignments.
-        self._set_additional_attributes(curr_seqs)
 
+        self._set_additional_attributes(seqs)
         self._repr_policy = dict(num_seqs=10, num_pos=60, ref_name="longest", wrap=60)
-
         self._annotation_db = None
 
-        if not isinstance(self, ArrayAlignment):
-            self.annotation_db = anno_db or DEFAULT_ANNOTATION_DB()
+    @property
+    def named_seqs(self):
+        if self._named_seqs is None:
+            self._named_seqs = _make_named_seqs(self.names, self._seqs)
+        return self._named_seqs
 
     @property
     def annotation_db(self):
@@ -586,13 +612,6 @@ def __str__(self):
 
         return FORMATTERS["fasta"](self.to_dict())
 
-    def _make_named_seqs(self, names, seqs):
-        """Returns named_seqs: dict of name:seq."""
-        name_seq_tuples = list(zip(names, seqs))
-        for n, s in name_seq_tuples:
-            s.name = n
-        return dict(name_seq_tuples)
-
     def _set_additional_attributes(self, curr_seqs):
         """Sets additional attributes based on current seqs: class-specific."""
         self.seq_data = curr_seqs
@@ -602,12 +621,6 @@ def _set_additional_attributes(self, curr_seqs):
         except ValueError:  # got empty sequence, for some reason?
             self.seq_len = 0
 
-    def _force_same_data(self, data, names):
-        """Forces dict that was passed in to be used as self.named_seqs"""
-        assert isinstance(data, dict), "force_same_data requires input data is a dict"
-        self.named_seqs = data
-        self.names = names or list(data.keys())
-
     def copy(self):
         """Returns deep copy of self."""
         result = self.__class__(self, moltype=self.moltype, info=self.info)
@@ -638,200 +651,11 @@ def deepcopy(self, sliced: bool = True):
             new_seqs[seq.name] = new_seq
 
         info = deepcopy(self.info)
-        result = self.__class__(
-            new_seqs, moltype=self.moltype, info=info, force_same_data=True
-        )
+        result = self.__class__(new_seqs, moltype=self.moltype, info=info)
         result.annotation_db = db
         result._repr_policy.update(self._repr_policy)
         return result
 
-    def _get_alphabet_and_moltype(self, alphabet, moltype, data):
-        """Returns alphabet and moltype, giving moltype precedence."""
-        if type(moltype) == str:
-            from cogent3.core.moltype import get_moltype
-
-            moltype = get_moltype(moltype)
-
-        if alphabet is None and moltype is None:
-            if hasattr(data, "moltype"):
-                moltype = data.moltype
-            elif hasattr(data, "alphabet"):
-                alphabet = data.alphabet
-            # check for containers
-            else:
-                curr_item = self._get_container_item(data)
-                if hasattr(curr_item, "moltype"):
-                    moltype = curr_item.moltype
-                elif hasattr(curr_item, "alphabet"):
-                    alphabet = curr_item.alphabet
-                else:
-                    moltype = self.moltype  # will be BYTES by default
-        if alphabet is not None and moltype is None:
-            moltype = alphabet.moltype
-        if moltype is not None and alphabet is None:
-            try:
-                alphabet = moltype.alphabets.degen_gapped
-            except AttributeError:
-                alphabet = moltype.alphabet
-        return alphabet, moltype
-
-    def _get_container_item(self, data):
-        """Checks container for item with alphabet or moltype"""
-        curr_item = None
-        if hasattr(data, "values"):
-            curr_item = next(iter(data.values()))
-        else:
-            try:
-                curr_item = next(iter(data))
-            except:
-                pass
-        return curr_item
-
-    def _strip_duplicates(self, names, seqs):
-        """Internal function to strip duplicates from list of names"""
-        if len(set(names)) == len(names):
-            return set(), names, seqs
-        # if we got here, there are duplicates
-        unique_names = {}
-        duplicates = {}
-        fixed_names = []
-        fixed_seqs = []
-        for n, s in zip(names, seqs):
-            if n in unique_names:
-                duplicates[n] = 1
-            else:
-                unique_names[n] = 1
-                fixed_names.append(n)
-                fixed_seqs.append(s)
-        if type(seqs) is ndarray:
-            fixed_seqs = array(fixed_seqs, seqs.dtype)
-        return duplicates, fixed_names, fixed_seqs
-
-    def _names_seqs_order(
-        self,
-        conversion_f,
-        data,
-        name_order,
-        is_array,
-        label_to_name,
-        remove_duplicate_names,
-        alphabet=None,
-    ):
-        """Internal function to figure out names, seqs, and name_order."""
-        # figure out conversion function and whether it's an array
-        if not conversion_f:
-            input_type = self._guess_input_type(data)
-            is_array = input_type in self.is_array
-            conversion_f = self._input_handlers[input_type]
-        # set seqs and names as properties
-        if alphabet:
-            seqs, names = conversion_f(data, alphabet=alphabet)
-        else:
-            seqs, names = conversion_f(data)
-        if names and label_to_name:
-            names = list(map(label_to_name, names))
-        curr_seqs = self._coerce_seqs(seqs, is_array)
-
-        # if no names were passed in as Names, if we obtained them from
-        # the seqs we should use them, but otherwise we should use the
-        # default names
-        if name_order is None:
-            if (names is None) or (None in names):
-                per_seq_names = name_order = self._make_names(len(curr_seqs))
-            else:  # got names from seqs
-                per_seq_names = name_order = names
-        else:
-            # otherwise, names were passed in as Names: use this as the order
-            # if we got names from the sequences, but otherwise assign the
-            # names to successive sequences in order
-            if (names is None) or (None in names):
-                per_seq_names = name_order
-            else:  # got names from seqs, so assume name_order is in Names
-                per_seq_names = names
-
-        # check for duplicate names
-        duplicates, fixed_names, fixed_seqs = self._strip_duplicates(
-            per_seq_names, curr_seqs
-        )
-        if duplicates:
-            if remove_duplicate_names:
-                per_seq_names, curr_seqs = fixed_names, fixed_seqs
-                # if name_order doesn't have the same names as per_seq_names,
-                # replace it with per_seq_names
-                if (set(name_order) != set(per_seq_names)) or (
-                    len(name_order) != len(per_seq_names)
-                ):
-                    name_order = per_seq_names
-            else:
-                raise ValueError(
-                    "Some names were not unique. Duplicates are:\n"
-                    + str(sorted(duplicates.keys()))
-                )
-
-        return per_seq_names, curr_seqs, name_order
-
-    def _coerce_seqs(self, seqs, is_array):
-        """Controls how seqs are coerced in _names_seqs_order.
-
-        Override in subclasses where this behavior should differ.
-        """
-        if is_array:
-            seqs = list(map(str, list(map(self.moltype.make_array_seq, seqs))))
-        return list(map(self.moltype.make_seq, seqs))
-
-    def _guess_input_type(self, data):
-        """Guesses input type of data; returns result as key of _input_handlers.
-
-        First checks whether data is an Alignment, then checks for some common
-        string formats, then tries to do it based on string or array properties.
-
-        Returns 'empty' if check fails, i.e. if it can't recognize the sequence
-        as a specific type. Note that bad sequences are not guaranteed to
-        return 'empty', and may be recognized as another type incorrectly.
-        """
-        from cogent3.core.sequence import Sequence
-
-        try:
-            length = len(data)
-        except TypeError:
-            length = 0
-
-        if length == 0:
-            return "empty"
-
-        if isinstance(data, ArrayAlignment):
-            return "array_aln"
-        if isinstance(data, Alignment):
-            return "aln"
-        if isinstance(data, SequenceCollection):
-            return "collection"
-        if isinstance(data, dict):
-            return "dict"
-        if isinstance(data, str):
-            if data.startswith(">"):
-                return "fasta"
-            else:
-                return "generic"
-
-        first = self._get_container_item(data)
-        if first is None:
-            return "empty"
-
-        if isinstance(first, ArraySequence):  # model sequence base type
-            return "array_seqs"
-        if hasattr(first, "dtype"):  # array object
-            return "array"
-        if isinstance(first, str) and first.startswith(">"):
-            return "fasta"
-        if isinstance(first, Aligned) or isinstance(first, Sequence):
-            return "generic"
-        try:
-            dict(data)
-            return "kv_pairs"
-        except (TypeError, ValueError):
-            pass
-        return "generic"
-
     def __eq__(self, other):
         """first tests as dict, then as str"""
         c = self.named_seqs == other
@@ -877,11 +701,11 @@ def iter_seqs(self, seq_order=None):
         for key in seq_order or self.names:
             yield get(key)
 
-    def _take_seqs(self):
+    @property
+    def seqs(self):
         return list(self.iter_seqs())
 
     # access as attribute if using default order.
-    seqs = property(_take_seqs)
 
     def take_seqs(self, seqs, negate=False, **kwargs):
         """Returns new Alignment containing only specified seqs.
@@ -1247,7 +1071,9 @@ def add_seqs(self, other, before_name=None, after_name=None):
                 seq.__class__ == self_seq_class
             ), f"Seq classes different: Expected {seq.__class__}, Got {self_seq_class}"
 
-        combined_aln = self.__class__(data=combined, info=self.info)
+        combined_aln = self.__class__(
+            data=combined, info=self.info, moltype=self.moltype
+        )
 
         if before_name is None and after_name is None:
             return combined_aln
@@ -1435,12 +1261,7 @@ def with_modified_termini(self):
             result.annotation_db = self.annotation_db
         return result
 
-    @c3warn.deprecated_args(
-        "2023.10", "replaced by strict", discontinued="allow_partial"
-    )
-    def has_terminal_stop(
-        self, gc: Any = None, strict: bool = False, allow_partial=False
-    ) -> bool:
+    def has_terminal_stop(self, gc: Any = None, strict: bool = False) -> bool:
         """Returns True if any sequence has a terminal stop codon.
 
         Parameters
@@ -1461,17 +1282,7 @@ def has_terminal_stop(
                 return True
         return False
 
-    @c3warn.deprecated_callable("2023.10", "better name", new="has_terminal_stop")
-    def has_terminal_stops(self, **kwargs) -> bool:
-        """deprecated"""
-        return self.has_terminal_stop(**kwargs)
-
-    @c3warn.deprecated_args(
-        "2023.10", "logically inconsistent", discontinued="allow_partial"
-    )
-    def trim_stop_codons(
-        self, gc: Any = None, strict: bool = False, allow_partial=False, **kwargs
-    ):
+    def trim_stop_codons(self, gc: Any = None, strict: bool = False, **kwargs):
         """Removes any terminal stop codons from the sequences
 
         Parameters
@@ -1621,8 +1432,9 @@ def get_motif_probs(
 
         only non-overlapping motifs are counted
         """
+        moltype = self.moltype
         if alphabet is None:
-            alphabet = self.moltype.alphabet
+            alphabet = moltype.alphabet
             if allow_gap:
                 alphabet = alphabet.gapped
 
@@ -1649,7 +1461,7 @@ def get_motif_probs(
                 probs[motif] = pseudocount
 
         for motif, count in list(counts.items()):
-            motif_set = alphabet.resolve_ambiguity(motif)
+            motif_set = moltype.resolve_ambiguity(motif, alphabet=alphabet)
             if len(motif_set) > 1:
                 if include_ambiguity:
                     count = float(count) / len(motif_set)
@@ -1691,9 +1503,15 @@ def omit_gap_runs(self, allowed_run=1):
 
     def to_moltype(self, moltype):
         """returns copy of self with moltype seqs"""
+        import cogent3
+
         if not moltype:
             raise ValueError(f"unknown moltype '{moltype}'")
 
+        moltype = cogent3.get_moltype(moltype)
+        if moltype is self.moltype:
+            return self
+
         data = [s.to_moltype(moltype) for s in self.seqs]
         result = self.__class__(
             data=data, moltype=moltype, name=self.name, info=self.info
@@ -1718,7 +1536,11 @@ def rc(self):
         """Returns the reverse complement alignment"""
         seqs = [self.named_seqs[name].rc() for name in self.names]
         rc = self.__class__(
-            data=seqs, names=self.names[:], name=self.name, info=self.info
+            data=seqs,
+            names=self.names[:],
+            name=self.name,
+            info=self.info,
+            moltype=self.moltype,
         )
         rc.annotation_db = self.annotation_db
         return rc
@@ -1860,6 +1682,7 @@ def dotplot(
             isinstance(self, AlignmentI),
             window=window,
             threshold=threshold,
+            k=k,
             min_gap=min_gap,
             xtitle=None if annotated else seq1.name,
             ytitle=None if annotated else seq2.name,
@@ -2073,6 +1896,14 @@ def entropy_per_seq(
 class SequenceCollection(_SequenceCollectionBase):
     """Container for unaligned sequences"""
 
+    moltype = None  # assigned to ASICII in moltype
+
+    def __init__(self, *args, **kwargs):
+        kwargs["conversion_f"] = kwargs.get("conversion_f", _coerce_to_unaligned_seqs)
+        super().__init__(*args, **kwargs)
+        anno_db = merged_db_collection(self.seqs)
+        self.annotation_db = anno_db or DEFAULT_ANNOTATION_DB()
+
     def copy_annotations(self, seq_db: SupportsFeatures) -> None:
         """copy annotations into attached annotation db
 
@@ -2325,11 +2156,10 @@ def annotate_matches_to(
             allow_multiple=allow_multiple,
         )
 
-    def _get_moltype(self):
+    @property
+    def moltype(self):
         return self.data.moltype
 
-    moltype = property(_get_moltype)
-
     def copy(self):
         """Returns a shallow copy of self"""
         return self.__class__(self.map, self.data)
@@ -2373,18 +2203,6 @@ def __repr__(self):
     def with_termini_unknown(self):
         return self.__class__(self.map.with_termini_unknown(), self.data)
 
-    @c3warn.deprecated_callable(
-        "2023.10", "handled by <collection>.annotate_from_gff()", is_discontinued=True
-    )
-    def annotate_from_gff(self, f):  # pragma: no cover
-        self.data.annotate_from_gff(f)
-
-    @c3warn.deprecated_callable(
-        "2023.10", "handled by <collection>.add_feature()", is_discontinued=True
-    )
-    def add_feature(self, *args, **kwargs):  # pragma: no cover
-        self.data.add_feature(*args, **kwargs)
-
     def __str__(self):
         """Returns string representation of aligned sequence, incl. gaps."""
         return str(self.get_gapped_seq())
@@ -2408,7 +2226,7 @@ def __iter__(self):
         """Iterates over sequence one motif (e.g. char) at a time, incl. gaps"""
         return self.data.gapped_by_map_motif_iter(self.map)
 
-    def get_gapped_seq(self, recode_gaps=False, moltype=None):
+    def get_gapped_seq(self, recode_gaps=False):
         """Returns sequence as an object, including gaps."""
         return self.data.gapped_by_map(self.map, recode_gaps)
 
@@ -2427,7 +2245,11 @@ def __add__(self, other):
         return Aligned(map, seq)
 
     def __getitem__(self, slice):
-        return Aligned(self.map[slice], self.data)
+        new_map = self.map[slice]
+        if new_map.reverse:
+            # A reverse slice means we should have an empty sequence
+            new_map = type(new_map)(locations=(), parent_length=len(self.data))
+        return Aligned(new_map, self.data)
 
     def rc(self):
         return Aligned(self.map.reversed(), self.data)
@@ -2465,6 +2287,15 @@ def to_json(self):
 
     def to_moltype(self, moltype):
         """returns copy of self with moltype seqs"""
+        import cogent3
+
+        if not moltype:
+            raise ValueError(f"unknown moltype '{moltype}'")
+
+        moltype = cogent3.get_moltype(moltype)
+        if moltype is self.moltype:
+            return self
+
         data = self.data.to_moltype(moltype)
         return self.__class__(map=self.map, data=data)
 
@@ -2476,22 +2307,6 @@ def make_feature(self, feature: FeatureDataType, alignment: "Alignment") -> Feat
         annot = self.data.make_feature(feature)
         return annot.remapped_to(alignment, self.map.inverse())
 
-    @c3warn.deprecated_callable(
-        "2023.10", "handled by <collection>.get_features()", is_discontinued=True
-    )
-    def get_annotations_matching(self, alignment, **kwargs):  # pragma: no cover
-        for feature in self.data.annotation_db.get_features(**kwargs):
-            yield self.make_feature(feature, alignment)
-
-    @c3warn.deprecated_callable(
-        "2023.10", "handled by <collection>.get_features()", is_discontinued=True
-    )
-    def get_features_matching(
-        self, alignment, annotation_type="*", **kwargs
-    ):  # pragma: no cover
-        for annot in self.data.get_features(biotype=annotation_type, **kwargs):
-            yield annot.remapped_to(alignment, self.map.inverse())
-
     def gap_vector(self):
         """Returns gap_vector of GappedSeq, for omit_gap_pos."""
         return self.get_gapped_seq().gap_vector()
@@ -2607,7 +2422,9 @@ def take_positions(self, cols, negate=False):
         else:
             for name, seq in list(self.named_seqs.items()):
                 result[name] = make_seq("".join(str(seq[i]) for i in cols))
-        return self.__class__(result, names=self.names, info=self.info)
+        return self.__class__(
+            result, names=self.names, info=self.info, moltype=self.moltype
+        )
 
     def get_position_indices(self, f, native=False, negate=False):
         """Returns list of column indices for which f(col) is True.
@@ -2629,10 +2446,7 @@ def new_f(x):
         else:
             new_f = f
 
-        if native and isinstance(self, ArrayAlignment):
-            result = [i for i in range(self.seq_len) if new_f(self.array_seqs[:, i])]
-        else:
-            result = [i for i, col in enumerate(self.positions) if new_f(col)]
+        result = [i for i, col in enumerate(self.positions) if new_f(col)]
 
         return result
 
@@ -2748,10 +2562,10 @@ def entropy_per_seq(
         exclude_unobserved
             if True, unobserved motif combinations are excluded.
 
-                Notes
-                -----
-                For motif_length > 1, it's advisable to specify exclude_unobserved=True,
-                this avoids unnecessary calculations.
+        Notes
+        -----
+        For motif_length > 1, it's advisable to specify exclude_unobserved=True,
+        this avoids unnecessary calculations.
         """
 
         probs = self.probs_per_seq(
@@ -2788,16 +2602,10 @@ def no_degenerates(self, motif_length=1, allow_gap=False):
             )
             raise ValueError(msg)
 
-        is_array = isinstance(self, ArrayAlignment)
-        alpha = self.alphabet
         if allow_gap:
             chars.extend(self.moltype.gap)
-            alpha = self.moltype.alphabet.gapped
 
-        if is_array:
-            chars = list(map(alpha.index, chars))
-
-        predicate = AllowedCharacters(chars, is_array=is_array)
+        predicate = AllowedCharacters(chars, is_array=False)
         return self.filtered(predicate, motif_length=motif_length)
 
     def omit_gap_pos(self, allowed_gap_frac=1 - eps, motif_length=1):
@@ -2816,18 +2624,11 @@ def omit_gap_pos(self, allowed_gap_frac=1 - eps, motif_length=1):
             is included in the counting. Default is 1.
 
         """
-        is_array = isinstance(self, ArrayAlignment)
-        try:
-            alpha = self.moltype.alphabets.degen_gapped
-        except:
-            alpha = self.moltype.alphabet
 
         gaps = list(self.moltype.gaps)
-        if is_array:
-            gaps = list(map(alpha.index, gaps))
 
         gaps_ok = GapsOk(
-            gaps, allowed_gap_frac, is_array=is_array, motif_length=motif_length
+            gaps, allowed_gap_frac, is_array=False, motif_length=motif_length
         )
         # if we're not deleting the 'naughty' seqs that contribute to the
         # gaps, it's easy...
@@ -2843,10 +2644,7 @@ def get_gap_array(self, include_ambiguity=True):
             if True, ambiguity characters that include the gap state are
             included
         """
-        if isinstance(self, ArrayAlignment):
-            data = self.array_seqs
-        else:
-            data = self.to_type(array_align=True).array_seqs
+        data = self.to_type(array_align=True).array_seqs
 
         gap = self.alphabet.index(self.moltype.gap)
         gapped = data == gap
@@ -3349,16 +3147,10 @@ def counts_per_seq(
         if alert and len(self) != length:
             warnings.warn(f"trimmed {len(self) - length}", UserWarning)
 
-        is_array = isinstance(self, ArrayAlignment)
         counts = []
         motifs = set()
         for name in self.names:
-            if is_array:
-                seq = self.moltype.make_array_seq(
-                    self.array_seqs[self.names.index(name)]
-                )
-            else:
-                seq = self.get_gapped_seq(name)
+            seq = self.get_gapped_seq(name)
             c = seq.counts(
                 motif_length=motif_length,
                 include_ambiguity=include_ambiguity,
@@ -3797,13 +3589,8 @@ def seqlogo(self, width=700, height=100, wrap=None, vspace=0.005, colours=None):
             width=width, height=height, wrap=wrap, vspace=vspace, colours=colours
         )
 
-    @c3warn.deprecated_args(
-        "2023.10", "logically inconsistent", discontinued="allow_partial"
-    )
     @extend_docstring_from(_SequenceCollectionBase.trim_stop_codons)
-    def trim_stop_codons(
-        self, gc: Any = None, strict: bool = False, allow_partial=False, **kwargs
-    ):
+    def trim_stop_codons(self, gc: Any = None, strict: bool = False, **kwargs):
         if not self.has_terminal_stop(gc=gc, strict=strict):
             return self
 
@@ -3862,7 +3649,10 @@ def _one_length(seqs):
         raise ValueError("not all sequences have same length")
 
 
-def aln_from_array(a, array_type=None, alphabet=None):
+@c3warn.deprecated_args(
+    "2024.3", reason="replaced by different loading mechanism", discontinued=True
+)
+def aln_from_array(a, array_type=None, alphabet=None):  # pragma: no cover
     """Alignment from array of pos x seq: no change, names are integers.
 
     This is an InputHandler for Alignment. It converts an arbitrary array
@@ -3877,7 +3667,10 @@ def aln_from_array(a, array_type=None, alphabet=None):
     return transpose(result), None
 
 
-def aln_from_array_seqs(seqs, array_type=None, alphabet=None):
+@c3warn.deprecated_args(
+    "2024.3", reason="replaced by different loading mechanism", discontinued=True
+)
+def aln_from_array_seqs(seqs, array_type=None, alphabet=None):  # pragma: no cover
     """Alignment from ArraySequence objects: seqs -> array, names from seqs.
 
     This is an InputHandler for Alignment. It converts a list of Sequence
@@ -3905,7 +3698,10 @@ def aln_from_array_seqs(seqs, array_type=None, alphabet=None):
     return result, names
 
 
-def aln_from_generic(data, array_type=None, alphabet=None):
+@c3warn.deprecated_args(
+    "2024.3", reason="replaced by different loading mechanism", discontinued=True
+)
+def aln_from_generic(data, array_type=None, alphabet=None):  # pragma: no cover
     """Alignment from generic seq x pos data: sequence of sequences of chars.
 
     This is an InputHandler for Alignment. It converts a generic list (each
@@ -3927,7 +3723,10 @@ def aln_from_generic(data, array_type=None, alphabet=None):
     return result, names
 
 
-def aln_from_collection(seqs, array_type=None, alphabet=None):
+@c3warn.deprecated_args(
+    "2024.3", reason="replaced by different loading mechanism", discontinued=True
+)
+def aln_from_collection(seqs, array_type=None, alphabet=None):  # pragma: no cover
     """Alignment from SequenceCollection object, or its subclasses."""
     names = seqs.names
     data = [seqs.named_seqs[i] for i in names]
@@ -3937,7 +3736,10 @@ def aln_from_collection(seqs, array_type=None, alphabet=None):
     return result, names
 
 
-def aln_from_fasta(seqs, array_type=None, alphabet=None):
+@c3warn.deprecated_args(
+    "2024.3", reason="replaced by different loading mechanism", discontinued=True
+)
+def aln_from_fasta(seqs, array_type=None, alphabet=None):  # pragma: no cover
     """Alignment from FASTA-format string or lines.
 
     This is an InputHandler for Alignment. It converts a FASTA-format string
@@ -3959,7 +3761,10 @@ def aln_from_fasta(seqs, array_type=None, alphabet=None):
     )
 
 
-def aln_from_dict(aln, array_type=None, alphabet=None):
+@c3warn.deprecated_args(
+    "2024.3", reason="replaced by different loading mechanism", discontinued=True
+)
+def aln_from_dict(aln, array_type=None, alphabet=None):  # pragma: no cover
     """Alignment from dict of {label:seq_as_str}.
 
     This is an InputHandler for Alignment. It converts a dict in which the
@@ -3979,7 +3784,10 @@ def aln_from_dict(aln, array_type=None, alphabet=None):
     return result, list(names)
 
 
-def aln_from_kv_pairs(aln, array_type=None, alphabet=None):
+@c3warn.deprecated_args(
+    "2024.3", reason="replaced by different loading mechanism", discontinued=True
+)
+def aln_from_kv_pairs(aln, array_type=None, alphabet=None):  # pragma: no cover
     """Alignment from sequence of (key, value) pairs.
 
     This is an InputHandler for Alignment. It converts a list in which the
@@ -3994,7 +3802,10 @@ def aln_from_kv_pairs(aln, array_type=None, alphabet=None):
     return result, list(names)
 
 
-def aln_from_array_aln(aln, array_type=None, alphabet=None):
+@c3warn.deprecated_args(
+    "2024.3", reason="replaced by different loading mechanism", discontinued=True
+)
+def aln_from_array_aln(aln, array_type=None, alphabet=None):  # pragma: no cover
     """Alignment from existing ArrayAlignment object: copies data.
 
     Retrieves data from positions field. Uses copy(), so array data type
@@ -4007,7 +3818,10 @@ def aln_from_array_aln(aln, array_type=None, alphabet=None):
     return transpose(result), aln.names[:]
 
 
-def aln_from_empty(obj, *args, **kwargs):
+@c3warn.deprecated_args(
+    "2024.3", reason="replaced by different loading mechanism", discontinued=True
+)
+def aln_from_empty(obj, *args, **kwargs):  # pragma: no cover
     """Alignment from empty data: raise exception."""
     raise ValueError("Cannot create empty alignment.")
 
@@ -4058,10 +3872,6 @@ class ArrayAlignment(AlignmentI, _SequenceCollectionBase):
 
     SequenceType: Constructor to use when building sequences. Default: Sequence
 
-    _input_handlers: dict of {input_type:input_handler} where input_handler is
-    from the _input_handlers above and input_type is a result of the method
-    self._guess_input_type (should always be a string).
-
     Creating a new array will always result in a new object unless you use
     the force_same_object=True parameter.
 
@@ -4078,35 +3888,21 @@ class ArrayAlignment(AlignmentI, _SequenceCollectionBase):
     moltype = None  # will be set to BYTES on moltype import
     alphabet = None  # will be set to BYTES.alphabet on moltype import
 
-    _input_handlers = {
-        "array": aln_from_array,
-        "array_seqs": aln_from_array_seqs,
-        "generic": aln_from_generic,
-        "fasta": aln_from_fasta,
-        "array_aln": aln_from_array_aln,
-        "aln": aln_from_collection,
-        "collection": aln_from_collection,
-        "dict": aln_from_dict,
-        "kv_pairs": aln_from_kv_pairs,
-        "empty": aln_from_empty,
-    }
-
     def __init__(self, *args, **kwargs):
         """Returns new ArrayAlignment object. Inherits from SequenceCollection."""
-        kwargs["suppress_named_seqs"] = True
+        kwargs["conversion_f"] = _coerce_to_array_aligned_seqs
         super(ArrayAlignment, self).__init__(*args, **kwargs)
         self.array_positions = transpose(self.seq_data.astype(self.alphabet.array_type))
         self.array_seqs = transpose(self.array_positions)
         self.seq_data = self.array_seqs
         self.seq_len = len(self.array_positions)
 
-    def _force_same_data(self, data, names):
-        """Forces array that was passed in to be used as selfarray_positions"""
-        if isinstance(data, ArrayAlignment):
-            data = data._positions
-
-        self.array_positions = data
-        self.names = names or self._make_names(len(data[0]))
+    def _set_additional_attributes(self, curr_seqs):
+        """Sets additional attributes based on current seqs: class-specific."""
+        curr_seqs = numpy.array(curr_seqs)
+        self.seq_data = curr_seqs
+        self._seqs = curr_seqs
+        self.seq_len = curr_seqs.shape[1] if len(curr_seqs) else 0
 
     def _get_positions(self):
         """Override superclass positions to return positions as symbols."""
@@ -4114,16 +3910,15 @@ def _get_positions(self):
 
     positions = property(_get_positions)
 
-    def _get_named_seqs(self):
-        if not hasattr(self, "_named_seqs"):
+    @property
+    def named_seqs(self):
+        if self._named_seqs is None:
             seqs = list(map(self.alphabet.to_string, self.array_seqs))
             if self.moltype:
                 seqs = [self.moltype.make_seq(s, preserve_case=True) for s in seqs]
-            self._named_seqs = self._make_named_seqs(self.names, seqs)
+            self._named_seqs = _make_named_seqs(self.names, seqs)
         return self._named_seqs
 
-    named_seqs = property(_get_named_seqs)
-
     def __iter__(self):
         """iter(aln) iterates over positions, returning array slices.
 
@@ -4143,34 +3938,38 @@ def __getitem__(self, item):
         else:
             data = self.array_seqs[:, item]
         result = self.__class__(
-            data.T,
+            data,
             list(map(str, self.names)),
-            self.alphabet,
-            conversion_f=aln_from_array,
             info=self.info,
+            moltype=self.moltype,
         )
         result._repr_policy.update(self._repr_policy)
         return result
 
-    def _coerce_seqs(self, seqs, is_array):
+    def _coerce_seqs(self, seqs):
         """Controls how seqs are coerced in _names_seqs_order.
 
         Override in subclasses where this behavior should differ.
         """
         return seqs
 
+    @c3warn.deprecated_args(
+        "2024.3",
+        reason="naming consistency",
+        old_new=(("invert_seqs", "negate_seqs"), ("invert_pos", "negate_pos")),
+    )
     def get_sub_alignment(
-        self, seqs=None, pos=None, invert_seqs=False, invert_pos=False
+        self, seqs=None, pos=None, negate_seqs=False, negate_pos=False
     ):
         """Returns subalignment of specified sequences and positions.
 
         seqs and pos can be passed in as lists of sequence indices to keep
         or positions to keep.
 
-        invert_seqs: if True (default False), gets everything _except_ the
+        negate_seqs: if True (default False), gets everything _except_ the
         specified sequences.
 
-        invert_pos: if True (default False), gets everything _except_ the
+        negate_pos: if True (default False), gets everything _except_ the
         specified positions.
 
         Unlike most of the other code that gets things out of an alignment,
@@ -4179,7 +3978,7 @@ def get_sub_alignment(
         """
         # figure out which positions to keep, and keep them
         if pos is not None:
-            if invert_pos:
+            if negate_pos:
                 pos_mask = ones(len(self.array_positions))
                 put(pos_mask, pos, 0)
                 pos = nonzero(pos_mask)[0]
@@ -4188,7 +3987,7 @@ def get_sub_alignment(
             data = self.array_positions
         # figure out which sequences to keep, and keep them
         if seqs is not None:
-            if invert_seqs:
+            if negate_seqs:
                 seq_mask = ones(len(self.array_seqs))
                 put(seq_mask, seqs, 0)
                 seqs = nonzero(seq_mask)[0]
@@ -4197,11 +3996,10 @@ def get_sub_alignment(
         else:
             names = self.names
         return self.__class__(
-            data,
+            data.T,
             list(map(str, names)),
-            self.alphabet,
-            conversion_f=aln_from_array,
             info=self.info,
+            moltype=self.moltype,
         )
 
     def __str__(self):
@@ -4295,7 +4093,6 @@ def sample(
         return self.__class__(
             positions.T,
             moltype=self.moltype,
-            force_same_data=True,
             info=self.info,
             names=self.names,
         )
@@ -4342,24 +4139,22 @@ def filtered(self, predicate, motif_length=1, drop_remainder=True, **kwargs):
         positions = self.array_seqs.take(indices, axis=1)
         return self.__class__(
             positions,
-            force_same_data=True,
             moltype=self.moltype,
             info=self.info,
             names=self.names,
         )
 
-    def get_gapped_seq(self, seq_name, recode_gaps=False, moltype=None):
+    def get_gapped_seq(self, seq_name, recode_gaps=False):
         """Return a gapped Sequence object for the specified seqname.
 
         Note: return type may depend on what data was loaded into the
         SequenceCollection or Alignment.
         """
         s = self.named_seqs[seq_name]
-        moltype = self.moltype if moltype is None else moltype
         if recode_gaps:
-            non_ambig = list(moltype)
-            ambig = moltype.degenerate_from_seq(non_ambig)
-            for gapchar in moltype.gaps:
+            non_ambig = list(self.moltype)
+            ambig = self.moltype.degenerate_from_seq(non_ambig)
+            for gapchar in self.moltype.gaps:
                 s = s.replace(gapchar, ambig)
         return s
 
@@ -4460,7 +4255,7 @@ def replace_seqs(self, seqs, aa_to_codon=True):
         If seqs is an alignment, any gaps in it will be ignored.
         """
         if not hasattr(seqs, "named_seqs"):
-            seqs = SequenceCollection(seqs)
+            seqs = SequenceCollection(seqs, moltype=self.moltype)
 
         seqs = seqs.degap()
 
@@ -4501,7 +4296,7 @@ def replace_seqs(self, seqs, aa_to_codon=True):
             new_seqarr[seqindex] = new
 
         return self.__class__(
-            new_seqarr.T, names=self.names, moltype=seqs.moltype, info=self.info
+            new_seqarr, names=self.names, moltype=seqs.moltype, info=self.info
         )
 
     def to_moltype(self, moltype):
@@ -4581,7 +4376,6 @@ def deepcopy(self, sliced=True):
         positions = deepcopy(self.array_seqs)
         result = self.__class__(
             positions,
-            force_same_data=True,
             moltype=self.moltype,
             info=info,
             names=self.names,
@@ -4589,20 +4383,182 @@ def deepcopy(self, sliced=True):
         result._repr_policy.update(self._repr_policy)
         return result
 
+    def no_degenerates(self, motif_length=1, allow_gap=False):
+        """returns new alignment without degenerate characters
+
+        Parameters
+        ----------
+        motif_length
+            sequences are segmented into units of this size
+        allow_gaps
+            whether gaps are to be treated as a degenerate
+            character (default, most evolutionary modelling treats gaps as
+            N) or not.
+
+        """
+        try:
+            chars = list(self.moltype.alphabet.non_degen)
+        except AttributeError:
+            msg = (
+                "Invalid MolType (no degenerate characters), "
+                "create the alignment using DNA, RNA or PROTEIN"
+            )
+            raise ValueError(msg)
+
+        alpha = self.alphabet
+        if allow_gap:
+            chars.extend(self.moltype.gap)
+            alpha = self.moltype.alphabet.gapped
+
+        chars = list(map(alpha.index, chars))
+
+        predicate = AllowedCharacters(chars, is_array=True)
+        return self.filtered(predicate, motif_length=motif_length)
+
+    def counts_per_seq(
+        self,
+        motif_length=1,
+        include_ambiguity=False,
+        allow_gap=False,
+        exclude_unobserved=False,
+        alert=False,
+    ):
+        """counts of non-overlapping motifs per sequence
+
+        Parameters
+        ----------
+        motif_length
+            number of elements per character.
+        include_ambiguity
+            if True, motifs containing ambiguous characters
+            from the seq moltype are included. No expansion of those is attempted.
+        allow_gaps
+            if True, motifs containing a gap character are included.
+        exclude_unobserved
+            if False, all canonical states included
+        alert
+            warns if motif_length > 1 and alignment trimmed to produce
+            motif columns
+
+        Returns
+        -------
+        MotifCountsArray
+        """
+        length = (len(self) // motif_length) * motif_length
+        if alert and len(self) != length:
+            warnings.warn(f"trimmed {len(self) - length}", UserWarning)
+
+        counts = []
+        motifs = set()
+        for name in self.names:
+            seq = self.moltype.make_array_seq(self.array_seqs[self.names.index(name)])
+            c = seq.counts(
+                motif_length=motif_length,
+                include_ambiguity=include_ambiguity,
+                allow_gap=allow_gap,
+                exclude_unobserved=exclude_unobserved,
+            )
+            motifs.update(c.keys())
+            counts.append(c)
+
+        if not exclude_unobserved:
+            motifs.update(self.moltype.alphabet.get_word_alphabet(motif_length))
+
+        motifs = list(sorted(motifs))
+        if not motifs:
+            return None
+
+        for i, c in enumerate(counts):
+            counts[i] = c.tolist(motifs)
+        return MotifCountsArray(counts, motifs, row_indices=self.names)
+
+    def omit_gap_pos(self, allowed_gap_frac=1 - eps, motif_length=1):
+        """Returns new alignment where all cols (motifs) have <= allowed_gap_frac gaps.
+
+        Parameters
+        ----------
+        allowed_gap_frac
+            specifies proportion of gaps is allowed in each
+            column (default is just < 1, i.e. only cols with at least one gap
+            character are preserved). Set to 1 - e-6 to exclude strictly gapped
+            columns.
+        motif_length
+            set's the "column" width, e.g. setting to 3
+            corresponds to codons. A motif that includes a gap at any position
+            is included in the counting. Default is 1.
+
+        """
+        try:
+            alpha = self.moltype.alphabets.degen_gapped
+        except:
+            alpha = self.moltype.alphabet
+
+        gaps = list(map(alpha.index, list(self.moltype.gaps)))
+
+        gaps_ok = GapsOk(
+            gaps, allowed_gap_frac, is_array=True, motif_length=motif_length
+        )
+        # if we're not deleting the 'naughty' seqs that contribute to the
+        # gaps, it's easy...
+        result = self.filtered(gaps_ok, motif_length=motif_length)
+        return result
+
+    def get_position_indices(self, f, native=False, negate=False):
+        """Returns list of column indices for which f(col) is True.
 
-class CodonArrayAlignment(ArrayAlignment):
+        f : callable
+          function that returns true/false given an alignment position
+        native : boolean
+          if True, and ArrayAlignment, f is provided with slice of array
+          otherwise the string is used
+        negate : boolean
+          if True, not f() is used
+        """
+        # negate f if necessary
+        if negate:
+
+            def new_f(x):
+                return not f(x)
+
+        else:
+            new_f = f
+
+        if native:
+            result = [i for i in range(self.seq_len) if new_f(self.array_seqs[:, i])]
+        else:
+            result = [i for i, col in enumerate(self.positions) if new_f(col)]
+
+        return result
+
+    def get_gap_array(self, include_ambiguity=True):
+        """returns bool array with gap state True, False otherwise
+
+        Parameters
+        ----------
+        include_ambiguity : bool
+            if True, ambiguity characters that include the gap state are
+            included
+        """
+        data = self.array_seqs
+
+        gap = self.alphabet.index(self.moltype.gap)
+        gapped = data == gap
+        if include_ambiguity:
+            gaps = list(map(self.alphabet.index, self.moltype.gaps))
+            gaps.remove(gap)
+            for gap in gaps:
+                gapped = logical_or(gapped, data == gap)
+
+        return gapped
+
+
+@c3warn.deprecated_callable(
+    "2024.3", reason="not being used", is_discontinued=True, stack_level=3
+)
+class CodonArrayAlignment(ArrayAlignment):  # pragma: no cover
     """Stores alignment of gapped codons, no degenerate symbols."""
 
-    _input_handlers = {
-        "array": aln_from_array,
-        "seqs": aln_from_array_seqs,
-        "generic": aln_from_generic,
-        "array_aln": aln_from_array_aln,
-        "aln": aln_from_collection,
-        "collection": aln_from_collection,
-        "dict": aln_from_dict,
-        "empty": aln_from_empty,
-    }
+    ...
 
 
 def make_gap_filter(template, gap_fraction, gap_run):
@@ -4646,34 +4602,11 @@ class Alignment(AlignmentI, SequenceCollection):
 
     def __init__(self, *args, **kwargs):
         """Returns new Alignment object: see SequenceCollection."""
-
+        kwargs["conversion_f"] = _coerce_to_aligned_seqs
         SequenceCollection.__init__(self, *args, **kwargs)
 
-        # need to convert seqs to Aligned objects
-        seqs = self.seq_data
-        names = self.names
-
-        self._motif_probs = {}
-        lengths = list(map(len, self.seq_data))
-        if lengths and (max(lengths) != min(lengths)):
-            raise DataError(
-                "Not all sequences are the same length:\n"
-                + f"max is {max(lengths)}, min is {min(lengths)}"
-            )
-        aligned_seqs = []
-        for s, n in zip(seqs, names):
-            if isinstance(s, Aligned):
-                s.name = n  # ensure consistency
-                aligned_seqs.append(s)
-            else:
-                aligned_seqs.append(self._seq_to_aligned(s, n))
-        self.named_seqs = dict(list(zip(names, aligned_seqs)))
-        self.seq_data = self._seqs = aligned_seqs
-
-    def _coerce_seqs(self, seqs, is_array):
-        if any(isinstance(seq, ArraySequence) for seq in seqs):
-            seqs = [self.moltype.make_seq(str(seq), name=seq.name) for seq in seqs]
-        elif not any(isinstance(seq, (Sequence, Aligned)) for seq in seqs):
+    def _coerce_seqs(self, seqs):
+        if not any(isinstance(seq, (Sequence, Aligned)) for seq in seqs):
             seqs = list(map(self.moltype.make_seq, seqs))
         return seqs
 
@@ -4687,6 +4620,9 @@ def _seq_to_aligned(self, seq, key):
         return Aligned(map, seq)
 
     def __getitem__(self, index):
+        # todo revisit this design Map's should probably not be used for
+        #  slicing, perhaps they could be used to create a slice object
+        #  instead?
         if hasattr(index, "get_slice"):
             if index.parent is not self:
                 raise ValueError(f"feature.parent {index.seqid!r} is not self")
@@ -4698,7 +4634,9 @@ def __getitem__(self, index):
         elif isinstance(index, (int, slice)):
             seqs = [s[index] for s in self.seqs]
 
-            new = self.__class__(seqs, name=self.name, info=self.info)
+            new = self.__class__(
+                seqs, name=self.name, info=self.info, moltype=self.moltype
+            )
 
         if isinstance(index, (list, tuple)):
             raise TypeError(f"cannot slice using {type(index)}")
@@ -4828,7 +4766,9 @@ def with_masked_annotations(self, biotypes, mask_char=None, shadow=False):
             # todo gah why is _masked_annotations semi-private? add public method
             # on Aligned
             masked_seqs += [seq._masked_annotations(biotypes, mask_char, shadow)]
-        new = self.__class__(data=masked_seqs, info=self.info, name=self.name)
+        new = self.__class__(
+            data=masked_seqs, info=self.info, name=self.name, moltype=self.moltype
+        )
         new.annotation_db = self.annotation_db
         return new
 
@@ -4869,14 +4809,15 @@ def get_seq(self, seqname):
 
         Note: always returns Sequence object, not ArraySequence.
         """
-        return self.named_seqs[seqname].data
+        seq = self.named_seqs[seqname]
+        return seq.data[seq.map.without_gaps()]
 
-    def get_gapped_seq(self, seq_name, recode_gaps=False, moltype=None):
+    def get_gapped_seq(self, seq_name, recode_gaps=False):
         """Return a gapped Sequence object for the specified seqname.
 
         Note: always returns Sequence object, not ArraySequence.
         """
-        return self.named_seqs[seq_name].get_gapped_seq(recode_gaps, moltype=moltype)
+        return self.named_seqs[seq_name].get_gapped_seq(recode_gaps)
 
     def iter_positions(self, pos_order=None):
         """Iterates over positions in the alignment, in order.
@@ -4924,7 +4865,7 @@ def with_gaps_from(self, template):
                 else:
                     combo.append(str(s))
             result[name] = combo
-        return Alignment(result, alphabet=self.alphabet.with_gap_motif())
+        return Alignment(result, moltype=self.moltype)
 
     def get_degapped_relative_to(self, name):
         """Remove all columns with gaps in sequence with given name.
@@ -4998,7 +4939,7 @@ def add_from_ref_aln(self, ref_aln, before_name=None, after_name=None):
         """
 
         if type(ref_aln) != type(self):  # let the seq class try and guess
-            ref_aln = self.__class__(ref_aln)
+            ref_aln = self.__class__(ref_aln, moltype=ref_aln.moltype)
 
         ref_seq_name = ref_aln.names[0]
 
@@ -5025,10 +4966,12 @@ def add_from_ref_aln(self, ref_aln, before_name=None, after_name=None):
             seq = ref_aln.get_gapped_seq(seq_name)
             new_seq = Aligned(self.named_seqs[ref_seq_name].map, seq)
             if not temp_aln:
-                temp_aln = self.__class__({new_seq.name: str(new_seq)})
+                temp_aln = self.__class__(
+                    {new_seq.name: str(new_seq)}, moltype=self.moltype
+                )
             else:
                 temp_aln = temp_aln.add_seqs(
-                    self.__class__({new_seq.name: str(new_seq)})
+                    self.__class__({new_seq.name: str(new_seq)}, moltype=self.moltype)
                 )
 
         return self.add_seqs(temp_aln, before_name, after_name)
@@ -5048,11 +4991,6 @@ def replace_seqs(self, seqs, aa_to_codon=True):
 
         If seqs is an alignment, any gaps in it will be ignored.
         """
-        if isinstance(self, ArrayAlignment):
-            new = self.to_type(array_align=False)
-            result = new.replace_seqs(seqs, aa_to_codon=aa_to_codon)
-            return result.to_type(array_align=True)
-
         if aa_to_codon:
             scale = 3
         else:
@@ -5076,7 +5014,7 @@ def replace_seqs(self, seqs, aa_to_codon=True):
 
             new_seqs.append((label, Aligned(aligned.map * scale, seq)))
 
-        return self.__class__(new_seqs, info=self.info)
+        return self.__class__(new_seqs, info=self.info, moltype=self.moltype)
 
     def get_drawables(self, *, biotype: Optional[str, Iterable[str]] = None) -> dict:
         """returns a dict of drawables, keyed by type
@@ -5393,3 +5331,390 @@ def get_features(
             # and if i've been reversed...?
             feature["reversed"] = seq_map.reverse
             yield self.make_feature(feature=feature, on_alignment=on_al)
+
+
+def _make_name_seq_data(data):
+    # for sequence objects with a .name attribute
+    result = []
+    sequential = assign_sequential_names(None, num_seqs=len(data))
+    for i, record in enumerate(data):
+        record.name = record.name or sequential[i]
+        result.append((record.name, record))
+    return result
+
+
+# prep for SequenceCollection
+O = typing.Tuple[typing.List[Sequence], typing.List[str]]
+
+
+@functools.singledispatch
+def _coerce_to_unaligned_seqs(data, names, label_to_name=str, moltype=None) -> O:
+    """coerces data to be Sequence objects with moltype
+
+    Returns
+    -------
+    Lists of names and seqs
+
+    Notes
+    -----
+    Assuming data is a series of [name, seq] pairs, or a dict
+    {name: seq}
+    """
+    if isinstance(data, (typing.Generator, typing.Iterator)):
+        data = list(data)
+
+    first = get_first_value(data)
+    if hasattr(first, "name"):
+        data = _make_name_seq_data(data)
+
+    if isinstance(first, (str, ndarray)):
+        data = list(zip(names or assign_sequential_names(None, len(data)), data))
+
+    if moltype is None:
+        val = get_first_value(data)
+        moltype = getattr(val, "moltype", "text")
+
+    _check_name_consistency(data, names)
+
+    data = {label_to_name(name): seq for name, seq in data}
+    names = names or list(data.keys())
+    seqs = []
+    for name in names:
+        seq = _construct_unaligned_seq(data[name], moltype=moltype)
+        seq.name = name
+        seqs.append(seq)
+    return seqs, names
+
+
+def _check_name_consistency(data, names):
+    data_names = {n for n, _ in data}
+    if names and set(names) != data_names:
+        raise ValueError(
+            f"provided names do not match those in data: {set(names) - data_names}"
+        )
+    elif len(data) != len(data_names):
+        raise ValueError("duplicate names")
+
+
+@_coerce_to_unaligned_seqs.register
+def _(data: dict, names, label_to_name=str, moltype=None) -> O:
+    return _coerce_to_unaligned_seqs(
+        data.items(), names, label_to_name, moltype=moltype
+    )
+
+
+def _coerce_from_containers(data):
+    return [(s.name, s) for s in data.seqs]
+
+
+@_coerce_to_unaligned_seqs.register
+def _(
+    data: Alignment,
+    names,
+    label_to_name=str,
+    moltype=None,
+) -> O:
+    return _coerce_to_unaligned_seqs(
+        _coerce_from_containers(data), names, label_to_name, moltype
+    )
+
+
+@_coerce_to_unaligned_seqs.register
+def _(
+    data: SequenceCollection,
+    names,
+    label_to_name=str,
+    moltype=None,
+) -> O:
+    return _coerce_to_unaligned_seqs(
+        _coerce_from_containers(data), names, label_to_name, moltype
+    )
+
+
+@_coerce_to_unaligned_seqs.register
+def _(
+    data: ArrayAlignment,
+    names,
+    label_to_name=str,
+    moltype=None,
+) -> O:
+    return _coerce_to_unaligned_seqs(
+        _coerce_from_containers(data), names, label_to_name, moltype
+    )
+
+
+# prep data for ArrayAlignment
+O = typing.Tuple[ndarray, typing.List[str]]
+
+
+@functools.singledispatch
+def _coerce_to_array_aligned_seqs(data, names, label_to_name=str, moltype=None) -> O:
+    """coerces aligned data to be A numpy array objects with moltype
+
+    Returns
+    -------
+    Lists of names and seqs
+
+    Notes
+    -----
+    Assuming data is a series of [name, seq] pairs, or a dict
+    {name: seq}
+    """
+    if isinstance(data, (typing.Generator, typing.Iterator)):
+        data = list(data)
+
+    first = get_first_value(data)
+    if hasattr(first, "name"):
+        data = _make_name_seq_data(data)
+
+    if isinstance(first, (str, ndarray)):
+        data = list(zip(names or assign_sequential_names(None, len(data)), data))
+
+    if moltype is None:
+        val = get_first_value(data)
+        moltype = getattr(val, "moltype", "bytes")
+
+    _check_name_consistency(data, names)
+
+    data = {label_to_name(name): seq for name, seq in data}
+    names = names or list(data.keys())
+    seqs = []
+    for name in names:
+        seq = _construct_array_aligned_seq(data[name], moltype=moltype)
+        seqs.append(seq)
+
+    return numpy.array(seqs), names
+
+
+@_coerce_to_array_aligned_seqs.register
+def _(data: dict, names, label_to_name, moltype) -> O:
+    return _coerce_to_array_aligned_seqs(data.items(), names, label_to_name, moltype)
+
+
+@_coerce_to_array_aligned_seqs.register
+def _(data: ndarray, names, label_to_name, moltype) -> O:
+    if names is None:
+        names = assign_sequential_names(None, data.shape[0])
+    return _coerce_to_array_aligned_seqs(
+        tuple(zip(names, data)), names, label_to_name, moltype
+    )
+
+
+@_coerce_to_array_aligned_seqs.register
+def _(
+    data: ArrayAlignment,
+    names,
+    label_to_name,
+    moltype,
+) -> O:
+    return _coerce_to_array_aligned_seqs(
+        _coerce_from_containers(data), names, label_to_name, moltype
+    )
+
+
+@_coerce_to_array_aligned_seqs.register
+def _(
+    data: SequenceCollection,
+    names,
+    label_to_name,
+    moltype,
+) -> O:
+    return _coerce_to_array_aligned_seqs(
+        _coerce_from_containers(data), names, label_to_name, moltype
+    )
+
+
+# prep data for Alignment
+O = typing.Tuple[typing.List[Aligned], typing.List[str]]
+
+
+@functools.singledispatch
+def _coerce_to_aligned_seqs(data, names, label_to_name=str, moltype=None) -> O:
+    """coerces data to be Aligned objects with moltype
+
+    Returns
+    -------
+    Lists of names and seqs
+
+    Notes
+    -----
+    Assuming data is a series of [name, seq] pairs, or a dict
+    {name: seq}
+    """
+    if isinstance(data, (typing.Generator, typing.Iterator)):
+        data = list(data)
+
+    first = get_first_value(data)
+    if hasattr(first, "name"):
+        data = _make_name_seq_data(data)
+
+    if isinstance(first, (str, ndarray)):
+        data = list(zip(names or assign_sequential_names(None, len(data)), data))
+
+    if moltype is None:
+        val = get_first_value(data)
+        moltype = getattr(val, "moltype", "text")
+
+    _check_name_consistency(data, names)
+
+    data = {label_to_name(name): seq for name, seq in data}
+    names = names or list(data.keys())
+    seqs = []
+    for name in names:
+        seq = _construct_aligned_seq(data[name], moltype=moltype)
+        seq.name = seq.data.name = name
+        seqs.append(seq)
+
+    _one_length(seqs)
+    return seqs, names
+
+
+@_coerce_to_aligned_seqs.register
+def _(data: dict, names, label_to_name, moltype) -> O:
+    return _coerce_to_aligned_seqs(data.items(), names, label_to_name, moltype)
+
+
+@_coerce_to_aligned_seqs.register
+def _(data: ndarray, names, label_to_name, moltype) -> O:
+    names = names or assign_sequential_names(None, data.shape[0])
+    seqs = ["".join(moltype.alphabet.to_string(d)) for d in data]
+    return _coerce_to_aligned_seqs(
+        tuple(zip(names, seqs)), names, label_to_name, moltype
+    )
+
+
+@_coerce_to_aligned_seqs.register
+def _(
+    data: ArrayAlignment,
+    names,
+    label_to_name,
+    moltype,
+) -> O:
+    return _coerce_to_aligned_seqs(
+        _coerce_from_containers(data), names, label_to_name, moltype
+    )
+
+
+@_coerce_to_aligned_seqs.register
+def _(
+    data: SequenceCollection,
+    names,
+    label_to_name,
+    moltype,
+) -> O:
+    return _coerce_to_aligned_seqs(
+        _coerce_from_containers(data), names, label_to_name, moltype
+    )
+
+
+def _make_named_seqs(
+    names: typing.Sequence[str], seqs: typing.Sequence[Sequence]
+) -> dict[str, Sequence]:
+    """make a {name:seq, ...} where seq.name == name"""
+    name_seq_tuples = tuple(zip(names, seqs))
+    for n, s in name_seq_tuples:
+        s.name = n
+    return dict(name_seq_tuples)
+
+
+T = typing.Sequence[typing.Union[Sequence, ndarray]]
+
+
+# sequence conversion functions
+
+
+@functools.singledispatch
+def _construct_unaligned_seq(data, moltype) -> Sequence:
+    try:
+        result = data.to_moltype(moltype) if moltype else data
+    except AttributeError:
+        result = moltype.make_seq(data, preserve_case=False)
+    return result
+
+
+@_construct_unaligned_seq.register
+def _(data: str, moltype) -> Sequence:
+    return moltype.make_seq(data, preserve_case=False)
+
+
+@_construct_unaligned_seq.register
+def _(data: bytes, moltype) -> Sequence:
+    return _construct_unaligned_seq(data.decode("utf8"), moltype)
+
+
+@_construct_unaligned_seq.register
+def _(data: Aligned, moltype) -> Sequence:
+    return data.get_gapped_seq().to_moltype(moltype)
+
+
+@_construct_unaligned_seq.register
+def _(data: ndarray, moltype) -> Sequence:
+    return moltype.make_array_seq(data)
+
+
+@functools.singledispatch
+def _construct_aligned_seq(data, moltype) -> Aligned:
+    seq = _construct_unaligned_seq(data, moltype)
+    return Aligned(*seq.parse_out_gaps())
+
+
+@_construct_aligned_seq.register
+def _(data: Aligned, moltype) -> Aligned:
+    return data
+
+
+@_construct_aligned_seq.register
+def _(data: list, moltype) -> Aligned:
+    return _construct_aligned_seq("".join(data), moltype)
+
+
+@_construct_aligned_seq.register
+def _(data: tuple, moltype) -> Aligned:
+    return _construct_aligned_seq("".join(data), moltype)
+
+
+# convert seq data into a numpy array for an array alignment
+@functools.singledispatch
+def _construct_array_aligned_seq(data, moltype) -> ndarray:
+    """convert data into a 1D numpy array of int"""
+    if hasattr(data, "_data"):
+        # some type of ArraySequence
+        return data._data
+
+    if hasattr(data, "_seq"):
+        # some type of Sequence
+        return _construct_array_aligned_seq(str(data), moltype=moltype)
+
+    raise NotImplementedError(f"{type(data)} is not implemented")
+
+
+@_construct_array_aligned_seq.register
+def _(data: ndarray, moltype) -> ndarray:
+    assert data.ndim == 1
+    return data
+
+
+@_construct_array_aligned_seq.register
+def _(data: str, moltype) -> ndarray:
+    data = moltype.make_array_seq(data)
+    return data._data
+
+
+@_construct_array_aligned_seq.register
+def _(data: bytes, moltype) -> ndarray:
+    return _construct_array_aligned_seq(data.decode("utf8"), moltype=moltype)
+
+
+@_construct_array_aligned_seq.register
+def _(data: list, moltype) -> ndarray:
+    return array(data, dtype=numpy.uint8)
+
+
+@_construct_array_aligned_seq.register
+def _(data: tuple, moltype) -> ndarray:
+    return array(data, dtype=numpy.uint8)
+
+
+@_construct_array_aligned_seq.register
+def _(data: Aligned, moltype) -> ndarray:
+    return _construct_array_aligned_seq(str(data), moltype=moltype)
diff --git a/src/cogent3/core/alphabet.py b/src/cogent3/core/alphabet.py
index 391f9cd85..a3c71b7fa 100644
--- a/src/cogent3/core/alphabet.py
+++ b/src/cogent3/core/alphabet.py
@@ -14,6 +14,7 @@
 """
 
 import json
+import typing
 
 from itertools import product
 
@@ -113,7 +114,10 @@ def __new__(cls, data=None, gap=None, moltype=None):
         Takes gap as an argument but ignores it (handled in __init__).
         """
         data = data or []
-        return tuple.__new__(cls, data)
+
+        result = tuple.__new__(cls, data)
+        register_alphabet_moltype(alphabet=result, moltype=moltype)
+        return result
 
     def __init__(self, data=None, gap=None, moltype=None):
         """Initializes self from data, and optionally a gap.
@@ -146,8 +150,6 @@ def __init__(self, data=None, gap=None, moltype=None):
         accidentally use negative numbers as indices (this is very bad when
         doing indexed lookups).
         """
-        self.moltype = moltype
-
         # check if motif lengths are homogeneous -- if so, set length
         try:
             motif_lengths = frozenset(list(map(len, self)))
@@ -181,6 +183,10 @@ def __init__(self, data=None, gap=None, moltype=None):
         self.array_type = get_array_type(len(self))
         self._complement_array = None  # set in moltypes.py for standard types
 
+    @property
+    def moltype(self):
+        return _get_moltype(self)
+
     def index(self, item):
         """Returns the index of a specified item.
 
@@ -264,22 +270,6 @@ def __mul__(self, other):
             moltype = None
         return JointEnumeration([self, other], moltype=moltype)
 
-    @c3warns.deprecated_callable(
-        "2023.10",
-        "Not used",
-        is_discontinued=True,
-        stack_level=4,
-    )
-    def counts(self, a):  # pragma: no cover
-        try:
-            data = ravel(a)
-        except ValueError:  # ravel failed; try coercing to array
-            try:
-                data = ravel(array(a))
-            except ValueError:  # try mapping to string
-                data = ravel(array(list(map(str, a))))
-        return sum(asarray(self._allowed_range == data, int), axis=-1)
-
 
 class JointEnumeration(Enumeration):
     """Holds an enumeration composed of subenumerations. Immutable.
@@ -511,26 +501,6 @@ def get_word_alphabet(self, word_length):
         cross_product = ["".join(combo) for combo in product(*states)]
         return Alphabet(cross_product, moltype=self.moltype)
 
-    def get_matched_array(self, motifs, dtype=float):
-        """Returns an array in which rows are motifs, columns are items in self.
-
-        Result is an array of Float in which a[i][j] indicates whether the ith
-        motif passed in as motifs is a symbol that matches the jth character
-        in self. For example, on the DNA alphabet 'TCAG', the degenerate symbol
-        'Y' would correspond to the row [1,1,0,0] because Y is a degenerate
-        symbol that encompasses T and C but not A or G.
-
-        This code is similar to code in the Profile class, and should perhaps
-        be merged with it (in particular, because there is nothing likelihood-
-        specific about the resulting match table).
-        """
-        result = zeros([len(motifs), len(self)], dtype)
-        obj_to_index = self._obj_to_index
-        for u, ambig_motif in enumerate(motifs):
-            for motif in self.resolve_ambiguity(ambig_motif):
-                result[u, obj_to_index[motif]] = 1.0
-        return result
-
     def get_motif_len(self):
         """Returns the length of the items in self, or None if they differ."""
         return self._motiflen
@@ -579,12 +549,13 @@ def get_subset(self, motif_subset, excluded=False):
             motif_subset = [m for m in self if m not in motif_subset]
         return self._with(motif_subset)
 
+    @c3warns.deprecated_callable(
+        "2024.3",
+        reason="does not belong on alphabet",
+        new="<moltype>.resolve_ambiguity()",
+    )
     def resolve_ambiguity(self, ambig_motif):
-        """Returns set of symbols corresponding to ambig_motif.
-
-        Handles multi-character symbols and screens against the set of
-        valid motifs, unlike the MolType version.
-        """
+        """deprecated, use method on MolType"""
         # shortcut easy case
         if ambig_motif in self._quick_motifset:
             return (ambig_motif,)
@@ -619,30 +590,18 @@ def resolve_ambiguity(self, ambig_motif):
         return tuple(motif_set)
 
     @c3warns.deprecated_callable(
-        "2023.10",
-        "Handled by cogent3.evolve.motif_prob_model.adapt_motif_probs",
-        is_discontinued=True,
-        stack_level=4,
+        "2024.3",
+        reason="does not belong on alphabet",
+        new="cogent3.evolve.likelihood_tree.get_matched_array",
     )
-    def adapt_motif_probs(self, motif_probs):  # pragma: no cover
-        """Prepare an array or dictionary of probabilities for use with
-        this alphabet by checking size and order"""
-        if hasattr(motif_probs, "keys"):
-            sample = list(motif_probs.keys())[0]
-            if sample not in self:
-                raise ValueError(f"Can't find motif {sample} in alphabet")
-            motif_probs = array([motif_probs[motif] for motif in self])
-        elif len(motif_probs) == len(self):
-            # what value this clause since order is just input order?
-            motif_probs = asarray(motif_probs)
-        else:
-            raise ValueError(
-                f"Can't match {len(motif_probs)} probs to {len(self)} alphabet"
-            )
-        assert_allclose(
-            motif_probs.sum(), 1, err_msg=f"does not summ to 1 {motif_probs}"
-        )
-        return motif_probs
+    def get_matched_array(self, motifs, dtype=float):
+        """deprecated, use function in evolve.likelihood_tree"""
+        result = zeros([len(motifs), len(self)], dtype)
+        obj_to_index = self._obj_to_index
+        for u, ambig_motif in enumerate(motifs):
+            for motif in self.resolve_ambiguity(ambig_motif):
+                result[u, obj_to_index[motif]] = 1.0
+        return result
 
 
 class CharAlphabet(Alphabet):
@@ -715,3 +674,50 @@ def to_string(self, data, delimiter="\n"):
             return delimiter.join(
                 [i.tobytes().decode("utf-8") for i in self.to_chars(data)]
             )
+
+
+T = typing.Union[Alphabet, CharAlphabet]
+
+
+def make_alphabet(
+    *, motifset: typing.Iterable[str], moltype: "MolType", gap: str = "-"
+) -> T:
+    """constructs an alphabet and registers moltype
+
+    Notes
+    -----
+    The moltype is associated with the alphabet, available as an
+    alphabet property.
+    """
+
+    # the association of an alphabet and its moltype is done within
+    # the Enumaration constructor
+    if isinstance(motifset, Enumeration):
+        alphabet = motifset
+    elif max(len(motif) for motif in motifset) == 1:
+        alphabet = CharAlphabet(motifset, gap=gap, moltype=moltype)
+    else:
+        alphabet = Alphabet(motifset, gap=gap, moltype=moltype)
+
+    return alphabet
+
+
+_alphabet_moltype_map = {}
+
+NT = type(None)
+
+
+def register_alphabet_moltype(*, alphabet: T, moltype: "MolType") -> NT:
+    """registers a lookup of moltype for an alphabet
+
+    Notes
+    -----
+    Uses id(object) rather than object value. This assumes
+    an alphabet is only every associated with one molecular
+    type.
+    """
+    _alphabet_moltype_map[id(alphabet)] = moltype
+
+
+def _get_moltype(alphabet):
+    return _alphabet_moltype_map.get(id(alphabet))
diff --git a/src/cogent3/core/annotation_db.py b/src/cogent3/core/annotation_db.py
index b5b59eee8..641e78ed0 100644
--- a/src/cogent3/core/annotation_db.py
+++ b/src/cogent3/core/annotation_db.py
@@ -12,6 +12,7 @@
 import typing
 
 import numpy
+import typing_extensions
 
 from cogent3._version import __version__
 from cogent3.util.deserialise import register_deserialiser
@@ -120,6 +121,19 @@ def num_matches(
     ) -> int:
         ...
 
+    def subset(
+        self,
+        *,
+        seqid: OptionalStr = None,
+        biotype: OptionalStr = None,
+        name: OptionalStr = None,
+        start: OptionalInt = None,
+        end: OptionalInt = None,
+        strand: OptionalStr = None,
+        attributes: OptionalStr = None,
+    ):
+        ...
+
 
 @typing.runtime_checkable
 class SupportsWriteFeatures(typing.Protocol):  # pragma: no cover
@@ -1031,6 +1045,43 @@ def write(self, path: os.PathLike) -> None:
             self.db.backup(backup)
         backup.close()
 
+    def subset(
+        self,
+        *,
+        source: os.PathLike | str = ":memory:",
+        biotype: str = None,
+        seqid: str = None,
+        name: str = None,
+        start: int = None,
+        end: int = None,
+        strand: bool = None,
+        attributes: OptionalStr = None,
+        allow_partial: bool = False,
+    ) -> typing_extensions.Self:
+        """returns a new db instance with records matching the provided conditions"""
+        kwargs = {k: v for k, v in locals().items() if k not in {"self", "source"}}
+
+        result = self.__class__(source=source)
+        if not len(self):
+            return result
+
+        for table_name in self.table_names:
+            cols = [
+                r["name"]
+                for r in self.db.execute(f"PRAGMA table_info({table_name})").fetchall()
+            ]
+            pos = ", ".join("?" * len(cols))
+            sql = f"INSERT INTO {table_name} ({','.join(cols)}) VALUES ({pos});"
+            records = []
+            for record in self._get_records_matching(table_name=table_name, **kwargs):
+                records.append(tuple(record[c] for c in cols))
+
+            with result.db as cursor:
+                cursor.executemany(sql, records)
+                cursor.commit()
+
+        return result
+
 
 class BasicAnnotationDb(SqliteAnnotationDbMixin):
     """Provides a user table for annotations. This can be merged with
@@ -1156,6 +1207,8 @@ def _merged_data(self, records) -> dict:
         # I am not convinced we can rely on gff files to be ordered,
         # if we could, we could do this as one pass over the data
         # all keys need to be lower case
+        # NOTE only records which have an ID field get merged into a single
+        # record.
         for record in records:
             record["biotype"] = record.pop("Type")
 
@@ -1456,6 +1509,7 @@ def _db_from_genbank(path: os.PathLike, db: SupportsFeatures, write_path, **kwar
     paths = list(paths.parent.glob(paths.name))
 
     ui = kwargs.pop("ui")
+    one_valid_path = False
     for path in ui.series(paths):
         with open_(path) as infile:
             rec = list(MinimalGenbankParser(infile))[0]
@@ -1465,7 +1519,10 @@ def _db_from_genbank(path: os.PathLike, db: SupportsFeatures, write_path, **kwar
                 seqid=rec["locus"],
                 db=db,
             )
+            one_valid_path = True
 
+    if not one_valid_path:
+        raise IOError(f"{str(path)!r} not found")
     return db
 
 
@@ -1490,6 +1547,7 @@ def _db_from_gff(
     paths = list(paths.parent.glob(paths.name))
 
     ui = kwargs.pop("ui")
+    one_valid_path = False
     for path in ui.series(paths):
         data = list(
             gff_parser(
@@ -1499,6 +1557,9 @@ def _db_from_gff(
             )
         )
         db = GffAnnotationDb(source=write_path, data=data, db=db)
+        one_valid_path = True
+    if not one_valid_path:
+        raise IOError(f"{str(path)!r} not found")
     return db
 
 
diff --git a/src/cogent3/core/genetic_code.py b/src/cogent3/core/genetic_code.py
index 5ad41f6c5..4d37a3c8a 100644
--- a/src/cogent3/core/genetic_code.py
+++ b/src/cogent3/core/genetic_code.py
@@ -338,11 +338,11 @@ def to_regex(self, seq):
         return "".join(mappings)
 
     def get_alphabet(self, include_stop=False):
-        from cogent3.core.alphabet import Alphabet
+        from cogent3.core.alphabet import make_alphabet
         from cogent3.core.moltype import DNA
 
         motifs = list(self.codons) if include_stop else list(self.sense_codons)
-        return Alphabet(motifs, moltype=DNA)
+        return make_alphabet(motifset=motifs, moltype=DNA)
 
 
 NcbiGeneticCodeData = [
diff --git a/src/cogent3/core/location.py b/src/cogent3/core/location.py
index b73362891..c157eb73d 100644
--- a/src/cogent3/core/location.py
+++ b/src/cogent3/core/location.py
@@ -1,19 +1,12 @@
-"""Alignments and Sequences are _Annotatables
-_Annotatables hold a list of Maps.
-Maps can be Features, Variables or AlignedSequences.
+"""
 Maps have a list of Spans.
 
-Also provides Range and Point classes for dealing with parts of sequences.
-
-Span is a region with a start, an end, and a direction. Range is an ordered
-collection of Spans (note: Range does _not_ support the list interface, but
-you can always access Range.spans directly). Map is like a Range but is
-immutable and is able to be nested, i.e. Maps can be defined relative to
-other Maps.
+Span is a region with a start, an end, and a direction.
 
-Implementation Notes
+Notes
+-----
 
-Span and Range behave much like Python's slices: a Span contains the element
+Spans behave much like Python's slices: a Span contains the element
 after its start but does not contain the element after its end. It may help to
 think of the Span indices occurring _between_ the list elements:
 
@@ -25,16 +18,6 @@
 a Span whose end is 2 more than its start contains 2 elements (e.g. 2:4 has c
 and d), etc. Similarly, Span(0,2) does _not_ overlap Span(2,3), since the
 former contains a and b while the latter contains c.
-
-A Point is a Span whose start and end refer to the same object, i.e. the same
-position in the sequence. A Point occurs between elements in the sequence,
-and so does not contain any elements itself.
-
-WARNING: this differs from the way e.g. NCBI handles sequence indices, where
-the sequence is 1-based, a single index is treated as containing one element,
-the point 3 contains exactly one element, 3, rather than no elements, and a
-range from 2:4 contains 2, 3 and 4, _not_ just 2 and 3.
-
 """
 import copy
 
@@ -46,6 +29,7 @@
 from numpy import array, ndarray
 
 from cogent3._version import __version__
+from cogent3.util import warning as c3warn
 from cogent3.util.misc import (
     ClassChecker,
     ConstrainedList,
@@ -880,15 +864,21 @@ def relative_position(self, abs_pos: T) -> T:
         return abs_pos - self.start
 
 
-class SpansOnly(ConstrainedList):
+@c3warn.deprecated_callable(
+    version="2024.3", reason="No being used.", is_discontinued=True
+)
+class SpansOnly(ConstrainedList):  # pragma: no cover
     """List that converts elements to Spans on addition."""
 
     mask = FunctionWrapper(Span)
     _constraint = ClassChecker(Span)
 
 
+@c3warn.deprecated_callable(
+    version="2024.3", reason="No being used.", is_discontinued=True
+)
 @total_ordering
-class Range(SpanI):
+class Range(SpanI):  # pragma: no cover
     """Complex object consisting of many spans."""
 
     def __init__(self, spans=None):
@@ -1052,7 +1042,10 @@ def simplify(self):
         self.spans[:] = forward + reverse
 
 
-class Point(Span):
+@c3warn.deprecated_callable(
+    version="2024.3", reason="No being used.", is_discontinued=True
+)
+class Point(Span):  # pragma: no cover
     """Point is a special case of Span, where start always equals end.
 
     Note that, as per Python standard, a point is _between_ two elements
@@ -1080,7 +1073,10 @@ def _set_start(self, start):
     end = start  # start and end are synonyms for the same property
 
 
-def RangeFromString(string, delimiter=","):
+@c3warn.deprecated_callable(
+    version="2024.3", reason="No being used.", is_discontinued=True
+)
+def RangeFromString(string, delimiter=","):  # pragma: no cover
     """Returns Range object from string of the form 1-5,11,20,30-50.
 
     Ignores whitespace; expects values to be comma-delimited and positive.
diff --git a/src/cogent3/core/moltype.py b/src/cogent3/core/moltype.py
index 9b3b27c3c..9ebc02a08 100644
--- a/src/cogent3/core/moltype.py
+++ b/src/cogent3/core/moltype.py
@@ -11,8 +11,10 @@
 types and the MolType can be made after the objects are created.
 """
 
+import itertools
 import json
 import re
+import typing
 
 from collections import defaultdict
 from copy import deepcopy
@@ -28,11 +30,10 @@
 from cogent3.core.alphabet import (
     Alphabet,
     AlphabetError,
-    CharAlphabet,
-    Enumeration,
     _make_complement_array,
+    make_alphabet,
+    register_alphabet_moltype,
 )
-from cogent3.core.genetic_code import get_code
 from cogent3.core.sequence import (
     ABSequence,
     ArrayDnaCodonSequence,
@@ -57,7 +58,6 @@
     get_object_provenance,
 )
 from cogent3.util.transform import KeepChars, first_index_in_set
-from cogent3.util.warning import deprecated
 
 
 maketrans = str.maketrans
@@ -451,20 +451,16 @@ def __init__(
         constructor=None,
     ):
         """Returns new AlphabetGroup."""
-        if constructor is None:
-            if max(list(map(len, chars))) == 1:
-                constructor = CharAlphabet
-                chars = "".join(chars)
-                degens = "".join(degens)
-            else:
-                constructor = Alphabet  # assume multi-char
+        if max(list(map(len, chars))) == 1:
+            chars = "".join(chars)
+            degens = "".join(degens)
 
         super(AlphabetGroup, self).__init__(
-            base=constructor(chars, moltype=moltype),
-            degen=constructor(chars + degens, moltype=moltype),
-            gapped=constructor(chars + gap, gap, moltype=moltype),
-            degen_gapped=constructor(
-                chars + gap + degens + missing, gap, moltype=moltype
+            base=make_alphabet(motifset=chars, moltype=moltype),
+            degen=make_alphabet(motifset=chars + degens, moltype=moltype),
+            gapped=make_alphabet(motifset=chars + gap, gap=gap, moltype=moltype),
+            degen_gapped=make_alphabet(
+                motifset=chars + gap + degens + missing, gap=gap, moltype=moltype
             ),
         )
 
@@ -636,12 +632,8 @@ def __init__(
             self.alphabets = AlphabetGroup(motifset, ambiguities, moltype=self)
             self.alphabet = self.alphabets.base
         else:
-            if isinstance(motifset, Enumeration):
-                self.alphabet = motifset
-            elif max(len(motif) for motif in motifset) == 1:
-                self.alphabet = CharAlphabet(motifset, moltype=self)
-            else:
-                self.alphabet = Alphabet(motifset, moltype=self)
+            self.alphabet = make_alphabet(motifset=motifset, moltype=self)
+
         # set the other properties
         self.degenerates = ambiguities and ambiguities.copy() or {}
         self.degenerates[self.missing] = "".join(motifset) + self.gap
@@ -696,6 +688,8 @@ def __init__(
 
         self._coerce_string = coerce_string or _do_nothing
 
+        register_alphabet_moltype(alphabet=self.alphabet, moltype=self)
+
     def __repr__(self):
         """String representation of MolType.
 
@@ -1292,6 +1286,56 @@ def get_css_style(self, colors=None, font_size=12, font_family="Lucida Console")
 
         return css, styles
 
+    def resolve_ambiguity(
+        self,
+        ambig_motif: str,
+        alphabet: typing.Optional[Alphabet] = None,
+        allow_gap: bool = False,
+    ) -> typing.Tuple[str]:
+        """Returns tuple of all possible canonical characters corresponding
+        to ambig_motif
+
+        Parameters
+        ----------
+        ambig_motif
+            the string to be expanded
+        alphabet
+            optional, disambiguated motifs not present in alphabet will be
+            excluded. This could be a codon alphabet where stop codons are
+            not present.
+        allow_gap
+            whether the gap character is allowed in output. Only
+            applied when alphabet is None.
+
+        Notes
+        -----
+        If ambig_motif is > 1 character long and alphabet is None, we construct
+        a word alphabet with the same length.
+        """
+        ambiguities = {**self.ambiguities}
+        if alphabet is None:
+            word_alpha = self.alphabet.get_word_alphabet(len(ambig_motif))
+            alphabet = word_alpha.with_gap_motif() if allow_gap else word_alpha
+            if not allow_gap:
+                ambiguities["?"] = tuple(c for c in ambiguities["?"] if c != self.gap)
+
+        if ambig_motif in alphabet:
+            return (ambig_motif,)
+
+        try:
+            resolved = [ambiguities[c] for c in ambig_motif]
+        except KeyError:
+            raise AlphabetError(ambig_motif)
+
+        result = tuple("".join(e) for e in itertools.product(*resolved))
+        if alphabet:
+            result = tuple(e for e in result if e in alphabet)
+
+        if not result:
+            raise AlphabetError(ambig_motif)
+
+        return result
+
 
 def _convert_to_rna(seq: str) -> str:
     return seq.replace("t", "u").replace("T", "U")
@@ -1389,44 +1433,6 @@ def _convert_to_dna(seq: str) -> str:
     label="ab",
 )
 
-# todo the _CodonAlphabet class should not exist,
-# the genetic code should have an alphabet, not
-# the alphabet has a genetic code
-
-
-class _CodonAlphabet(Alphabet):
-    """Codon alphabets are DNA TupleAlphabets with a genetic code attribute and some codon-specific methods"""
-
-    def _with(self, motifs):
-        a = Alphabet._with(self, motifs)
-        a.__class__ = type(self)
-        a._gc = self._gc
-        return a
-
-    def is_sense_codon(self, codon):
-        return not self._gc.is_stop(codon)
-
-    def is_stop_codon(self, codon):
-        return self._gc.is_stop(codon)
-
-    def get_genetic_code(self):
-        return self._gc
-
-
-def CodonAlphabet(gc=1, include_stop_codons=False):
-    if isinstance(gc, (int, str)):
-        gc = get_code(gc)
-    if include_stop_codons:
-        motifset = list(gc.codons)
-    else:
-        motifset = list(gc.sense_codons)
-    motifset = [codon.upper().replace("U", "T") for codon in motifset]
-    a = _CodonAlphabet(motifset, moltype=DNA)
-    a._gc = gc
-    return a
-
-
-STANDARD_CODON = CodonAlphabet()
 
 # Modify NucleicAcidSequence to avoid circular import
 NucleicAcidSequence.protein = PROTEIN
@@ -1452,7 +1458,7 @@ def CodonAlphabet(gc=1, include_stop_codons=False):
 
 # Modify Alignment to avoid circular import
 Alignment.moltype = ASCII
-SequenceCollection.moltype = BYTES
+SequenceCollection.moltype = ASCII
 
 
 def _make_moltype_dict():
@@ -1499,3 +1505,13 @@ def available_moltypes():
     result = result.sorted(columns=["Number of states", "Abbreviation"])
     result.format_column("Abbreviation", repr)
     return result
+
+
+def CodonAlphabet(gc=1, include_stop_codons=False):
+    from cogent3.core import genetic_code
+
+    gc = genetic_code.get_code(gc)
+    return gc.get_alphabet(include_stop=include_stop_codons)
+
+
+STANDARD_CODON = CodonAlphabet()
diff --git a/src/cogent3/core/sequence.py b/src/cogent3/core/sequence.py
index 797d411dd..092f2ccb4 100644
--- a/src/cogent3/core/sequence.py
+++ b/src/cogent3/core/sequence.py
@@ -29,6 +29,9 @@
     arange,
     array,
     compress,
+    floating,
+    integer,
+    issubdtype,
     logical_not,
     logical_or,
     nonzero,
@@ -63,11 +66,7 @@
     get_setting_from_environ,
 )
 from cogent3.util.transform import for_seq, per_shortest
-from cogent3.util.warning import (
-    deprecated,
-    deprecated_args,
-    deprecated_callable,
-)
+from cogent3.util.warning import deprecated_args
 
 
 ARRAY_TYPE = type(array(1))
@@ -77,6 +76,16 @@
 frac_diff = for_seq(f=ne, aggregator=sum, normalizer=per_shortest)
 
 
+def _is_int(val) -> bool:
+    """whether val is builtin, or numpy, integer"""
+    return issubdtype(val.__class__, integer) or isinstance(val, int)
+
+
+def _is_float(val) -> bool:
+    """whether val is builtin, or numpy, integer"""
+    return issubdtype(val.__class__, floating) or isinstance(val, float)
+
+
 @total_ordering
 class SequenceI(object):
     """Abstract class containing Sequence interface.
@@ -1154,6 +1163,9 @@ def to_moltype(self, moltype):
             raise ValueError(f"unknown moltype '{moltype}'")
 
         moltype = get_moltype(moltype)
+        if moltype is self.moltype:
+            return self
+
         s = moltype.coerce_str(self._seq.value)
         moltype.verify_sequence(s, gaps_allowed=True, wildcards_allowed=True)
         sv = SeqView(s)
@@ -1317,7 +1329,7 @@ def __getitem__(self, index):
             new = self._mapped(index)
             preserve_offset = not index.reverse
 
-        elif isinstance(index, (int, slice)):
+        elif isinstance(index, slice) or _is_int(index):
             new = self.__class__(
                 self._seq[index], name=self.name, check=False, info=self.info
             )
@@ -1331,6 +1343,9 @@ def __getitem__(self, index):
             new.replace_annotation_db(self.annotation_db, check=False)
             new.annotation_offset = self.annotation_offset
 
+        if _is_float(index):
+            raise TypeError("cannot slice using float")
+
         if hasattr(self, "_repr_policy"):
             new._repr_policy.update(self._repr_policy)
 
@@ -1629,9 +1644,10 @@ def rc(self):
         rc.annotation_db = self.annotation_db
         return rc
 
-    @deprecated_args("2023.10", "replaced by strict", discontinued="allow_partial")
     def has_terminal_stop(
-        self, gc: Any = None, strict: bool = False, allow_partial=False
+        self,
+        gc: Any = None,
+        strict: bool = False,
     ) -> bool:
         """Return True if the sequence has a terminal stop codon.
 
@@ -1656,9 +1672,10 @@ def has_terminal_stop(
 
         return False
 
-    @deprecated_args("2023.10", "replaced by strict", discontinued="allow_partial")
     def trim_stop_codon(
-        self, gc: Any = None, strict: bool = False, allow_partial=False
+        self,
+        gc: Any = None,
+        strict: bool = False,
     ):
         """Removes a terminal stop codon from the sequence
 
@@ -1742,6 +1759,7 @@ def get_translation(
         protein = get_moltype("protein_with_stop" if include_stop else "protein")
         gc = get_code(gc)
         codon_alphabet = gc.get_alphabet(include_stop=include_stop).with_gap_motif()
+        moltype = self.moltype
         # translate the codons
         translation = []
         if include_stop or not trim_stop:
@@ -1753,7 +1771,9 @@ def get_translation(
         for posn in range(0, len(seq) - 2, 3):
             orig_codon = str(seq[posn : posn + 3])
             try:
-                resolved = codon_alphabet.resolve_ambiguity(orig_codon)
+                resolved = moltype.resolve_ambiguity(
+                    orig_codon, alphabet=codon_alphabet
+                )
             except AlphabetError:
                 if not incomplete_ok or "-" not in orig_codon:
                     raise AlphabetError(
@@ -2136,7 +2156,7 @@ def _get_reverse_slice_from_reverse_seqview_(self, slice_start, slice_stop, step
         )
 
     def __getitem__(self, segment):
-        if isinstance(segment, int):
+        if _is_int(segment):
             start, stop, step = self._get_index(segment)
             return self.__class__(self.seq, start=start, stop=stop, step=step)
 
diff --git a/src/cogent3/core/tree.py b/src/cogent3/core/tree.py
index 84efc41ab..2a76d50e5 100644
--- a/src/cogent3/core/tree.py
+++ b/src/cogent3/core/tree.py
@@ -40,6 +40,7 @@
 
 from cogent3._version import __version__
 from cogent3.maths.stats.test import correlation
+from cogent3.phylo.tree_distance import get_tree_distance_measure
 from cogent3.util.io import atomic_write, get_format_suffixes
 from cogent3.util.misc import get_object_provenance
 
@@ -1598,7 +1599,70 @@ def rooted_with_tip(self, outgroup_name):
         tip = self.get_node_matching_name(outgroup_name)
         return tip.parent.unrooted_deepcopy()
 
-    def lin_rajan_moret(self, tree2) -> float:
+    def tree_distance(self, other: TreeNode, method: str | None = None) -> int:
+        """Return the specified tree distance between this and another tree.
+
+        Defaults to the Lin-Rajan-Moret distance on unrooted trees.
+        Defaults to the Matching Cluster distance on rooted trees.
+
+        Parameters
+        ----------
+        other: TreeNode
+            The other tree to calculate the distance between.
+        method: str | None
+            The tree distance metric to use.
+
+            Options are:
+            "rooted_robinson_foulds": The Robinson-Foulds distance for rooted trees.
+            "unrooted_robinson_foulds": The Robinson-Foulds distance for unrooted trees.
+            "matching_cluster": The Matching Cluster distance for rooted trees.
+            "lin_rajan_moret": The Lin-Rajan-Moret distance for unrooted trees.
+            "rrf": An alias for rooted_robinson_foulds.
+            "urf": An alias for unrooted_robinson_foulds.
+            "mc": An alias for matching_cluster.
+            "lrm": An alias for lin_rajan_moret.
+            "rf": The unrooted/rooted Robinson-Foulds distance for unrooted/rooted trees.
+            "matching": The Lin-Rajan-Moret/Matching Cluster distance for unrooted/rooted trees.
+
+            Default is "matching".
+
+        Returns
+        -------
+        int
+            the chosen distance between the two trees.
+
+        Notes
+        -----
+        The Lin-Rajan-Moret distance [2]_ and Matching Cluster distance [1]_
+        display superior statistical properties than the Robinson-Foulds
+        distance [3]_ on unrooted and rooted trees respectively.
+
+        References
+        ----------
+        .. [1] Bogdanowicz, D., & Giaro, K. (2013).
+           On a matching distance between rooted phylogenetic trees.
+           International Journal of Applied Mathematics and Computer Science, 23(3), 669-684.
+        .. [2] Lin et al. 2012
+           A Metric for Phylogenetic Trees Based on Matching
+           IEEE/ACM Transactions on Computational Biology and Bioinformatics
+           vol. 9, no. 4, pp. 1014-1022, July-Aug. 2012
+        .. [3] Robinson, David F., and Leslie R. Foulds.
+           Comparison of phylogenetic trees.
+           Mathematical biosciences 53.1-2 (1981): 131-147.
+        """
+
+        if method is None:
+            method = "matching"
+
+        is_rooted = len(self) == 2
+        if is_rooted and len(other) != 2 or not is_rooted and len(other) == 2:
+            raise ValueError(
+                "Both trees must be rooted or both trees must be unrooted."
+            )
+
+        return get_tree_distance_measure(method, is_rooted)(self, other)
+
+    def lin_rajan_moret(self, tree2) -> int:
         """return the lin-rajan-moret distance between trees
 
         float
diff --git a/src/cogent3/evolve/fast_distance.py b/src/cogent3/evolve/fast_distance.py
index e6b684609..3b73e9af2 100644
--- a/src/cogent3/evolve/fast_distance.py
+++ b/src/cogent3/evolve/fast_distance.py
@@ -1,14 +1,14 @@
 from collections import defaultdict, namedtuple
 from numbers import Number
+from typing import Tuple
 
-import numpy
+import numpy as np
 
 from numpy import array, diag, dot, eye, float64, int32, log, sqrt, zeros
 from numpy.linalg import det, inv
 
 from cogent3._version import __version__
 from cogent3.core.moltype import DNA, RNA, get_moltype
-from cogent3.util import warning as c3warn
 from cogent3.util.dict_array import DictArray
 from cogent3.util.misc import get_object_provenance
 from cogent3.util.progress_display import display_wrap
@@ -800,9 +800,9 @@ def drop_invalid(self):
             raise RuntimeError("Must be a square matrix")
         names = array(self.names)
         # NaN is an invalid value
-        cols = numpy.isnan(self.array).sum(axis=0)
+        cols = np.isnan(self.array).sum(axis=0)
         exclude = names[cols != 0].tolist()
-        rows = numpy.isnan(self.array).sum(axis=1)
+        rows = np.isnan(self.array).sum(axis=1)
         exclude += names[rows != 0].tolist()
         exclude = set(exclude)
         keep = set(names) ^ exclude
@@ -810,6 +810,7 @@ def drop_invalid(self):
 
     def quick_tree(self, show_progress=False):
         """returns a neighbour joining tree
+
         Returns
         -------
         an estimated Neighbour Joining Tree, note that invalid distances are dropped
@@ -822,3 +823,43 @@ def quick_tree(self, show_progress=False):
             raise ValueError("Too few distances to build a treenj")
         dists = dists.to_dict()
         return nj(dists, show_progress=show_progress)
+
+    def max_pair(self) -> Tuple[str, str]:
+        """returns the pair of names with the maximum distance
+
+        Returns
+        -------
+        tuple of the two names corresponding to the maximum distance
+
+        Notes
+        -----
+        In case of multiple occurrences of the maximum values, the names
+        corresponding to the first occurrence are returned.
+        """
+        max_index_flat = np.argmax(self)
+        max_index_1, max_index_2 = np.unravel_index(max_index_flat, self.shape)
+        max_pair = self.names[max_index_1], self.names[max_index_2]
+
+        return max_pair
+
+    def min_pair(self) -> Tuple[str, str]:
+        """returns the pair of names with the minimum distance (excluding the diagonal)
+
+        Returns
+        -------
+        tuple of the two names corresponding to the minimum distance
+
+        Notes
+        -----
+        In case of multiple occurrences of the minimum values, the names
+        corresponding to the first occurrence are returned.
+        """
+        dmat_copy = self.array.copy()
+        np.fill_diagonal(
+            dmat_copy, np.inf
+        )  # Exclude diagonal by setting diagonal elements to infinity
+        min_index_flat = np.argmin(dmat_copy)
+        min_index_1, min_index_2 = np.unravel_index(min_index_flat, self.shape)
+        min_pair = self.names[min_index_1], self.names[min_index_2]
+
+        return min_pair
diff --git a/src/cogent3/evolve/likelihood_function.py b/src/cogent3/evolve/likelihood_function.py
index c4e4badb3..64aed6862 100644
--- a/src/cogent3/evolve/likelihood_function.py
+++ b/src/cogent3/evolve/likelihood_function.py
@@ -849,9 +849,9 @@ def get_statistics(self, with_motif_probs=True, with_titles=True):
             if group[table_dims] == [mprob_name]:
                 # if stat_table.shape
                 # if mprobs, we use the motifs as header
-                motifs = list(sorted(set(stat_table.tolist("motif"))))
+                motifs = list(sorted(set(stat_table.to_list("motif"))))
                 if stat_table.shape[1] == 2:
-                    motif_prob = dict(stat_table.tolist())
+                    motif_prob = dict(stat_table.to_list())
                     heading_names = motifs
                     list_table = [motif_prob[m] for m in motifs]
                     list_table = [list_table]
@@ -867,7 +867,7 @@ def get_statistics(self, with_motif_probs=True, with_titles=True):
                             lambda x: x == val, columns=other_col
                         )
                         motif_prob = dict(
-                            subtable.tolist(
+                            subtable.to_list(
                                 [c for c in stat_table.header if c != other_col]
                             )
                         )
diff --git a/src/cogent3/evolve/likelihood_tree.py b/src/cogent3/evolve/likelihood_tree.py
index af3cb1c00..77ae96b8b 100644
--- a/src/cogent3/evolve/likelihood_tree.py
+++ b/src/cogent3/evolve/likelihood_tree.py
@@ -205,17 +205,29 @@ def _indexed(values):
     return unique, counts, index
 
 
-def make_likelihood_tree_leaf(sequence, alphabet=None, seq_name=None):
-    if alphabet is None:
-        alphabet = sequence.moltype.alphabet
-    if seq_name is None:
-        seq_name = sequence.get_name()
-
+def get_matched_array(alphabet, moltype, motifs, dtype=float) -> numpy.ndarray:
+    """Returns an array in which rows are motifs, columns are items in self.
+
+    Result is an array of dtype in which a[i][j] indicates whether the ith
+    motif passed in as motifs is a symbol that matches the jth character
+    in self. For example, on the DNA alphabet 'TCAG', the degenerate symbol
+    'Y' would correspond to the row [1,1,0,0] because Y is a degenerate
+    symbol that encompasses T and C but not A or G.
+    """
+    result = numpy.zeros([len(motifs), len(alphabet)], dtype)
+    obj_to_index = alphabet.to_indices
+    for u, ambig_motif in enumerate(motifs):
+        for motif in moltype.resolve_ambiguity(ambig_motif, alphabet=alphabet):
+            result[u, obj_to_index((motif,))] = 1.0
+    return result
+
+
+def make_likelihood_tree_leaf(sequence, alphabet, seq_name):
     motif_len = alphabet.get_motif_len()
     sequence2 = sequence.get_in_motif_size(motif_len)
 
     # Convert sequence to indexed list of unique motifs
-    (uniq_motifs, counts, index) = _indexed(sequence2)
+    uniq_motifs, counts, index = _indexed(sequence2)
 
     # extra column for gap
     uniq_motifs.append("?" * motif_len)
@@ -225,7 +237,9 @@ def make_likelihood_tree_leaf(sequence, alphabet=None, seq_name=None):
 
     # Convert list of unique motifs to array of unique profiles
     try:
-        likelihoods = alphabet.get_matched_array(uniq_motifs, float)
+        likelihoods = get_matched_array(
+            alphabet, alphabet.moltype, uniq_motifs, dtype=float
+        )
     except alphabet.AlphabetError as detail:
         motif = str(detail)
         posn = list(sequence2).index(motif) * motif_len
diff --git a/src/cogent3/evolve/motif_prob_model.py b/src/cogent3/evolve/motif_prob_model.py
index 85e194ff5..f091a95ef 100644
--- a/src/cogent3/evolve/motif_prob_model.py
+++ b/src/cogent3/evolve/motif_prob_model.py
@@ -49,16 +49,10 @@ def set_param_controller_motif_probs(self, pc, motif_probs, **kw):
 
     def count_motifs(self, alignment, include_ambiguity=False, recode_gaps=True):
         result = None
-        try:
-            mtype = self.alphabet.moltype
-        except AttributeError:
-            mtype = self.monomer_alphabet.moltype
-
+        alpha = self.get_counted_alphabet()
         for seq_name in alignment.names:
-            sequence = alignment.get_gapped_seq(seq_name, recode_gaps, moltype=mtype)
-            leaf = make_likelihood_tree_leaf(
-                sequence, self.get_counted_alphabet(), seq_name
-            )
+            sequence = alignment.get_gapped_seq(seq_name, recode_gaps)
+            leaf = make_likelihood_tree_leaf(sequence, alpha, seq_name)
             count = leaf.get_motif_counts(include_ambiguity=include_ambiguity)
             if result is None:
                 result = count.copy()
diff --git a/src/cogent3/evolve/substitution_model.py b/src/cogent3/evolve/substitution_model.py
index a49dfa753..9c3044b48 100644
--- a/src/cogent3/evolve/substitution_model.py
+++ b/src/cogent3/evolve/substitution_model.py
@@ -388,7 +388,7 @@ def convert_alignment(self, alignment):
         return result
 
     def convert_sequence(self, sequence, name):
-        # make_likelihood_tree_leaf, sort of an indexed profile where duplicate
+        # make_likelihood_tree_leaf, a sort of indexed profile where duplicate
         # columns stored once, so likelihoods only calc'd once
         return make_likelihood_tree_leaf(sequence, self.get_alphabet(), name)
 
diff --git a/src/cogent3/phylo/tree_distance.py b/src/cogent3/phylo/tree_distance.py
index 657b42012..ba6c75c60 100644
--- a/src/cogent3/phylo/tree_distance.py
+++ b/src/cogent3/phylo/tree_distance.py
@@ -1,31 +1,105 @@
 from itertools import product
-from typing import List
+from typing import TYPE_CHECKING
 
-import numpy
+import numpy as np
 
 from scipy.optimize import linear_sum_assignment
 
 
-def lin_rajan_moret(tree1: "PhyloNode", tree2: "PhyloNode") -> float:
-    """calculate the lin-rajan-moret distance (matching distance) between trees
+if TYPE_CHECKING:
+    from cogent3.core.tree import TreeNode
 
-    trees should have matching tips and must not be rooted.
+
+def get_tree_distance_measure(method: str, is_rooted: bool):
+    if method in _TREE_DISTANCE_FUNCTIONS:
+        return _TREE_DISTANCE_FUNCTIONS[method]
+
+    if is_rooted and method in _ROOTED_TREE_DISTANCE_FUNCTIONS:
+        return _ROOTED_TREE_DISTANCE_FUNCTIONS[method]
+
+    if not is_rooted and method in _UNROOTED_TREE_DISTANCE_FUNCTIONS:
+        return _UNROOTED_TREE_DISTANCE_FUNCTIONS[method]
+
+    raise ValueError(
+        f"Tree distance method '{method}' is not supported for {'' if is_rooted else 'un'}rooted trees."
+    )
+
+
+def unrooted_robinson_foulds(tree1: "TreeNode", tree2: "TreeNode") -> int:
+    """Calculate the Robinson-Foulds distance between two unrooted trees.
 
     Parameters
     ----------
-    tree1, tree2: PhyloNode
-        trees to calculate distance between
+    tree1, tree2: TreeNode
+        Trees to calculate the distance between.
 
     Returns
     -------
-    float
-        the Lin-Rajan-Moret distance
+    int
+        The unrooted Robinson-Foulds distance.
 
     Notes
     -----
-    see: Lin et al. 2012 A Metric for Phylogenetic Trees Based on Matching
-    IEEE/ACM Transactions on Computational Biology and Bioinformatics
-    vol. 9, no. 4, pp. 1014-1022, July-Aug. 2012
+    For unrooted trees, the Robinson-Foulds distance [1]_ is defined
+    as the cardinality of the symmetric difference of the set of splits
+    for the two trees.
+
+    Trees should have matching tips and must not be rooted.
+
+    References
+    ----------
+    .. [1] Robinson, David F., and Leslie R. Foulds.
+       Comparison of phylogenetic trees.
+       Mathematical biosciences 53.1-2 (1981): 131-147.
+    """
+
+    names = tree1.get_tip_names()
+
+    if set(names) != set(tree2.get_tip_names()):
+        raise ValueError("tree tip names must match")
+    if len(tree1.children) == 2 or len(tree2.children) == 2:
+        raise ValueError("trees must be unrooted")
+
+    tree1_clusters = tree1.subsets()
+    tree2_clusters = tree2.subsets()
+
+    tree1_splits = _compute_splits(tree1_clusters, names)
+    tree2_splits = _compute_splits(tree2_clusters, names)
+
+    return len(tree1_splits.symmetric_difference(tree2_splits))
+
+
+def lin_rajan_moret(tree1: "TreeNode", tree2: "TreeNode") -> int:
+    """Calculate the Lin-Rajan-Moret distance (matching distance)
+    between two unrooted trees.
+
+    Parameters
+    ----------
+    tree1, tree2: TreeNode
+        Trees to calculate the distance between.
+
+    Returns
+    -------
+    int
+        The Lin-Rajan-Moret distance.
+
+    Notes
+    -----
+    The Lin-Rajan-Moret distance [1]_ displays superior statistical
+    properties than the Robinson-Foulds distance [2]_
+    on unrooted trees.
+
+    Trees should have matching tips and must not be rooted.
+
+    References
+    ----------
+    .. [1] Lin et al. 2012
+       A Metric for Phylogenetic Trees Based on Matching
+       IEEE/ACM Transactions on Computational Biology and Bioinformatics
+       vol. 9, no. 4, pp. 1014-1022, July-Aug. 2012
+    .. [2] Robinson, David F., and Leslie R. Foulds.
+       Comparison of phylogenetic trees.
+       Mathematical biosciences 53.1-2 (1981): 131-147.
     """
     names = tree1.get_tip_names()
 
@@ -41,16 +115,115 @@ def lin_rajan_moret(tree1: "PhyloNode", tree2: "PhyloNode") -> float:
 
     matching_distance = _matched_distance(vector1, vector2)
 
-    return float(matching_distance)
+    return matching_distance
+
+
+def rooted_robinson_foulds(tree1: "TreeNode", tree2: "TreeNode") -> int:
+    """Calculate the Robinson-Foulds distance between two rooted trees.
+
+    Parameters
+    ----------
+    tree1, tree2: TreeNode
+        Trees to calculate the distance between.
 
+    Returns
+    -------
+    int
+        The rooted Robinson-Foulds distance
 
-def _convert_tree_to_vectors(tree: "PhyloNode", tip_names: List) -> numpy.ndarray:
+    Notes
+    -----
+    For rooted trees, the Robinson-Foulds distance [1]_ is defined as the
+    cardinality of the symmetric difference of the set of clades for
+    the two trees.
+
+    Trees should have matching tips and must be rooted.
+
+    References
+    ----------
+    .. [1] Robinson, David F., and Leslie R. Foulds.
+       Comparison of phylogenetic trees.
+       Mathematical biosciences 53.1-2 (1981): 131-147.
+    """
+    if set(tree1.get_tip_names()) != set(tree2.get_tip_names()):
+        raise ValueError("tree tip names must match")
+    if len(tree1.children) != 2 or len(tree2.children) != 2:
+        raise ValueError("trees must be rooted")
+
+    tree1_clusters = tree1.subsets()
+    tree2_clusters = tree2.subsets()
+
+    return len(tree1_clusters.symmetric_difference(tree2_clusters))
+
+
+def matching_cluster_distance(tree1: "TreeNode", tree2: "TreeNode") -> int:
+    """Calculate the Matching Cluster distance between two rooted trees.
+
+    Parameters
+    ----------
+    tree1, tree2: TreeNode
+        Trees to calculate the distance between.
+
+    Returns
+    -------
+    int
+        The Matching Cluster distance.
+
+    Notes
+    -----
+    The Matching Cluster distance [1]_ is a similar metric
+    to the beta-distance [2]_.
+
+    The Matching Cluster distance [1]_ displays superior statistical
+    properties than the Robinson-Foulds distance [3]_ on rooted trees.
+
+    Trees should have matching tips and must be rooted.
+
+    References
+    ----------
+    .. [1] Bogdanowicz, D., & Giaro, K. (2013).
+       On a matching distance between rooted phylogenetic trees.
+       International Journal of Applied Mathematics and Computer Science, 23(3), 669-684.
+    .. [2] Boorman, S. A., & Olivier, D. C. (1973).
+       Metrics on spaces of finite trees.
+       Journal of Mathematical Psychology, 10(1), 26-59.
+    .. [3] Robinson, David F., and Leslie R. Foulds.
+       Comparison of phylogenetic trees.
+       Mathematical biosciences 53.1-2 (1981): 131-147.
+    """
+
+    if set(tree1.get_tip_names()) != set(tree2.get_tip_names()):
+        raise ValueError("tree tip names must match")
+    if len(tree1.children) != 2 or len(tree2.children) != 2:
+        raise ValueError("trees must be rooted")
+
+    tree1_clusters = list(tree1.subsets())
+    tree2_clusters = list(tree2.subsets())
+
+    while len(tree1_clusters) < len(tree2_clusters):
+        tree1_clusters.append(set())
+    while len(tree2_clusters) < len(tree1_clusters):
+        tree2_clusters.append(set())
+
+    adjacency = np.zeros(shape=(len(tree1_clusters), len(tree2_clusters)))
+
+    for i, cluster_1 in enumerate(tree1_clusters):
+        for j, cluster_2 in enumerate(tree2_clusters):
+            adjacency[i, j] = len(cluster_1.symmetric_difference(cluster_2))
+
+    row_ind, col_ind = linear_sum_assignment(adjacency)
+    distance = int(adjacency[row_ind, col_ind].sum())
+
+    return distance
+
+
+def _convert_tree_to_vectors(tree: "TreeNode", tip_names: list) -> np.ndarray:
     ref_tip = tip_names[0]
     name_set = set(tip_names)
     name_index = {n: i for i, n in enumerate(tip_names)}
     # we get the tree as a set of splits.
     splits = tree.subsets()
-    rows = numpy.zeros((len(splits), len(tip_names)), dtype=bool)
+    rows = np.zeros((len(splits), len(tip_names)), dtype=bool)
     for i, split in enumerate(splits):
         row = rows[i]
         # Cogent only returns one side of a
@@ -64,25 +237,60 @@ def _convert_tree_to_vectors(tree: "PhyloNode", tip_names: List) -> numpy.ndarra
     return rows
 
 
-def _hamming(vector1: numpy.ndarray, vector2: numpy.ndarray) -> int:
+def _hamming(vector1: np.ndarray, vector2: np.ndarray) -> int:
     return (vector1 != vector2).sum()
 
 
-def _weight(vector1: numpy.ndarray, vector2: numpy.ndarray) -> int:
+def _weight(vector1: np.ndarray, vector2: np.ndarray) -> int:
     return min(_hamming(vector1, vector2), _hamming(vector1, 1 - vector2))
 
 
-def _bipartite_graph(vector1: numpy.ndarray, vector2: numpy.ndarray) -> numpy.ndarray:
+def _bipartite_graph(vector1: np.ndarray, vector2: np.ndarray) -> np.ndarray:
     if not len(vector1) == len(vector2):
         raise ValueError("number of edges must be equal")
 
-    B = numpy.empty([len(vector1)] * 2, int)
+    B = np.empty([len(vector1)] * 2, int)
     for i, j in product(*[range(len(vector1))] * 2):
         B[i, j] = _weight(vector1[i], vector2[j])
     return B
 
 
-def _matched_distance(vector1: numpy.ndarray, vector2: numpy.ndarray) -> int:
+def _matched_distance(vector1: np.ndarray, vector2: np.ndarray) -> int:
     B = _bipartite_graph(vector1, vector2)
     matching = linear_sum_assignment(B)
     return B[matching].sum()
+
+
+def _compute_splits(clades: frozenset[frozenset], tip_names: list) -> set[frozenset]:
+    ref_tip = tip_names[0]
+    name_set = frozenset(tip_names)
+
+    splits = set()
+    for clade in clades:
+        if ref_tip in clade:
+            splits.add(clade)
+        else:
+            splits.add(name_set - clade)
+    return splits
+
+
+_TREE_DISTANCE_FUNCTIONS = {
+    "rooted_robinson_foulds": rooted_robinson_foulds,
+    "unrooted_robinson_foulds": unrooted_robinson_foulds,
+    "matching_cluster": matching_cluster_distance,
+    "lin_rajan_moret": lin_rajan_moret,
+    "rrf": rooted_robinson_foulds,
+    "urf": unrooted_robinson_foulds,
+    "mc": matching_cluster_distance,
+    "lrm": lin_rajan_moret,
+}
+
+_ROOTED_TREE_DISTANCE_FUNCTIONS = {
+    "rf": rooted_robinson_foulds,
+    "matching": matching_cluster_distance,
+}
+
+_UNROOTED_TREE_DISTANCE_FUNCTIONS = {
+    "rf": unrooted_robinson_foulds,
+    "matching": lin_rajan_moret,
+}
diff --git a/src/cogent3/util/deserialise.py b/src/cogent3/util/deserialise.py
index 92115efba..22577bf70 100644
--- a/src/cogent3/util/deserialise.py
+++ b/src/cogent3/util/deserialise.py
@@ -165,18 +165,13 @@ def deserialise_result(data):
 @register_deserialiser("cogent3.core.moltype")
 def deserialise_moltype(data):
     """returns a cogent3 MolType instance, or a CodonAlphabet"""
-    from cogent3.core.moltype import _CodonAlphabet, get_moltype
+    from cogent3.core.moltype import get_moltype
 
     data.pop("version", None)
     label = data["moltype"]
     data["moltype"] = get_moltype(label)
     klass = _get_class(data.pop("type"))
-    if klass == _CodonAlphabet:
-        gc = get_code(data.pop("genetic_code"))
-        result = _CodonAlphabet(**data)
-        result._gc = gc
-    else:
-        result = data["moltype"]
+    result = data["moltype"]
 
     return result
 
@@ -184,13 +179,9 @@ def deserialise_moltype(data):
 @register_deserialiser("cogent3.core.alphabet")
 def deserialise_alphabet(data):
     """returns a cogent3 Alphabet instance"""
-    from cogent3.core.moltype import _CodonAlphabet, get_moltype
+    from cogent3.core.moltype import get_moltype
 
     data.pop("version", None)
-    if _get_class(data.get("type")) == _CodonAlphabet:
-        result = deserialise_moltype(data)
-        return result
-
     label = data["moltype"]
     data["moltype"] = get_moltype(label)
     key = "data" if "data" in data else "motifset"
diff --git a/src/cogent3/util/misc.py b/src/cogent3/util/misc.py
index b7fc41e92..8813645be 100644
--- a/src/cogent3/util/misc.py
+++ b/src/cogent3/util/misc.py
@@ -1,10 +1,13 @@
 """Generally useful utility classes and methods.
 """
+
 from __future__ import annotations
 
+import contextlib
 import inspect
 import os
 import re
+import typing
 import warnings
 
 from random import randint
@@ -1085,3 +1088,24 @@ def get_true_spans(arr: ndarray, absolute_pos: bool = True) -> ndarray:
         num_runs += 1
 
     return result[:num_runs]
+
+
+def get_first_value(data: typing.Iterable[typing.Any]):
+    """return first value from a container
+
+    Parameters
+    ----------
+    data
+        any iterable
+
+    Returns
+    -------
+    Returns the first element of data. If data is a dict, it will return
+    the first element of data.values().
+    """
+    with contextlib.suppress(StopIteration):
+        if hasattr(data, "values"):
+            return next(iter(data.values()))
+
+        with contextlib.suppress(TypeError):
+            return next(iter(data))
diff --git a/src/cogent3/util/parallel.py b/src/cogent3/util/parallel.py
index ab82809dc..a1251a094 100644
--- a/src/cogent3/util/parallel.py
+++ b/src/cogent3/util/parallel.py
@@ -22,8 +22,7 @@
         MPI = None
     else:
         COMM = MPI.COMM_WORLD
-        size = COMM.Get_attr(MPI.UNIVERSE_SIZE)
-        if size == 1:
+        if COMM.Get_attr(MPI.UNIVERSE_SIZE) == 1:
             MPI = None
 
 
@@ -45,6 +44,16 @@ def get_rank():
     return rank
 
 
+def get_size():
+    """Returns the num cpus"""
+    return (
+        COMM.Get_attr(MPI.UNIVERSE_SIZE) if USING_MPI else multiprocessing.cpu_count()
+    )
+
+
+SIZE = get_size()
+
+
 def is_master_process():
     """
     Evaluates if current process is master
@@ -127,10 +136,10 @@ def imap(f, s, max_workers=None, use_mpi=False, if_serial="raise", chunksize=Non
         if not USING_MPI:
             raise RuntimeError("Cannot use MPI")
 
-        if COMM.Get_attr(MPI.UNIVERSE_SIZE) == 1:
+        if SIZE == 1:
             err_msg = (
                 "Execution in serial. For parallel MPI execution, use:\n"
-                " $ mpiexec -n <number CPUs> python3 -m mpi4py.futures <executable script>"
+                " $ mpiexec -n <number CPUs> python -m mpi4py.futures <executable script>"
             )
 
             if if_serial == "raise":
@@ -140,12 +149,12 @@ def imap(f, s, max_workers=None, use_mpi=False, if_serial="raise", chunksize=Non
 
         max_workers = max_workers or 1
 
-        if max_workers > COMM.Get_attr(MPI.UNIVERSE_SIZE):
+        if max_workers > SIZE:
             warnings.warn(
                 "max_workers too large, reducing to UNIVERSE_SIZE-1", UserWarning
             )
 
-        max_workers = min(max_workers, COMM.Get_attr(MPI.UNIVERSE_SIZE) - 1)
+        max_workers = min(max_workers, SIZE - 1)
         if not chunksize:
             chunksize = get_default_chunksize(s, max_workers)
 
@@ -175,7 +184,7 @@ def _as_completed_mpi(f, s, max_workers, if_serial, chunksize=None):
     if not USING_MPI:
         raise RuntimeError("Cannot use MPI")
 
-    if COMM.Get_attr(MPI.UNIVERSE_SIZE) == 1:
+    if SIZE == 1:
         err_msg = (
             "Execution in serial. For parallel MPI execution, use:\n"
             " $ mpiexec -n <number CPUs> python3 -m mpi4py.futures <executable script>"
@@ -190,10 +199,10 @@ def _as_completed_mpi(f, s, max_workers, if_serial, chunksize=None):
 
     f = PicklableAndCallable(f)
 
-    if max_workers > COMM.Get_attr(MPI.UNIVERSE_SIZE):
+    if max_workers > SIZE:
         warnings.warn("max_workers too large, reducing to UNIVERSE_SIZE-1", UserWarning)
 
-    max_workers = min(max_workers, COMM.Get_attr(MPI.UNIVERSE_SIZE) - 1)
+    max_workers = min(max_workers, SIZE - 1)
     if not chunksize:
         chunksize = get_default_chunksize(s, max_workers)
 
diff --git a/src/cogent3/util/recode_alignment.py b/src/cogent3/util/recode_alignment.py
index 92b78a120..f02ed5f62 100755
--- a/src/cogent3/util/recode_alignment.py
+++ b/src/cogent3/util/recode_alignment.py
@@ -420,14 +420,10 @@ def recode_dense_alignment(aln, alphabet_id=None, alphabet_def=None):
     # Map the old alphabet onto the new alphabet. Note: characters that
     # that are not mapped are ignored. Returns a new ArrayAlignment.
     return ArrayAlignment(
-        take(new_indices, aln.array_seqs).transpose(), aln.names[:], moltype=aln.moltype
+        take(new_indices, aln.array_seqs), aln.names[:], moltype=aln.moltype
     )
 
 
-def recode_alignment(aln, alphabet_id=None, alphabet_def=None):
-    raise NotImplementedError
-
-
 def recode_freq_vector(alphabet_def, freqs, ignores="BXZ"):
     """recode the bg_freqs to reflect the recoding defined in alphabet_def
 
diff --git a/src/cogent3/util/table.py b/src/cogent3/util/table.py
index f7186e578..efbcbca13 100644
--- a/src/cogent3/util/table.py
+++ b/src/cogent3/util/table.py
@@ -24,6 +24,7 @@
 
 from cogent3.format import bedgraph
 from cogent3.format import table as table_format
+from cogent3.util import warning as c3warn
 from cogent3.util.dict_array import DictArray, DictArrayTemplate
 from cogent3.util.io import atomic_write, get_format_suffixes
 from cogent3.util.misc import extend_docstring_from, get_object_provenance
@@ -1915,7 +1916,7 @@ def to_html(self, column_alignment=None):
             HtmlElement("\n".join(tables), "div", css_classes=["c3table"], newline=True)
         )
 
-    def tolist(self, columns=None):
+    def to_list(self, columns=None):
         """Returns raw data as a list
 
         Parameters
@@ -1940,6 +1941,12 @@ def tolist(self, columns=None):
 
         return result
 
+    @c3warn.deprecated_callable(version="2024.1", reason="PEP8", new="to_list")
+    def tolist(self, **kwargs):  # pragma: no cover
+        """deprecated, use to_list"""
+
+        return self.to_list(**kwargs)
+
     @extend_docstring_from(DictArray.to_dict)
     def to_dict(self, flatten=False):
         index = self.columns[self.index_name] if self.index_name else self.shape[0]
@@ -1957,7 +1964,7 @@ def to_json(self):
         data = self.to_rich_dict()
         return json.dumps(data)
 
-    def to_dataframe(self, categories=None):
+    def to_pandas(self, categories=None):
         """returns pandas DataFrame instance
 
         Parameters
@@ -1980,6 +1987,14 @@ def to_dataframe(self, categories=None):
 
         return df
 
+    @c3warn.deprecated_callable(
+        version="2024.3", reason="to enable usage by plotly", new="to_pandas()"
+    )
+    def to_dataframe(self, **kwargs):  # pragma: no cover
+        """deprecated, use to_pandas()"""
+
+        return self.to_pandas(**kwargs)
+
     def to_plotly(self, width=500, font_size=12, layout=None, **kwargs):
         """returns a Plotly Table"""
         from cogent3.draw.drawable import Drawable


Test patch:
diff --git a/.github/workflows/testing_develop.yml b/.github/workflows/testing_develop.yml
index f08b4ff47..6b2a66da1 100644
--- a/.github/workflows/testing_develop.yml
+++ b/.github/workflows/testing_develop.yml
@@ -16,7 +16,7 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest, macos-latest, windows-latest]
-        python-version: ["3.8", "3.9", "3.10", "3.11"]
+        python-version: ["3.9", "3.10", "3.11"]
 
     steps:
       - uses: "actions/checkout@v2"
@@ -51,7 +51,7 @@ jobs:
     strategy:
       matrix:
         os: [macos-latest, ubuntu-latest, windows-latest]
-        python-version: ["3.8", "3.9", "3.10"]
+        python-version: ["3.9", "3.10", "3.11"]
     steps:
     - name: Coveralls Finished
       uses: coverallsapp/github-action@master
diff --git a/tests/test_align/test_compare.py b/tests/test_align/test_compare.py
index 9e2213fb8..f45dbb631 100644
--- a/tests/test_align/test_compare.py
+++ b/tests/test_align/test_compare.py
@@ -60,14 +60,15 @@ def test_find_matched_k_eq_1():
 
 
 def test_calc_seed_size():
-    x = _calc_seed_size(20, 20)
-    assert x == 20
+    """default seed size should not be larger than than threshold"""
+    for i in range(2, 30):
+        x = _calc_seed_size(30, i)
+        assert x <= i
 
-    x = _calc_seed_size(20, 19)
-    assert x == 10
 
-    x = _calc_seed_size(20, 14)
-    assert x == 7
+def test_calc_seed_size_singleton_window():
+    x = _calc_seed_size(1, 1)
+    assert x == 1
 
 
 def test_segment():
@@ -389,6 +390,16 @@ def test_find_matched_with_rc():
     assert x == yrc
 
 
+def test_find_matched_with_small_seed():
+    s1 = make_seq("CACACCACTGCAGTCGGATAGACC", moltype="dna", name="s1")
+    s2 = make_seq("GGTCTATCCGACTGCAGTGGTGTG", moltype="dna", name="s2")
+    k = 2
+    expect = _brute_force(s1, s2, 4, 4)
+    sk = SeqKmers(s1, k=k, canonical="ACGT")
+    got = find_matched_paths(seq_kmers=sk, seq1=s1, seq2=s2, window=4, threshold=4)
+    assert got.paths == expect.paths
+
+
 @pytest.mark.parametrize("w,t", [(4, 4), (3, 3)])
 def test_find_matched_1seq(w, t):
     s = make_seq("CACACCACTGCAGTCGGATAGACC", moltype="dna", name="s1")
diff --git a/tests/test_app/test_composable.py b/tests/test_app/test_composable.py
index cbdefb043..eabec7301 100644
--- a/tests/test_app/test_composable.py
+++ b/tests/test_app/test_composable.py
@@ -5,7 +5,6 @@
 from pathlib import Path
 from pickle import dumps, loads
 from typing import Set, Tuple
-from unittest import main
 from unittest.mock import Mock
 
 import pytest
@@ -38,13 +37,10 @@
     get_data_source,
 )
 from cogent3.app.sample import min_length, omit_degenerates
+from cogent3.app.sqlite_data_store import DataStoreSqlite
 from cogent3.app.translate import select_translatable
 from cogent3.app.tree import quick_tree
-from cogent3.app.typing import (
-    SERIALISABLE_TYPE,
-    AlignedSeqsType,
-    PairwiseDistanceType,
-)
+from cogent3.app.typing import AlignedSeqsType, PairwiseDistanceType
 from cogent3.core.alignment import Alignment, SequenceCollection
 
 
@@ -314,30 +310,63 @@ def test_as_completed(DATA_DIR):
     got = list(proc.as_completed(path, show_progress=False))
     assert len(got) == 1
     assert isinstance(got[0].obj, SequenceCollection)
-    # raises ValueError if empty list
-    with pytest.raises(ValueError):
-        proc.as_completed([])
 
-    # raises ValueError if list with empty string
-    with pytest.raises(ValueError):
-        list(proc.as_completed(["", ""]))
+
+@pytest.mark.parametrize("data", [(), ("", "")])
+def test_as_completed_empty_data(data):
+    """correctly applies iteratively"""
+    reader = get_app("load_unaligned", format="fasta", moltype="dna")
+    min_length = get_app("sample.min_length", 10)
+    proc = reader + min_length
+
+    # returns empty input
+    got = proc.as_completed(data)
+    assert got == []
 
 
-@pytest.mark.parametrize("klass", (DataStoreDirectory,))
-def test_apply_to_strings(DATA_DIR, tmp_dir, klass):
-    """apply_to handles strings as paths"""
+@pytest.mark.parametrize("klass", (DataStoreDirectory, DataStoreSqlite))
+@pytest.mark.parametrize("cast", (str, Path))
+def test_apply_to_strings(DATA_DIR, tmp_dir, klass, cast):
+    """apply_to handles non-DataMember"""
+    dname = "test_apply_to_strings"
+    outpath = tmp_dir / dname
+
     dstore = open_data_store(DATA_DIR, suffix="fasta", limit=3)
-    dstore = [str(m) for m in dstore]
+    dstore = [cast(str(m)) for m in dstore]
     reader = io_app.load_aligned(format="fasta", moltype="dna")
     min_length = sample_app.min_length(10)
-    outpath = tmp_dir / "test_apply_to_strings"
-    writer = io_app.write_seqs(klass(outpath, mode=OVERWRITE, suffix="fasta"))
+    if klass == DataStoreDirectory:
+        writer = io_app.write_seqs(klass(outpath, mode=OVERWRITE, suffix="fasta"))
+    else:
+        writer = io_app.write_seqs(klass(outpath, mode=OVERWRITE))
     process = reader + min_length + writer
     # create paths as strings
-    _ = process.apply_to(dstore, id_from_source=get_data_source)
+    _ = process.apply_to(dstore, id_from_source=get_data_source, show_progress=False)
     assert len(process.data_store.logs) == 1
 
 
+@pytest.mark.parametrize("klass", (DataStoreDirectory, DataStoreSqlite))
+@pytest.mark.parametrize("cast", (str, Path))
+def test_as_completed_strings(DATA_DIR, tmp_dir, klass, cast):
+    """as_completed handles non-DataMember"""
+    dname = "test_apply_to_strings"
+    outpath = tmp_dir / dname
+
+    dstore = open_data_store(DATA_DIR, suffix="fasta", limit=3)
+    dstore = [cast(str(m)) for m in dstore]
+    reader = io_app.load_aligned(format="fasta", moltype="dna")
+    min_length = sample_app.min_length(10)
+    if klass == DataStoreDirectory:
+        writer = io_app.write_seqs(klass(outpath, mode=OVERWRITE, suffix="fasta"))
+    else:
+        writer = io_app.write_seqs(klass(outpath, mode=OVERWRITE))
+    orig_length = len(writer.data_store)
+    process = reader + min_length + writer
+    # create paths as strings
+    got = list(process.as_completed(dstore, show_progress=False))
+    assert len(got) > orig_length
+
+
 def test_apply_to_non_unique_identifiers(tmp_dir):
     """should fail if non-unique names"""
     dstore = [
@@ -1119,9 +1148,10 @@ def test_apply_to_only_appends(half_dstore1, half_dstore2):
     dstore1 = [
         str(Path(m.data_store.source) / m.unique_id) for m in half_dstore1.completed
     ]
-    # check fail on all the same records
-    with pytest.raises(ValueError):
-        _ = process1.apply_to(dstore1, id_from_source=get_data_source)
+    # check does not modify dstore when applied to same records
+    orig_members = {m.unique_id for m in half_dstore1.members}
+    got = process1.apply_to(dstore1, id_from_source=get_data_source)
+    assert {m.unique_id for m in got.members} == orig_members
 
     half_dstore2 = open_data_store(
         half_dstore2.source, suffix=half_dstore2.suffix, mode=APPEND
@@ -1171,7 +1201,3 @@ def delme2(val: c3types.SerialisableType) -> dict:
 
     __app_registry.pop(get_object_provenance(delme), None)
     __app_registry.pop(get_object_provenance(delme2), None)
-
-
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_app/test_data_store.py b/tests/test_app/test_data_store.py
index 094b2cffd..fac1d8d1c 100644
--- a/tests/test_app/test_data_store.py
+++ b/tests/test_app/test_data_store.py
@@ -704,3 +704,30 @@ def test_summary_not_completed(dstore, request):
     got = full_dstore.summary_not_completed
     assert got.shape >= (1, 1)
     assert isinstance(got, Table)
+
+
+@pytest.fixture(scope="function")
+def app_dstore_in(tmp_path):
+    from cogent3 import get_app, open_data_store
+
+    in_path = tmp_path / "in_data"
+    in_path.mkdir(parents=True)
+    fasta_content = ">seq\nACGT"
+    with open(in_path / "one.fa", "w") as file:
+        file.write(fasta_content)
+
+    dstore_in = open_data_store(in_path, suffix=".fa", mode="r")
+    dstore_out = open_data_store(tmp_path / "data_out", suffix="fa", mode="w")
+    loader = get_app("load_unaligned")
+    writer = get_app("write_seqs", dstore_out)
+
+    pipe = loader + writer
+    return pipe, dstore_in
+
+
+def test_write_multiple_times_apply_to(app_dstore_in):
+    app, dstore_in = app_dstore_in
+    app.apply_to(dstore_in)
+    orig_length = len(app.data_store)
+    app.apply_to(dstore_in)
+    assert len(app.data_store) == orig_length
diff --git a/tests/test_app/test_dist.py b/tests/test_app/test_dist.py
index b8aa6fd46..634662fe4 100644
--- a/tests/test_app/test_dist.py
+++ b/tests/test_app/test_dist.py
@@ -1,8 +1,7 @@
 import itertools
-import pathlib
 
 from tempfile import TemporaryDirectory
-from unittest import TestCase, main
+from unittest import TestCase
 
 import pytest
 
@@ -259,10 +258,6 @@ def test_functions_as_composable(self):
             self.assertTrue(output.exists())
 
 
-if __name__ == "__main__":
-    main()
-
-
 @pytest.mark.parametrize("moltype", ("dna", "rna"))
 def test_jaccard_dist(moltype):
     """jaccard_dist app should work for the simple case
diff --git a/tests/test_app/test_init.py b/tests/test_app/test_init.py
index 63ada9187..0f4db0228 100644
--- a/tests/test_app/test_init.py
+++ b/tests/test_app/test_init.py
@@ -3,7 +3,7 @@
 
 from pathlib import Path
 from tempfile import TemporaryDirectory
-from unittest import TestCase, main
+from unittest import TestCase
 
 import pytest
 
@@ -210,5 +210,13 @@ def test_app_help_signature(capsys, app_name):
         assert got.count("\n") > 1
 
 
-if __name__ == "__main__":
-    main()
+def test_available_apps_filter():
+    """available apps can be filtered by name"""
+    app_name_filter: str = "load"
+    filtered_apps = available_apps(app_name_filter)
+    assert isinstance(filtered_apps, Table)
+    assert len(filtered_apps) > 0
+    # check every returned table row 'name' has filter in it
+    assert sum(app_name_filter in n for n in filtered_apps.columns["name"]) == len(
+        filtered_apps
+    )
diff --git a/tests/test_app/test_io.py b/tests/test_app/test_io.py
index d4a92bcd3..547c66903 100644
--- a/tests/test_app/test_io.py
+++ b/tests/test_app/test_io.py
@@ -183,7 +183,7 @@ def test_load_tabular(tmp_dir):
     outpath = tmp_dir / "delme2.tsv"
     with open(outpath, "w") as out:
         out.write("\t".join(table.header[:1]) + "\n")
-        for row in table.tolist():
+        for row in table.to_list():
             row = "\t".join(map(str, row))
             out.write(row + "\n")
     result = load_table(outpath)
diff --git a/tests/test_app/test_result.py b/tests/test_app/test_result.py
index 8da559044..018f2cb0c 100644
--- a/tests/test_app/test_result.py
+++ b/tests/test_app/test_result.py
@@ -1,6 +1,6 @@
 import pathlib
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3 import make_aligned_seqs, make_table
 from cogent3.app import evo as evo_app
@@ -409,7 +409,3 @@ def test_invalid_setitem(self):
         tr = tabular_result("null")
         with self.assertRaises(TypeError):
             tr["null"] = {0, 23}
-
-
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_app/test_sample.py b/tests/test_app/test_sample.py
index 4bd289826..ffad3aadb 100644
--- a/tests/test_app/test_sample.py
+++ b/tests/test_app/test_sample.py
@@ -1,4 +1,4 @@
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3 import DNA, make_aligned_seqs, make_unaligned_seqs
 from cogent3.app import composable, sample
@@ -543,7 +543,3 @@ def test_take_n_seqs(self):
         take = sample.take_n_seqs(2, random=True, seed=123)
         got = take(seqs1)
         self.assertNotIsInstance(got, NotCompleted)
-
-
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_app/test_sqlite_data_store.py b/tests/test_app/test_sqlite_data_store.py
index 240ce9ca0..bd5ced317 100644
--- a/tests/test_app/test_sqlite_data_store.py
+++ b/tests/test_app/test_sqlite_data_store.py
@@ -576,6 +576,6 @@ def test_summary_not_completed(nc_objects):
     # relying on the fact that all nc_objects have same origin
     # and message, so those columns can be readily interrogated
     summary = dstore.summary_not_completed
-    vals = summary.tolist(columns=["origin", "message", "num"])
+    vals = summary.to_list(columns=["origin", "message", "num"])
     assert len(vals) == 1
     assert vals[0] == ["location", "'message'", 3]
diff --git a/tests/test_app/test_translate.py b/tests/test_app/test_translate.py
index 670d24b70..6a8bc6df5 100644
--- a/tests/test_app/test_translate.py
+++ b/tests/test_app/test_translate.py
@@ -1,4 +1,4 @@
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3 import DNA, make_aligned_seqs, make_unaligned_seqs
 from cogent3.app.composable import NotCompleted
diff --git a/tests/test_app/test_typing.py b/tests/test_app/test_typing.py
index ee6879dee..c07c89987 100644
--- a/tests/test_app/test_typing.py
+++ b/tests/test_app/test_typing.py
@@ -11,8 +11,8 @@
     SerialisableType,
     TabularType,
     UnalignedSeqsType,
+    defined_types,
     get_constraint_names,
-    hints_from_strings,
     type_tree,
 )
 
@@ -68,17 +68,6 @@ def test_get_constraint_names_mixed_serilisable_identifiertype():
     assert got == {"SerialisableType", "IdentifierType", "Alignment", "ArrayAlignment"}
 
 
-def test_hints_from_strings_invalid():
-    """raise an exception if unknown string"""
-    with pytest.raises(ValueError):
-        hints_from_strings("abcde")
-
-
-def test_hints_from_strings():
-    got = hints_from_strings("serialisable", "aligned")
-    assert got == [SerialisableType, AlignedSeqsType]
-
-
 def test_hints_resolved_from_str():
     got = get_constraint_names("DnaSequence")
     assert got == {"DnaSequence"}
@@ -131,3 +120,10 @@ def test_typing_tree_depth(hint, expect):
 def test_type_tree(hint, expect):
     _, t = type_tree(hint)
     assert t == expect, (t, expect)
+
+
+def test_defined_types():
+    types = defined_types()
+    # we are checking a single value which we know has 3 entries
+    # also indexing by the type name
+    assert len(types["TabularType"][0, "includes"].split(",")) == 3
diff --git a/tests/test_cluster/test_UPGMA.py b/tests/test_cluster/test_UPGMA.py
index 7deba9bb5..e399240fc 100644
--- a/tests/test_cluster/test_UPGMA.py
+++ b/tests/test_cluster/test_UPGMA.py
@@ -1,4 +1,4 @@
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy import array
 from numpy.testing import assert_allclose
@@ -162,5 +162,3 @@ def test_inputs_from_dict_array(self):
 
 
 # run if called from command line
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_core/test_alignment.py b/tests/test_core/test_alignment.py
index 6986138b4..665a923c9 100644
--- a/tests/test_core/test_alignment.py
+++ b/tests/test_core/test_alignment.py
@@ -17,6 +17,7 @@
 from cogent3 import (
     get_app,
     get_code,
+    get_moltype,
     load_aligned_seqs,
     load_seq,
     load_unaligned_seqs,
@@ -32,22 +33,10 @@
     ArrayAlignment,
     DataError,
     SequenceCollection,
+    _coerce_to_unaligned_seqs,
+    _construct_unaligned_seq,
     _SequenceCollectionBase,
-    aln_from_array,
-    aln_from_array_aln,
-    aln_from_array_seqs,
-    aln_from_collection,
-    aln_from_empty,
-    aln_from_fasta,
-    aln_from_generic,
     make_gap_filter,
-    seqs_from_aln,
-    seqs_from_array,
-    seqs_from_array_seqs,
-    seqs_from_empty,
-    seqs_from_fasta,
-    seqs_from_generic,
-    seqs_from_kv_pairs,
 )
 from cogent3.core.alphabet import AlphabetError
 from cogent3.core.annotation_db import GffAnnotationDb
@@ -58,110 +47,6 @@
 from cogent3.util.misc import get_object_provenance
 
 
-class alignment_tests(TestCase):
-    """Tests of top-level functions."""
-
-    def test_seqs_from_array(self):
-        """seqs_from_array should return chars, and successive indices."""
-        a = array([[0, 1, 2], [2, 1, 0]])  # three 2-char seqs
-        obs_a, obs_labels = seqs_from_array(a)
-        # note transposition
-        assert_equal(obs_a, [array([0, 2]), array([1, 1]), array([2, 0])])
-        self.assertEqual(obs_labels, None)
-
-    def test_seqs_from_array_seqs(self):
-        """seqs_from_array_seqs should return model seqs + names."""
-        s1 = ArraySequence("ABC", name="a")
-        s2 = ArraySequence("DEF", name="b")
-        obs_a, obs_labels = seqs_from_array_seqs([s1, s2])
-        self.assertEqual(obs_a, [s1, s2])  # seq -> numbers
-        self.assertEqual(obs_labels, ["a", "b"])
-
-    def test_seqs_from_generic(self):
-        """seqs_from_generic should initialize seqs from list of lists, etc."""
-        s1 = "ABC"
-        s2 = "DEF"
-        obs_a, obs_labels = seqs_from_generic([s1, s2])
-        self.assertEqual(obs_a, ["ABC", "DEF"])
-        self.assertEqual(obs_labels, [None, None])
-
-    def test_seqs_from_fasta(self):
-        """seqs_from_fasta should initialize seqs from fasta-format string"""
-        s = ">aa\nAB\nC\n>bb\nDE\nF\n"
-        obs_a, obs_labels = seqs_from_fasta(s)
-        self.assertEqual(obs_a, ["ABC", "DEF"])
-        self.assertEqual(obs_labels, ["aa", "bb"])
-
-    def test_seqs_from_aln(self):
-        """seqs_from_aln should initialize from existing alignment"""
-        c = SequenceCollection(["abc", "def"])
-        obs_a, obs_labels = seqs_from_aln(c)
-        self.assertEqual(obs_a, ["abc", "def"])
-        self.assertEqual(obs_labels, ["seq_0", "seq_1"])
-
-    def test_seqs_from_kv_pairs(self):
-        """seqs_from_kv_pairs should initialize from key-value pairs"""
-        c = [["a", "abc"], ["b", "def"]]
-        obs_a, obs_labels = seqs_from_kv_pairs(c)
-        self.assertEqual(obs_a, ["abc", "def"])
-        self.assertEqual(obs_labels, ["a", "b"])
-
-    def test_seqs_from_empty(self):
-        """seqs_from_empty should always raise ValueError"""
-        self.assertRaises(ValueError, seqs_from_empty, "xyz")
-
-    def test_aln_from_array(self):
-        """aln_from_array should return same array, and successive indices."""
-        a = array([[0, 1, 2], [3, 4, 5]])  # three 2-char seqs
-        obs_a, obs_labels = aln_from_array(a)
-        assert_equal(obs_a, transpose(a))
-        assert_equal(obs_labels, None)
-
-    def test_aln_from_array_seqs(self):
-        """aln_from_array_seqs should initialize aln from sequence objects."""
-        s1 = ArraySequence("ACC", name="a", alphabet=RNA.alphabet)
-        s2 = ArraySequence("GGU", name="b", alphabet=RNA.alphabet)
-        obs_a, obs_labels = aln_from_array_seqs([s1, s2], alphabet=BYTES.alphabet)
-        assert_equal(obs_a, array([[2, 1, 1], [3, 3, 0]], "b"))
-        # seq -> numbers
-        assert_equal(obs_labels, ["a", "b"])
-
-    def test_aln_from_generic(self):
-        """aln_from_generic should initialize aln from list of lists, etc."""
-        s1 = "AAA"
-        s2 = "GGG"
-        obs_a, obs_labels = aln_from_generic(
-            [s1, s2], "b", alphabet=RNA.alphabet
-        )  # specify array type
-        assert_equal(obs_a, array([[2, 2, 2], [3, 3, 3]], "b"))  # str -> chars
-        assert_equal(obs_labels, [None, None])
-
-    def test_aln_from_fasta(self):
-        """aln_from_fasta should initialize aln from fasta-format string"""
-        s = ">aa\nAB\nC\n>bb\nDE\nF\n"
-        obs_a, obs_labels = aln_from_fasta(s.splitlines())
-        assert_equal(obs_a, array(["ABC", "DEF"], "c").view("B"))  # seq -> numbers
-        assert_equal(obs_labels, ["aa", "bb"])
-
-    def test_aln_from_array_aln(self):
-        """aln_from_array_aln should initialize from existing alignment"""
-        a = ArrayAlignment(array([[0, 1, 2], [3, 4, 5]]), conversion_f=aln_from_array)
-        obs_a, obs_labels = aln_from_array_aln(a)
-        assert_equal(obs_a, a.seq_data)
-        assert_equal(obs_labels, a.names)
-
-    def test_aln_from_collection(self):
-        """aln_from_collection should initialize from existing alignment"""
-        a = SequenceCollection(["AAA", "GGG"])
-        obs_a, obs_labels = aln_from_collection(a, alphabet=RNA.alphabet)
-        assert_equal(a.to_fasta(), ">seq_0\nAAA\n>seq_1\nGGG\n")
-        assert_equal(obs_a, array([[2, 2, 2], [3, 3, 3]]))
-
-    def test_aln_from_empty(self):
-        """aln_from_empty should always raise ValueError"""
-        self.assertRaises(ValueError, aln_from_empty, "xyz")
-
-
 class SequenceCollectionBaseTests(object):
     """base class for testing the SequenceCollection object.
 
@@ -229,23 +114,6 @@ def setUp(self):
         self.b = Alignment(["AAA", "AAA"])
         self.c = SequenceCollection(["AAA", "AAA"])
 
-    def test_guess_input_type(self):
-        """SequenceCollection  _guess_input_type should figure out data type correctly"""
-        git = self.a._guess_input_type
-        self.assertEqual(git(self.a), "array_aln")
-        self.assertEqual(git(self.b), "aln")
-        self.assertEqual(git(self.c), "collection")
-        self.assertEqual(git(">ab\nabc"), "fasta")
-        self.assertEqual(git([">ab", "abc"]), "fasta")
-        self.assertEqual(git(["abc", "def"]), "generic")
-        # precedence over generic
-        self.assertEqual(git([[1, 2], [4, 5]]), "kv_pairs")
-        self.assertEqual(git([[1, 2, 3], [4, 5, 6]]), "generic")
-        self.assertEqual(git([ArraySequence("abc")]), "array_seqs")
-        self.assertEqual(git(array([[1, 2, 3], [4, 5, 6]])), "array")
-        self.assertEqual(git({"a": "aca"}), "dict")
-        self.assertEqual(git([]), "empty")
-
     def test_init_aln(self):
         """SequenceCollection should init from existing alignments"""
         exp = self.Class(["AAA", "AAA"])
@@ -258,18 +126,6 @@ def test_init_aln(self):
 
     test_init_aln.__doc__ = Class.__name__ + test_init_aln.__doc__
 
-    def test_init_dict(self):
-        """SequenceCollection init from dict should work as expected"""
-        d = {"a": "AAAAA", "b": "BBBBB"}
-        a = self.Class(d, names=["a", "b"])
-        self.assertEqual(a, d)
-        self.assertEqual(list(a.named_seqs.items()), list(d.items()))
-
-        # from bytes strings
-        a = self.Class({"a": b"AAAAA", "b": b"BBBBB"}, names=["a", "b"])
-        self.assertEqual(a, d)
-        self.assertEqual(list(a.named_seqs.items()), list(d.items()))
-
     def test_names_attribute(self):
         """expected to be a list"""
         seqs = self.Class({"a": b"AAAAA", "b": b"BBBBB"}, names=("a", "b"))
@@ -300,26 +156,6 @@ def test_init_seq(self):
         self.assertEqual(a.names, ["seq_0", "seq_1", "seq_2"])
         self.assertEqual(list(a.seqs), ["AAAAA", "BBBBB", "CCCCC"])
 
-    def test_init_seq_info(self):
-        """SequenceCollection init from seqs w/ info should preserve data"""
-        a = Sequence("AAA", name="a", info={"x": 3})
-        b = Sequence("CCC", name="b", info={"x": 4})
-        c = Sequence("GGG", name="c", info={"x": 5})
-        seqs = [c, b, a]
-        a = self.Class(seqs)
-        self.assertEqual(list(a.names), ["c", "b", "a"])
-        self.assertEqual(list(map(str, a.seqs)), ["GGG", "CCC", "AAA"])
-        if self.Class is not ArrayAlignment:
-            # ArrayAlignment is allowed to strip info objects
-            self.assertEqual([i.info.x for i in a.seqs], [5, 4, 3])
-        # check it still works if constructed from same class
-        b = self.Class(a)
-        self.assertEqual(list(b.names), ["c", "b", "a"])
-        self.assertEqual(list(map(str, b.seqs)), ["GGG", "CCC", "AAA"])
-        if self.Class is not ArrayAlignment:
-            # ArrayAlignment is allowed to strip Info objects
-            self.assertEqual([i.info.x for i in b.seqs], [5, 4, 3])
-
     def test_init_pairs(self):
         """SequenceCollection init from list of (key,val) pairs should work correctly"""
         seqs = [["x", "XXX"], ["b", "BBB"], ["c", "CCC"]]
@@ -331,16 +167,6 @@ def test_init_pairs(self):
         self.assertEqual(a.names, ["x", "b", "c"])
         self.assertEqual(list(a.seqs), ["XXX", "BBB", "CCC"])
 
-    def test_init_duplicate_keys(self):
-        """SequenceCollection init from (key, val) pairs should fail on dup. keys"""
-        seqs = [["x", "XXX"], ["b", "BBB"], ["x", "CCC"], ["d", "DDD"], ["a", "AAA"]]
-        self.assertRaises(ValueError, self.Class, seqs)
-        aln = self.Class(seqs, remove_duplicate_names=True)
-        self.assertEqual(
-            str(self.Class(seqs, remove_duplicate_names=True)),
-            ">x\nXXX\n>b\nBBB\n>d\nDDD\n>a\nAAA\n",
-        )
-
     def test_init_ordered(self):
         """SequenceCollection should iterate over seqs correctly even if ordered"""
         first = self.ordered1
@@ -369,25 +195,6 @@ def test_init_ambig(self):
         aln = self.Class(["A-A", "CC-"], moltype=DNA)
         aln = self.Class(["A?A", "CC-"], moltype=DNA)
 
-    def test_aln_from_fasta_parser(self):
-        """aln_from_fasta_parser should init from iterator"""
-        s = ">aa\nAC\n>bb\nAA\n>c\nGG\n".splitlines()
-        p = MinimalFastaParser(s)
-        aln = self.Class(p, moltype=DNA)
-        self.assertEqual(aln.named_seqs["aa"], "AC")
-        self.assertEqual(aln.to_fasta(), ">aa\nAC\n>bb\nAA\n>c\nGG\n")
-        s2_ORIG = ">x\nCA\n>b\nAA\n>>xx\nGG"
-        s2 = ">aa\nAC\n>bb\nAA\n>c\nGG\n"
-        d = ArrayAlignment(MinimalFastaParser(s2.splitlines()))
-        d.to_fasta()
-        self.assertEqual(d.to_fasta(), aln.to_fasta())
-
-    def test_aln_from_fasta(self):
-        """SequenceCollection should init from fasta-format string"""
-        s = ">aa\nAC\n>bb\nAA\n>c\nGG\n"
-        aln = self.Class(s)
-        self.assertEqual(aln.to_fasta(), s)
-
     def test_seq_len_get(self):
         """SequenceCollection seq_len should return length of longest seq"""
         self.assertEqual(self.one_seq.seq_len, 5)
@@ -601,138 +408,6 @@ def test_get_identical_sets(self):
         got = frozenset(frozenset(s) for s in got)
         self.assertEqual(got, expect)
 
-    def test_get_similar(self):
-        """SequenceCollection get_similar should get all sequences close to target seq"""
-        aln = self.many
-        RnaSequence("GGGGGGGGGG")
-        RnaSequence("----------")
-        # test min and max similarity ranges
-        result = aln.get_similar(
-            aln.named_seqs["a"], min_similarity=0.4, max_similarity=0.7
-        )
-        for seq in "cefg":
-            self.assertIn(seq, result.named_seqs)
-            self.assertEqual(result.named_seqs[seq], aln.named_seqs[seq])
-        self.assertEqual(len(result.named_seqs), 4)
-
-        result = aln.get_similar(
-            aln.named_seqs["a"], min_similarity=0.95, max_similarity=1
-        )
-        for seq in "a":
-            self.assertIn(seq, result.named_seqs)
-            self.assertEqual(result.named_seqs[seq], aln.named_seqs[seq])
-        self.assertEqual(len(result.named_seqs), 1)
-
-        result = aln.get_similar(
-            aln.named_seqs["a"], min_similarity=0.75, max_similarity=0.85
-        )
-        for seq in "bd":
-            self.assertIn(seq, result.named_seqs)
-            self.assertEqual(result.named_seqs[seq], aln.named_seqs[seq])
-        self.assertEqual(len(result.named_seqs), 2)
-
-        result = aln.get_similar(
-            aln.named_seqs["a"], min_similarity=0, max_similarity=0.2
-        )
-        self.assertEqual(result, {})
-
-        # test some sequence transformations
-        def transform(s):
-            return s[1:4]
-
-        result = aln.get_similar(
-            aln.named_seqs["a"], min_similarity=0.5, transform=transform
-        )
-        for seq in "abdfg":
-            self.assertIn(seq, result.named_seqs)
-            self.assertEqual(result.named_seqs[seq], aln.named_seqs[seq])
-        self.assertEqual(len(result.named_seqs), 5)
-
-        def transform(s):
-            return s[-3:]
-
-        result = aln.get_similar(
-            aln.named_seqs["a"], min_similarity=0.5, transform=transform
-        )
-        for seq in "abcde":
-            self.assertIn(seq, result.named_seqs)
-            self.assertEqual(result.named_seqs[seq], aln.named_seqs[seq])
-        self.assertEqual(len(result.named_seqs), 5)
-
-        # test a different distance metric
-        def metric(x, y):
-            return str(x).count("G") + str(y).count("G")
-
-        result = aln.get_similar(
-            aln.named_seqs["a"], min_similarity=5, max_similarity=10, metric=metric
-        )
-        for seq in "ef":
-            self.assertIn(seq, result.named_seqs)
-            self.assertEqual(result.named_seqs[seq], aln.named_seqs[seq])
-        self.assertEqual(len(result.named_seqs), 2)
-
-        # test the combination of a transform and a distance metric
-        aln = self.Class(
-            dict(enumerate(map(RnaSequence, ["GA-GU", "A-GAC", "GG-GG"]))), moltype=RNA
-        )
-
-        def transform(s):
-            return RnaSequence(str(s).replace("G", "A").replace("U", "C"))
-
-        metric = RnaSequence.frac_same_non_gaps
-
-        def null_transform(s):
-            return RnaSequence(str(s))
-
-        # first, do it without the transformation
-        try:
-            result = aln.get_similar(
-                aln.named_seqs[0], min_similarity=0.5, metric=metric
-            )
-        except TypeError:  # need to coerce to RNA seq w/ null_transform
-            result = aln.get_similar(
-                aln.named_seqs[0],
-                min_similarity=0.5,
-                metric=metric,
-                transform=null_transform,
-            )
-        for seq in [0, 2]:
-            self.assertIn(seq, result.named_seqs)
-            self.assertEqual(result.named_seqs[seq], aln.named_seqs[seq])
-        self.assertEqual(len(result.named_seqs), 2)
-        # repeat with higher similarity
-        try:
-            result = aln.get_similar(
-                aln.named_seqs[0], min_similarity=0.8, metric=metric
-            )
-        except TypeError:  # need to coerce to RNA
-            result = aln.get_similar(
-                aln.named_seqs[0],
-                min_similarity=0.8,
-                metric=metric,
-                transform=null_transform,
-            )
-        for seq in [0]:
-            self.assertIn(seq, result.named_seqs)
-            self.assertEqual(result.named_seqs[seq], aln.named_seqs[seq])
-        self.assertEqual(len(result.named_seqs), 1)
-        # then, verify that the transform changes the results
-        result = aln.get_similar(
-            aln.named_seqs[0], min_similarity=0.5, metric=metric, transform=transform
-        )
-        for seq in [0, 1, 2]:
-            self.assertIn(seq, result.named_seqs)
-            self.assertEqual(result.named_seqs[seq], aln.named_seqs[seq])
-        self.assertEqual(len(result.named_seqs), 3)
-
-        result = aln.get_similar(
-            aln.named_seqs[0], min_similarity=0.8, metric=metric, transform=transform
-        )
-        for seq in [0, 1]:
-            self.assertIn(seq, result.named_seqs)
-            self.assertEqual(result.named_seqs[seq], aln.named_seqs[seq])
-        self.assertEqual(len(result.named_seqs), 2)
-
     def test_is_ragged(self):
         """SequenceCollection is_ragged should return true if ragged alignment"""
         assert not self.identical.is_ragged()
@@ -759,11 +434,6 @@ def test_to_fasta(self):
         aln = self.Class(["AAA", "CCC"])
         self.assertEqual(aln.to_fasta(), ">seq_0\nAAA\n>seq_1\nCCC\n")
 
-        # NOTE THE FOLLOWING SURPRISING BEHAVIOR BECAUSE OF THE TWO-ITEM
-        # SEQUENCE RULE:
-        aln = self.Class(["AA", "CC"])
-        self.assertEqual(aln.to_fasta(), ">A\nA\n>C\nC\n")
-
     def test_to_nexus(self):
         """SequenceCollection should return correct Nexus string format"""
         align_norm = self.Class(
@@ -799,16 +469,6 @@ def test_num_seqs(self):
         aln = self.Class({"seq1": "ACGU", "seq2": "CGUA", "seq3": "CCGU"})
         self.assertEqual(aln.num_seqs, 3)
 
-    def test_add(self):
-        """__add__ should concatenate sequence data, by name"""
-        align1 = self.Class({"a": "AAAA", "b": "TTTT", "c": "CCCC"})
-        align2 = self.Class({"a": "GGGG", "b": "----", "c": "NNNN"})
-        align = align1 + align2
-        concatdict = align.to_dict()
-        self.assertEqual(
-            concatdict, {"a": "AAAAGGGG", "b": "TTTT----", "c": "CCCCNNNN"}
-        )
-
     def test_add_info(self):
         """__add__ should preserve info attribute"""
         align1 = self.Class(
@@ -1054,30 +714,6 @@ def test_pad_seqs(self):
         # assertRaises error when pad_length is less than max seq length
         self.assertRaises(ValueError, self.ragged_padded.pad_seqs, 5)
 
-    def test_to_moltype(self):
-        """correctly convert to specified moltype"""
-        data = {"seq1": "ACGTACGTA", "seq2": "ACCGAA---", "seq3": "ACGTACGTT"}
-        seqs = self.Class(data=data)
-        dna = seqs.to_moltype("dna")
-        self.assertEqual(dna.moltype.label, "dna")
-        rc = dna.rc().to_dict()
-        expect = {"seq1": "TACGTACGT", "seq2": "---TTCGGT", "seq3": "AACGTACGT"}
-        self.assertEqual(rc, expect)
-
-        data = {"seq1": "TTTTTTAAAA", "seq2": "AAAATTTTTT", "seq3": "AATTTTTAAA"}
-        seqs = self.Class(data=data)
-        rna = seqs.to_moltype("rna")
-        self.assertEqual(rna.moltype.label, "rna")
-        rc = rna.rc().to_dict()
-        expect = {"seq1": "UUUUAAAAAA", "seq2": "AAAAAAUUUU", "seq3": "UUUAAAAAUU"}
-        self.assertEqual(rc, expect)
-        # calling with a null object should raise an exception
-        with self.assertRaises(ValueError):
-            seqs.to_moltype(None)
-
-        with self.assertRaises(ValueError):
-            seqs.to_moltype("")
-
     def test_to_moltype_info(self):
         """correctly convert to specified moltype"""
         data = {"seq1": "ACGTACGTA", "seq2": "ACCGAA---", "seq3": "ACGTACGTT"}
@@ -1401,34 +1037,6 @@ def test_alignment_quality(self):
         )
         assert_allclose(got, expect)
 
-    def make_and_filter(self, raw, expected, motif_length, drop_remainder):
-        # a simple filter func
-        aln = self.Class(raw, info={"key": "value"})
-        func = _make_filter_func(aln)
-        result = aln.filtered(
-            func,
-            motif_length=motif_length,
-            log_warnings=False,
-            drop_remainder=drop_remainder,
-        )
-        self.assertEqual(result.to_dict(), expected)
-        self.assertEqual(result.info["key"], "value")
-
-    def test_filtered(self):
-        """filtered should return new alignment with positions consistent with
-        provided callback function"""
-        # a simple filter option
-        raw = {"a": "ACGACGACG", "b": "CCC---CCC", "c": "AAAA--AAA"}
-        self.make_and_filter(
-            raw, {"a": "ACGACG", "b": "CCCCCC", "c": "AAAAAA"}, 1, True
-        )
-        # check with motif_length = 2
-        self.make_and_filter(raw, {"a": "ACAC", "b": "CCCC", "c": "AAAA"}, 2, True)
-        # check with motif_length = 3
-        self.make_and_filter(
-            raw, {"a": "ACGACG", "b": "CCCCCC", "c": "AAAAAA"}, 3, True
-        )
-
     def test_filter_drop_remainder(self):
         """filter allows dropping"""
         raw = {"a": "ACGACGACG", "b": "CCC---CCC", "c": "AAAA--AAA"}
@@ -1454,20 +1062,6 @@ def test_positions(self):
             list(map(list, ["AAA", "AAA", "AAA", "A-A", "A--", "A--"])),
         )
 
-    def test_iter_positions(self):
-        # """SequenceCollection iter_positions() method should support reordering of #cols"""
-        r = self.Class(self.ragged_padded.named_seqs, names=["c", "b"])
-        self.assertEqual(
-            list(r.iter_positions(pos_order=[5, 1, 3])),
-            list(map(list, ["--", "AA", "A-"])),
-        )
-        # reorder names
-        r = self.Class(self.ragged_padded.named_seqs, names=["a", "b", "c"])
-        cols = list(r.iter_positions())
-        self.assertEqual(
-            cols, list(map(list, ["AAA", "AAA", "AAA", "A-A", "A--", "A--"]))
-        )
-
     def test_take_positions(self):
         """SequenceCollection take_positions should return new alignment w/ specified pos"""
         self.assertEqual(
@@ -1792,22 +1386,6 @@ def test_uncertainties(self):
         obs = aln.entropy_per_pos()
         assert_allclose(obs, [0, 0, 0])
 
-    def test_sample(self):
-        """Alignment.sample should permute alignment by default"""
-        alignment = self.Class({"seq1": "ABCDEFGHIJKLMNOP", "seq2": "ABCDEFGHIJKLMNOP"})
-        # effectively permute columns, preserving length
-        shuffled = alignment.sample()
-        self.assertEqual(len(shuffled), len(alignment))
-        # ensure length correct
-        sample = alignment.sample(10)
-        self.assertEqual(len(sample), 10)
-        # test columns alignment preserved
-        seqs = list(sample.to_dict().values())
-        self.assertEqual(seqs[0], seqs[1])
-        # ensure each char occurs once as sampling without replacement
-        for char in seqs[0]:
-            self.assertEqual(seqs[0].count(char), 1)
-
     def test_sample_info(self):
         """Alignment.sample should preserver info attribute"""
         alignment = self.Class(
@@ -2033,42 +1611,6 @@ def test_to_protein_info(self):
         dna = aln.to_dna()
         self.assertEqual(dna.info["key"], "value")
 
-    def test_replace_seqs(self):
-        """replace_seqs should replace 1-letter w/ 3-letter seqs"""
-        a = self.Class({"seq1": "ACGU", "seq2": "C-UA", "seq3": "C---"})
-        seqs = {"seq1": "AAACCCGGGUUU", "seq2": "CCCUUUAAA", "seq3": "CCC"}
-        result = a.replace_seqs(seqs)  # default behaviour
-        self.assertEqual(
-            result.to_fasta(),
-            ">seq1\nAAACCCGGGUUU\n>seq2\nCCC---UUUAAA\n>seq3\nCCC---------\n",
-        )
-
-        result = a.replace_seqs(seqs, aa_to_codon=True)  # default behaviour
-        self.assertEqual(
-            result.to_fasta(),
-            ">seq1\nAAACCCGGGUUU\n>seq2\nCCC---UUUAAA\n>seq3\nCCC---------\n",
-        )
-
-        # should correctly gap the same sequences with same length
-        result = a.replace_seqs(a.degap(), aa_to_codon=False)  # default behaviour
-        self.assertEqual(
-            result.to_dict(), {"seq1": "ACGU", "seq2": "C-UA", "seq3": "C---"}
-        )
-
-        # should fail when not same length if aa_to_codon is False
-        new = SequenceCollection(
-            [(n, s.replace("-", "")) for n, s in list(a[:3].to_dict().items())]
-        )
-        self.assertRaises(ValueError, a.replace_seqs, new, aa_to_codon=False)
-
-        # check the gaps are changed
-        aln1 = self.Class(data={"a": "AC-CT", "b": "ACGCT"})
-        aln2 = self.Class(data={"a": "ACC-T", "b": "ACGCT"})
-
-        result = aln1.replace_seqs(aln2, aa_to_codon=False)
-        self.assertTrue(id(aln1) != id(aln2))
-        self.assertEqual(aln1.to_dict(), result.to_dict())
-
     def test_replace_seqs_info(self):
         """replace_seqs should preserve info attribute"""
         a = self.Class(
@@ -2103,32 +1645,6 @@ def test_counts(self):
         for k, v in expect.items():
             self.assertEqual(got[k], v)
 
-    def test_counts_per_seq(self):
-        """SequenceCollection.counts_per_seq handles motif length, allow_gaps etc.."""
-        data = {"a": "AAAA??????", "b": "CCCGGG--NN", "c": "CCGGTTCCAA"}
-        coll = self.Class(data=data, moltype="dna")
-        mtype = coll.moltype
-        got = coll.counts_per_seq()
-        self.assertEqual(got["a", "A"], 4)
-        self.assertEqual(len(got.motifs), len(mtype.alphabet))
-        got = coll.counts_per_seq(include_ambiguity=True, allow_gap=True)
-        # N, -, ? are the additional states
-        self.assertEqual(len(got.motifs), 7)
-        expect = {"-": 2, "?": 0, "A": 0, "C": 3, "G": 3, "N": 2, "T": 0}
-        b = got["b"].to_dict()
-        for k in expect:
-            self.assertEqual(b[k], expect[k])
-
-        got = coll.counts_per_seq(motif_length=2)
-        self.assertEqual(len(got.motifs), len(mtype.alphabet) ** 2)
-        self.assertEqual(got["a", "AA"], 2)
-        self.assertEqual(got["b", "GG"], 1)
-        got = coll.counts_per_seq(exclude_unobserved=True)
-        expect = {"C": 4, "G": 2, "T": 2, "A": 2}
-        c = got["c"].to_dict()
-        for k in expect:
-            self.assertEqual(c[k], expect[k])
-
     def test_counts_per_pos(self):
         """correctly count motifs"""
         exp = array(
@@ -2207,15 +1723,6 @@ def test_counts_per_pos_default_moltype(self):
         self.assertTrue("-" not in found_motifs)
         self.assertEqual(lengths, {2})
 
-    def test_seq_entropy_just_gaps(self):
-        """ArrayAlignment get_seq_entropy should get entropy of each seq"""
-        a = self.Class(dict(a="A---", b="----"), moltype=DNA)
-        entropy = a.entropy_per_seq()
-        assert_allclose(entropy, [0, numpy.nan])
-        a = self.Class(dict(a="----", b="----"), moltype=DNA)
-        entropy = a.entropy_per_seq()
-        self.assertIs(entropy, None)
-
     def test_entropy_per_pos_just_gaps(self):
         """pos with just gaps have nan"""
         a = self.Class(dict(a="A---", b="C---", c="C---"), moltype=DNA)
@@ -2275,16 +1782,6 @@ def test_get_gapped_seq(self):
         aln = self.Class({"seq1": "--TTT?", "seq2": "GATC??"})
         self.assertEqual(str(aln.get_gapped_seq("seq1")), "--TTT?")
 
-    def test_count_gaps_per_pos(self):
-        """correctly compute the number of gaps"""
-        data = {"a": "AAAA---GGT", "b": "CCC--GG?GT"}
-        aln = self.Class(data=data, moltype=DNA)
-        # per position
-        got = aln.count_gaps_per_pos(include_ambiguity=False)
-        assert_equal(got.array, [0, 0, 0, 1, 2, 1, 1, 0, 0, 0])
-        got = aln.count_gaps_per_pos(include_ambiguity=True)
-        assert_equal(got.array, [0, 0, 0, 1, 2, 1, 1, 1, 0, 0])
-
     def test_count_gaps_per_seq(self):
         """correctly compute the number of gaps"""
         data = {"a": "AAAA---GGT", "b": "CCC--GG?GT"}
@@ -2559,81 +2056,6 @@ def test_get_degapped_relative_to_info(self):
         gdrt = aln.get_degapped_relative_to("name1")
         self.assertEqual(gdrt.info["key"], "foo")
 
-    def test_add_from_ref_aln(self):
-        """should add or insert seqs based on align to reference"""
-        aln1 = self.Class(
-            [
-                ["name1", "-AC-DEFGHI---"],
-                ["name2", "XXXXXX--XXXXX"],
-                ["name3", "YYYY-YYYYYYYY"],
-            ]
-        )
-
-        aln2 = self.Class(
-            [
-                ["name1", "ACDEFGHI"],
-                ["name4", "KL--MNPR"],
-                ["name5", "KLACMNPR"],
-                ["name6", "KL--MNPR"],
-            ]
-        )
-
-        aligned_to_ref_out_aln_inserted = self.Class(
-            [
-                ["name1", "-AC-DEFGHI---"],
-                ["name4", "-KL---MNPR---"],
-                ["name5", "-KL-ACMNPR---"],
-                ["name6", "-KL---MNPR---"],
-                ["name2", "XXXXXX--XXXXX"],
-                ["name3", "YYYY-YYYYYYYY"],
-            ]
-        )
-
-        aln2_wrong_refseq = self.Class((("name1", "ACDXFGHI"), ("name4", "KL--MNPR")))
-
-        aln2_wrong_refseq_name = self.Class(
-            [["nameY", "ACDEFGHI"], ["name4", "KL--MNPR"]]
-        )
-
-        aln2_different_aln_class = ArrayAlignment(
-            [["name1", "ACDEFGHI"], ["name4", "KL--MNPR"]]
-        )
-
-        aln2_list = [["name1", "ACDEFGHI"], ["name4", "KL--MNPR"]]
-
-        aligned_to_ref_out_aln = self.Class(
-            [
-                ["name1", "-AC-DEFGHI---"],
-                ["name2", "XXXXXX--XXXXX"],
-                ["name3", "YYYY-YYYYYYYY"],
-                ["name4", "-KL---MNPR---"],
-            ]
-        )
-
-        out_aln = aln1.add_from_ref_aln(aln2, after_name="name1")
-        self.assertEqual(
-            str(aligned_to_ref_out_aln_inserted), str(out_aln)
-        )  # test insert_after
-
-        out_aln = aln1.add_from_ref_aln(aln2, before_name="name2")
-        self.assertEqual(aligned_to_ref_out_aln_inserted, out_aln)  # test insert_before
-
-        self.assertRaises(
-            ValueError, aln1.add_from_ref_aln, aln2_wrong_refseq_name
-        )  # test wrong_refseq_name
-
-        aln = aln1.add_from_ref_aln(aln2_different_aln_class)
-        self.assertEqual(
-            aligned_to_ref_out_aln, aln
-        )  # test_align_to_refseq_different_aln_class
-
-        aln = aln1.add_from_ref_aln(aln2_list)
-        self.assertEqual(aligned_to_ref_out_aln, aln)  # test from_list
-
-        self.assertRaises(
-            ValueError, aln1.add_from_ref_aln, aln2_wrong_refseq
-        )  # test wrong_refseq
-
     def test_deepcopy2(self):
         """ "Aligned.deepcopy correctly handles gapped sequences"""
         seqs = self.Class(
@@ -2693,41 +2115,21 @@ class ArrayAlignmentSpecificTests(TestCase):
 
     def setUp(self):
         """Define some standard alignments."""
-        self.a = ArrayAlignment(
-            array([[0, 1, 2], [3, 4, 5]]), conversion_f=aln_from_array
-        )
         self.a2 = ArrayAlignment(["ABC", "DEF"], names=["x", "y"])
         seqs = []
         for s in ["abaa", "abbb"]:
             seqs.append(AB.make_seq(s, preserve_case=True))
-        self.a = ArrayAlignment(seqs, alphabet=AB.alphabet)
+        self.a = ArrayAlignment(seqs, moltype=AB)
         self.b = Alignment(["ABC", "DEF"])
         self.c = SequenceCollection(["ABC", "DEF"])
 
     def test_init(self):
         """ArrayAlignment init should work from a sequence"""
-        a = ArrayAlignment(array([[0, 1, 2], [3, 4, 5]]), conversion_f=aln_from_array)
+        a = ArrayAlignment(array([[0, 1, 2], [3, 4, 5]]).T)
         assert_equal(a.seq_data, array([[0, 3], [1, 4], [2, 5]], "B"))
         assert_equal(a.array_positions, array([[0, 1, 2], [3, 4, 5]], "B"))
         assert_equal(a.names, ["seq_0", "seq_1", "seq_2"])
 
-    def test_guess_input_type(self):
-        """ArrayAlignment _guess_input_type should figure out data type correctly"""
-        git = self.a._guess_input_type
-        self.assertEqual(git(self.a), "array_aln")
-        self.assertEqual(git(self.b), "aln")
-        self.assertEqual(git(self.c), "collection")
-        self.assertEqual(git(">ab\nabc"), "fasta")
-        self.assertEqual(git([">ab", "abc"]), "fasta")
-        self.assertEqual(git(["abc", "def"]), "generic")
-        # precedence over generic
-        self.assertEqual(git([[1, 2], [4, 5]]), "kv_pairs")
-        self.assertEqual(git([[1, 2, 3], [4, 5, 6]]), "generic")
-        self.assertEqual(git([ArraySequence("abc")]), "array_seqs")
-        self.assertEqual(git(array([[1, 2, 3], [4, 5, 6]])), "array")
-        self.assertEqual(git({"a": "aca"}), "dict")
-        self.assertEqual(git([]), "empty")
-
     def test_init_seqs(self):
         """ArrayAlignment init should work from ArraySequence objects."""
         s = list(map(ArraySequence, ["abc", "def"]))
@@ -2755,43 +2157,6 @@ def test_init_dict(self):
         assert_equal(a.seq_data, array(["AAACCC", "GCGCGC"], "c").view("B"))
         self.assertEqual(tuple(a.names), ("abc", "xyz"))
 
-    def test_init_empty(self):
-        """ArrayAlignment init should fail if empty."""
-        self.assertRaises(TypeError, ArrayAlignment)
-        self.assertRaises(ValueError, ArrayAlignment, 3)
-
-    def test_get_alphabet_and_moltype(self):
-        """ArrayAlignment should figure out correct alphabet and moltype"""
-        s1 = "A"
-        s2 = RNA.make_seq("AA")
-
-        d = ArrayAlignment(s1)
-        self.assertIs(d.moltype, BYTES)
-        self.assertIs(d.alphabet, BYTES.alphabet)
-
-        d = ArrayAlignment(s1, moltype=RNA)
-        self.assertIs(d.moltype, RNA)
-        self.assertIs(d.alphabet, RNA.alphabets.degen_gapped)
-
-        d = ArrayAlignment(s1, alphabet=RNA.alphabet)
-        self.assertIs(d.moltype, RNA)
-        self.assertIs(d.alphabet, RNA.alphabet)
-
-        d = ArrayAlignment(s2)
-        self.assertIs(d.moltype, RNA)
-        self.assertIs(d.alphabet, RNA.alphabets.degen_gapped)
-
-        d = ArrayAlignment(s2, moltype=DNA)
-        self.assertIs(d.moltype, DNA)
-        self.assertIs(d.alphabet, DNA.alphabets.degen_gapped)
-        # checks for containers
-        d = ArrayAlignment([s2])
-        self.assertIs(d.moltype, RNA)
-        d = ArrayAlignment({"x": s2})
-        self.assertIs(d.moltype, RNA)
-        d = ArrayAlignment(set([s2]))
-        self.assertIs(d.moltype, RNA)
-
     def test_iter(self):
         """ArrayAlignment iter should iterate over positions"""
         result = list(iter(self.a2))
@@ -2810,40 +2175,40 @@ def test_getitem(self):
 
     def test_get_sub_alignment(self):
         """ArrayAlignment get_sub_alignment should get requested part of alignment"""
-        a = ArrayAlignment(">x ABCE >y FGHI >z JKLM".split())
+        a = ArrayAlignment({"x": "ABCE", "y": "FGHI", "z": "JKLM"})
         # passing in positions should keep all seqs, but just selected
         # positions
-        b = ArrayAlignment(">x BC >y GH >z KL".split())
+        b = ArrayAlignment({"x": "BC", "y": "GH", "z": "KL"})
         a_1 = a.get_sub_alignment(pos=[1, 2])
         self.assertEqual(a_1.names, b.names)
 
         self.assertEqual(a_1.seqs, b.seqs)
-        # ...and with invert_pos, should keep all except the positions passed in
-        a_2 = a.get_sub_alignment(pos=[0, 3], invert_pos=True)
+        # ...and with negate_pos, should keep all except the positions passed in
+        a_2 = a.get_sub_alignment(pos=[0, 3], negate_pos=True)
         self.assertEqual(a_2.seqs, b.seqs)
         self.assertEqual(a_2.names, b.names)
         # passing in seqs should keep all positions, but just selected seqs
-        c = ArrayAlignment(">x ABCE >z JKLM".split())
+        c = ArrayAlignment({"x": "ABCE", "z": "JKLM"})
         a_3 = a.get_sub_alignment(seqs=[0, 2])
         self.assertEqual(a_3.seqs, c.seqs)
         # check that labels were updates as well...
         self.assertEqual(a_3.names, c.names)
-        # ...and should work with invert_seqs to exclude just selected seqs
-        a_4 = a.get_sub_alignment(seqs=[1], invert_seqs=True)
+        # ...and should work with negate_seqs to exclude just selected seqs
+        a_4 = a.get_sub_alignment(seqs=[1], negate_seqs=True)
         self.assertEqual(a_4.seqs, c.seqs)
         self.assertEqual(a_4.names, c.names)
         # should be able to do both seqs and positions simultaneously
-        d = ArrayAlignment(">x BC >z KL".split())
+        d = ArrayAlignment({"x": "BC", "z": "KL"})
         a_5 = a.get_sub_alignment(seqs=[0, 2], pos=[1, 2])
         self.assertEqual(a_5.seqs, d.seqs)
         self.assertEqual(a_5.names, d.names)
 
     def test_get_sub_alignment_info(self):
         """ArrayAlignment get_sub_alignment should preserve info attribute"""
-        a = ArrayAlignment(">x ABCE >y FGHI >z JKLM".split(), info={"key": "foo"})
+        a = ArrayAlignment({"x": "ABCE", "y": "FGHI", "z": "JKLM"}, info={"key": "foo"})
         # passing in positions should keep all seqs, but just selected
         # positions
-        b = ArrayAlignment(">x BC >y GH >z KL".split(), info={"key": "bar"})
+        b = ArrayAlignment({"x": "BC", "y": "GH", "z": "KL"}, info={"key": "bar"})
         a_1 = a.get_sub_alignment(pos=[1, 2])
         self.assertEqual(a_1.names, b.names)
         self.assertEqual(a.info["key"], "foo")
@@ -2855,14 +2220,6 @@ def test_str(self):
         self.a2.names[-1] = "yyy"
         self.assertEqual(str(self.a2), ">x\nABC\n>yyy\nDEF\n")
 
-    def test_counts_per_seq(self):
-        """ArrayAlignment counts_per_seq should return motif counts each seq"""
-        a = self.a
-        f = a.counts_per_seq()
-        assert_equal(f.array, array([[3, 1], [1, 3]]))
-        f = a.counts_per_seq(motif_length=2, exclude_unobserved=True)
-        assert_equal(f.array, array([[1, 1, 0], [0, 1, 1]]))
-
     def test_entropy_per_pos(self):
         """entropy_per_pos should get entropy of each pos"""
         a = self.a
@@ -2915,14 +2272,6 @@ def test_entropy_per_pos(self):
         ]
         assert_allclose(f, e)
 
-    def test_coevolution_segments(self):
-        """specifying coordinate segments produces matrix with just those"""
-        aln = load_aligned_seqs("data/brca1.fasta", moltype="dna")
-        aln = aln.take_seqs(aln.names[:20])
-        aln = aln.no_degenerates()[:20]
-        coevo = aln.coevolution(segments=[(4, 6), (11, 13)], show_progress=False)
-        self.assertEqual(coevo.template.names[0], [4, 5, 11, 12])
-
 
 class IntegrationTests(TestCase):
     """Test for integration between regular and model seqs and alns"""
@@ -2937,9 +2286,9 @@ def setUp(self):
     def test_model_to_model(self):
         """Model seq should work with dense alignment"""
         a = ArrayAlignment([self.m1, self.m2])
-        self.assertEqual(str(a), ">xx\nAAA\n>yy\nCCC\n")
+        self.assertEqual(a.to_dict(), {"xx": "AAA", "yy": "CCC"})
         a = ArrayAlignment([self.m1, self.m2], moltype=DNA)
-        self.assertEqual(str(a), ">xx\nAAA\n>yy\nCCC\n")
+        self.assertEqual(a.to_dict(), {"xx": "AAA", "yy": "CCC"})
         self.assertEqual(self.m1.name, "xx")
 
     def test_regular_to_model(self):
@@ -3414,7 +2763,7 @@ def test_dotplot_annotated(cls):
 def test_get_seq_entropy(cls):
     """ArrayAlignment get_seq_entropy should get entropy of each seq"""
     seqs = [AB.make_seq(s, preserve_case=True) for s in ["abab", "bbbb", "abbb"]]
-    a = cls(seqs, alphabet=AB.alphabet)
+    a = cls(seqs, moltype=AB)
     entropy = a.entropy_per_seq()
     e = 0.81127812445913283  # sum(p log_2 p) for p = 0.25, 0.75
     assert_allclose(entropy, array([1, 0, e]))
@@ -3709,3 +3058,474 @@ def test_get_translation_incomplete(cls):
     assert got.to_dict() == {"seq1": "D?", "seq2": "XS"}
     with pytest.raises(AlphabetError):
         _ = alignment.get_translation(incomplete_ok=False)
+
+
+@pytest.mark.parametrize("name", ("s1", "s2", "s3"))
+def test_get_seq_with_sliced_aln(name):
+    seqs = {
+        "s1": "GTTGAAGTAGTAGAAGTTCCAAATAATGAA",
+        "s2": "GTG------GTAGAAGTTCCAAATAATGAA",
+        "s3": "GCTGAAGTAGTGGAAGTTGCAAAT---GAA",
+    }
+    aln = make_aligned_seqs(data=seqs, moltype="dna", array_align=False)
+    start, stop = 1, 5
+    a1 = aln[start:stop]
+
+    seq = a1.get_seq(name)
+    assert isinstance(seq, Sequence), seq
+
+    got = str(seq)
+    expect = seqs[name][start:stop].replace("-", "")
+    assert got == expect, (got, expect)
+
+
+@pytest.mark.parametrize("name", ("s1", "s2", "s3"))
+def test_get_seq_with_sliced_rced_aln(name):
+    seqs = {
+        "s1": "GTTGAAGTAGTAGAAGTTCCAAATAATGAA",
+        "s2": "GTG------GTAGAAGTTCCAAATAATGAA",
+        "s3": "GCTGAAGTAGTGGAAGTTGCAAAT---GAA",
+    }
+    aln = make_aligned_seqs(data=seqs, moltype="dna", array_align=False)
+    start, stop = 1, 5
+    a1 = aln[start:stop]
+    a1 = a1.rc()
+    got = str(a1.get_seq(name))
+
+    dna = get_moltype("dna")
+    expect = dna.complement(seqs[name][start:stop].replace("-", ""))[::-1]
+    assert got == expect, (got, expect)
+
+
+@pytest.mark.parametrize("name", ("s1", "s2", "s3"))
+def test_get_seq_with_sliced_aln_multiple_spans(name):
+    seqs = {  # the sliced seq has:
+        "s1": "GTTGA--TAGTAGAAGTTCCAAATAATGAA",  # span gap span
+        "s2": "G----TT------AAGTTCCAAATAATGAA",  # gap span gap
+        "s3": "G--GA--TA--GGAAGTTGCAAAT---GAA",  # gap span gap span gap
+    }
+    aln = make_aligned_seqs(data=seqs, moltype="dna", array_align=False)
+    start, stop = 1, 10
+    a1 = aln[start:stop]
+    seq = a1.get_seq(name)
+    assert isinstance(seq, Sequence), seq
+
+    expect = seqs[name][start:stop].replace("-", "")
+    got = str(seq)
+    assert got == expect, (got, expect)
+
+
+@pytest.mark.parametrize("name", ("s1", "s2", "s3"))
+def test_get_seq_with_sliced_rced_aln_multiple_spans(name):
+    seqs = {  # the sliced seq has:
+        "s1": "GTTGA--TAGTAGAAGTTCCAAATAATGAA",  # span gap span
+        "s2": "G----TT------AAGTTCCAAATAATGAA",  # gap span gap
+        "s3": "G--GA--TA--GGAAGTTGCAAAT---GAA",  # gap span gap span gap
+    }
+    aln = make_aligned_seqs(data=seqs, moltype="dna", array_align=False)
+    start, stop = 1, 10
+    a1 = aln[start:stop].rc()
+    got = str(a1.get_seq(name))
+    dna = get_moltype("dna")
+    expect = dna.complement(seqs[name][start:stop].replace("-", ""))[::-1]
+    assert got == expect, (got, expect)
+
+
+@pytest.mark.parametrize("name", ("s1", "s2", "s3"))
+def test_get_gapped_seq_with_sliced_aln(name):
+    seqs = {
+        "s1": "G-TG---TAGTAGAAGTTCCAAATAATGAA",
+        "s2": "GTG------GTAGAAGTTCCAAATAATGAA",
+        "s3": "GC--AAGTAGTGGAAGTTGCAAAT---GAA",
+    }
+    aln = make_aligned_seqs(data=seqs, moltype="dna", array_align=False)
+    start, stop = 1, 10
+    a1 = aln[start:stop]
+
+    seq = a1.get_gapped_seq(name)
+    assert isinstance(seq, Sequence), seq
+
+    got = str(seq)
+    expect = seqs[name][start:stop]
+    assert got == expect, (got, expect)
+
+
+@pytest.mark.parametrize("name", ("s1", "s2", "s3"))
+@pytest.mark.parametrize("array_align", (True, False))
+def test_aln_rev_slice(name, array_align):
+    seqs = {
+        "s1": "AAGGTTCC",
+        "s2": "AAGGTTCC",
+        "s3": "AAGGTTCC",
+    }
+
+    aln = make_aligned_seqs(data=seqs, moltype="dna", array_align=array_align)
+    got = aln[5:1]
+    assert not got
+
+    seq = got.get_gapped_seq(name)
+    assert str(seq) == ""
+
+    seq = got.get_seq(name)
+    assert str(seq) == ""
+
+
+@pytest.mark.parametrize("cls", (SequenceCollection, Alignment, ArrayAlignment))
+def test_init_duplicate_keys_raises(cls):
+    """SequenceCollection init from (key, val) pairs should fail on dup. keys"""
+    data = [["x", "XXX"], ["b", "BBB"], ["x", "CCC"], ["d", "DDD"], ["a", "AAA"]]
+    with pytest.raises(ValueError):
+        cls(data)
+
+
+@pytest.mark.parametrize(
+    "seq",
+    (
+        "ACGG",
+        DNA.make_seq("ACGG"),
+        numpy.array([2, 1, 3, 3], dtype=int),
+        Aligned(*DNA.make_seq("AC-GG").parse_out_gaps()),
+    ),
+)
+def test_construct_unaligned_seq(seq):
+    moltype = get_moltype("dna")
+    got = _construct_unaligned_seq(seq, moltype=moltype)
+    assert isinstance(got, (Sequence, ArraySequence))
+
+
+@pytest.mark.parametrize(
+    "data",
+    (
+        [["s1", "ACGG"]],
+        {"s1": "ACGG"},
+        {"s1": DNA.make_seq("ACGG")},
+        (
+            Aligned(
+                *DNA.make_seq("AC-GG", name="s1").parse_out_gaps(),
+            ),
+        ),
+        iter([["s1", "ACGG"]]),
+    ),
+)
+def test_to_unaligned_seqs(data):
+    moltype = get_moltype("dna")
+    got = _coerce_to_unaligned_seqs(data, names=None, moltype=moltype)
+    assert isinstance(got[0], list)
+    assert isinstance(got[0][0], (Sequence, ArraySequence))
+
+
+@pytest.mark.parametrize("moltype", ("dna", "protein"))
+@pytest.mark.parametrize("cls", (SequenceCollection, Alignment, ArrayAlignment))
+def test_same_moltype(cls, moltype):
+    moltype = get_moltype(moltype)
+    data = dict(s1="ACGTT", s2="ACCTT")
+    seqs = cls(data, moltype=moltype)
+    got = seqs.to_moltype(moltype=moltype)
+    assert got is seqs
+
+
+@pytest.mark.parametrize("cls", (SequenceCollection, Alignment, ArrayAlignment))
+def test_add(cls):
+    """__add__ should concatenate sequence data, by name"""
+    align1 = cls({"a": "AAAA", "b": "TTTT", "c": "CCCC"})
+    align2 = cls({"a": "GGGG", "b": "----", "c": "NNNN"})
+    align = align1 + align2
+    concatdict = align.to_dict()
+    assert concatdict == {"a": "AAAAGGGG", "b": "TTTT----", "c": "CCCCNNNN"}
+
+
+@pytest.mark.parametrize("cls", (Alignment, ArrayAlignment))
+def test_count_gaps_per_pos(cls):
+    """correctly compute the number of gaps"""
+    data = {"a": "AAAA---GGT", "b": "CCC--GG?GT"}
+    aln = cls(data=data, moltype=DNA)
+    # per position
+    got = aln.count_gaps_per_pos(include_ambiguity=False)
+    assert_equal(got.array, [0, 0, 0, 1, 2, 1, 1, 0, 0, 0])
+    got = aln.count_gaps_per_pos(include_ambiguity=True)
+    assert_equal(got.array, [0, 0, 0, 1, 2, 1, 1, 1, 0, 0])
+
+
+def _make_and_filter(cls, raw, expected, motif_length, drop_remainder):
+    # a simple filter func
+    aln = cls(raw, info={"key": "value"})
+    func = _make_filter_func(aln)
+    result = aln.filtered(
+        func,
+        motif_length=motif_length,
+        log_warnings=False,
+        drop_remainder=drop_remainder,
+    )
+    assert result.to_dict() == expected
+    assert result.info["key"] == "value"
+
+
+@pytest.mark.parametrize("cls", (Alignment, ArrayAlignment))
+def test_filtered(cls):
+    """filtered should return new alignment with positions consistent with
+    provided callback function"""
+    # a simple filter option
+    raw = {"a": "ACGACGACG", "b": "CCC---CCC", "c": "AAAA--AAA"}
+    _make_and_filter(cls, raw, {"a": "ACGACG", "b": "CCCCCC", "c": "AAAAAA"}, 1, True)
+    # check with motif_length = 2
+    _make_and_filter(cls, raw, {"a": "ACAC", "b": "CCCC", "c": "AAAA"}, 2, True)
+    # check with motif_length = 3
+    _make_and_filter(cls, raw, {"a": "ACGACG", "b": "CCCCCC", "c": "AAAAAA"}, 3, True)
+
+
+@pytest.mark.parametrize("cls", (SequenceCollection, Alignment, ArrayAlignment))
+def test_counts_per_seq(cls):
+    """SequenceCollection.counts_per_seq handles motif length, allow_gaps etc.."""
+    data = {"a": "AAAA??????", "b": "CCCGGG--NN", "c": "CCGGTTCCAA"}
+    coll = cls(data=data, moltype="dna")
+    mtype = coll.moltype
+    got = coll.counts_per_seq()
+    assert got["a", "A"] == 4
+    assert len(got.motifs) == len(mtype.alphabet)
+    got = coll.counts_per_seq(include_ambiguity=True, allow_gap=True)
+    # N, -, ? are the additional states
+    assert len(got.motifs) == 7
+    expect = {"-": 2, "?": 0, "A": 0, "C": 3, "G": 3, "N": 2, "T": 0}
+    b = got["b"].to_dict()
+    for k in expect:
+        assert b[k] == expect[k]
+
+    got = coll.counts_per_seq(motif_length=2)
+    assert len(got.motifs), len(mtype.alphabet) ** 2
+    assert got["a", "AA"] == 2
+    assert got["b", "GG"] == 1
+    got = coll.counts_per_seq(exclude_unobserved=True)
+    expect = {"C": 4, "G": 2, "T": 2, "A": 2}
+    c = got["c"].to_dict()
+    for k in expect:
+        assert c[k] == expect[k]
+
+
+def test_counts_per_seq_arr_alignment():
+    """ArrayAlignment counts_per_seq should return motif counts each seq"""
+    seqs = []
+    for s in ["abaa", "abbb"]:
+        seqs.append(AB.make_seq(s, preserve_case=True))
+    a = ArrayAlignment(seqs, moltype=AB)
+    f = a.counts_per_seq()
+    assert_equal(f.array, array([[3, 1], [1, 3]]))
+    f = a.counts_per_seq(motif_length=2, exclude_unobserved=True)
+    assert_equal(f.array, array([[1, 1, 0], [0, 1, 1]]))
+
+
+def test_coevolution_segments():
+    """specifying coordinate segments produces matrix with just those"""
+    aln = load_aligned_seqs("data/brca1.fasta", moltype="dna")
+    aln = aln.take_seqs(aln.names[:20])
+    aln = aln.no_degenerates()[:20]
+    coevo = aln.coevolution(segments=[(4, 6), (11, 13)], show_progress=False)
+    assert coevo.template.names[0] == [4, 5, 11, 12]
+
+
+@pytest.mark.parametrize("cls", (Alignment, ArrayAlignment))
+def test_iter_positions(cls):
+    data = {"a": "AAAAAA", "b": "AAA---", "c": "AAAA--"}
+    r = cls({k: data[k] for k in "cb"})
+    assert list(r.iter_positions(pos_order=[5, 1, 3])) == list(
+        map(list, ["--", "AA", "A-"])
+    )
+    # reorder names
+    r = cls(data)
+    cols = list(r.iter_positions())
+    assert cols == list(map(list, ["AAA", "AAA", "AAA", "A-A", "A--", "A--"]))
+
+
+@pytest.mark.parametrize("cls", (Alignment, ArrayAlignment))
+def test_add_from_ref_aln(cls):
+    """should add or insert seqs based on align to reference"""
+    aln1 = cls(
+        [
+            ["name1", "-AC-DEFGHI---"],
+            ["name2", "XXXXXX--XXXXX"],
+            ["name3", "YYYY-YYYYYYYY"],
+        ]
+    )
+
+    aln2 = cls(
+        [
+            ["name1", "ACDEFGHI"],
+            ["name4", "KL--MNPR"],
+            ["name5", "KLACMNPR"],
+            ["name6", "KL--MNPR"],
+        ]
+    )
+
+    aligned_to_ref_out_aln_inserted = cls(
+        [
+            ["name1", "-AC-DEFGHI---"],
+            ["name4", "-KL---MNPR---"],
+            ["name5", "-KL-ACMNPR---"],
+            ["name6", "-KL---MNPR---"],
+            ["name2", "XXXXXX--XXXXX"],
+            ["name3", "YYYY-YYYYYYYY"],
+        ]
+    )
+
+    aln2_wrong_refseq = cls((("name1", "ACDXFGHI"), ("name4", "KL--MNPR")))
+
+    aln2_wrong_refseq_name = cls([["nameY", "ACDEFGHI"], ["name4", "KL--MNPR"]])
+
+    aln2_different_aln_class = ArrayAlignment(
+        [["name1", "ACDEFGHI"], ["name4", "KL--MNPR"]]
+    )
+
+    aligned_to_ref_out_aln = cls(
+        [
+            ["name1", "-AC-DEFGHI---"],
+            ["name2", "XXXXXX--XXXXX"],
+            ["name3", "YYYY-YYYYYYYY"],
+            ["name4", "-KL---MNPR---"],
+        ]
+    )
+
+    out_aln = aln1.add_from_ref_aln(aln2, after_name="name1")
+    assert str(aligned_to_ref_out_aln_inserted) == str(out_aln)  # test insert_after
+
+    out_aln = aln1.add_from_ref_aln(aln2, before_name="name2")
+    assert aligned_to_ref_out_aln_inserted == out_aln  # test insert_before
+
+    with pytest.raises(ValueError):
+        aln1.add_from_ref_aln(aln2_wrong_refseq_name)
+
+    aln = aln1.add_from_ref_aln(aln2_different_aln_class)
+    # test_align_to_refseq_different_aln_class
+    assert aligned_to_ref_out_aln == aln
+
+    with pytest.raises(ValueError):
+        # test wrong_refseq
+        aln1.add_from_ref_aln(aln2_wrong_refseq)
+
+
+@pytest.mark.parametrize("cls", (Alignment, ArrayAlignment))
+def test_replace_seqs(cls):
+    """replace_seqs should replace 1-letter w/ 3-letter seqs"""
+    a = cls({"seq1": "ACGU", "seq2": "C-UA", "seq3": "C---"})
+    seqs = {"seq1": "AAACCCGGGUUU", "seq2": "CCCUUUAAA", "seq3": "CCC"}
+    result = a.replace_seqs(seqs)  # default behaviour
+    assert result.to_dict() == {
+        "seq1": "AAACCCGGGUUU",
+        "seq2": "CCC---UUUAAA",
+        "seq3": "CCC---------",
+    }
+
+    result = a.replace_seqs(seqs, aa_to_codon=True)  # default behaviour
+    assert result.to_dict() == {
+        "seq1": "AAACCCGGGUUU",
+        "seq2": "CCC---UUUAAA",
+        "seq3": "CCC---------",
+    }
+
+    # should correctly gap the same sequences with same length
+    result = a.replace_seqs(a.degap(), aa_to_codon=False)  # default behaviour
+    assert result.to_dict() == {"seq1": "ACGU", "seq2": "C-UA", "seq3": "C---"}
+
+    # should fail when not same length if aa_to_codon is False
+    new = SequenceCollection(
+        [(n, s.replace("-", "")) for n, s in list(a[:3].to_dict().items())],
+        moltype="bytes",
+    )
+    with pytest.raises(ValueError):
+        a.replace_seqs(new, aa_to_codon=False)
+
+    # check the gaps are changed
+    aln1 = cls(data={"a": "AC-CT", "b": "ACGCT"})
+    aln2 = cls(data={"a": "ACC-T", "b": "ACGCT"})
+
+    result = aln1.replace_seqs(aln2, aa_to_codon=False)
+    assert id(aln1) != id(aln2)
+    assert aln1.to_dict() == result.to_dict()
+
+
+def test_get_alphabet_and_moltype():
+    """ArrayAlignment should figure out correct alphabet and moltype"""
+    s1 = "A"
+    s2 = RNA.make_seq("AA")
+
+    d = ArrayAlignment(s1)
+    assert d.moltype is BYTES
+    assert d.alphabet is BYTES.alphabet
+
+    d = ArrayAlignment(s1, moltype=RNA)
+    assert d.moltype is RNA
+    assert d.alphabet is RNA.alphabets.degen_gapped
+
+    d = ArrayAlignment([s2])
+    assert d.moltype is RNA
+    assert d.alphabet is RNA.alphabets.degen_gapped
+
+    d = ArrayAlignment(s2, moltype=DNA)
+    assert d.moltype is DNA
+    assert d.alphabet is DNA.alphabets.degen_gapped
+    # checks for containers
+    d = ArrayAlignment([s2])
+    assert d.moltype is RNA
+    d = ArrayAlignment({"x": s2})
+    assert d.moltype is RNA
+    d = ArrayAlignment(set([s2]))
+    assert d.moltype is RNA
+
+
+@pytest.mark.parametrize("cls", (Alignment, ArrayAlignment, SequenceCollection))
+def test_aln_from_fasta_parser(cls):
+    """aln_from_fasta_parser should init from iterator"""
+    s = ">aa\nAC\n>bb\nAA\n>c\nGG\n".splitlines()
+    p = MinimalFastaParser(s)
+    aln = cls(p, moltype=DNA)
+    assert aln.named_seqs["aa"] == "AC"
+    assert aln.to_dict() == {"aa": "AC", "bb": "AA", "c": "GG"}
+    s2 = ">aa\nAC\n>bb\nAA\n>c\nGG\n"
+    d = cls(MinimalFastaParser(s2.splitlines()))
+    assert d.to_dict() == aln.to_dict()
+
+
+@pytest.mark.parametrize(
+    "d", ({"a": "AAAAA", "b": "BBBBB"}, {"a": b"AAAAA", "b": b"BBBBB"})
+)
+@pytest.mark.parametrize("cls", (Alignment, ArrayAlignment, SequenceCollection))
+def test_init_dict(cls, d):
+    """SequenceCollection init from dict should work as expected"""
+    # from bytes strings
+    a = cls(d, names=["a", "b"])
+    expect = {k: v.decode("utf8") if isinstance(v, bytes) else v for k, v in d.items()}
+    assert a.to_dict() == expect
+
+
+@pytest.mark.parametrize("cls", (Alignment, ArrayAlignment, SequenceCollection))
+def test_init_seq_info(cls):
+    """SequenceCollection init from seqs w/ info should preserve data"""
+    a = Sequence("AAA", name="a", info={"x": 3})
+    b = Sequence("CCC", name="b", info={"x": 4})
+    c = Sequence("GGG", name="c", info={"x": 5})
+    seqs = [c, b, a]
+    a = cls(seqs)
+    assert list(a.names) == ["c", "b", "a"]
+    assert list(map(str, a.seqs)) == ["GGG", "CCC", "AAA"]
+    if cls is not ArrayAlignment:
+        # ArrayAlignment is allowed to strip info objects
+        assert [i.info.x for i in a.seqs] == [5, 4, 3]
+    # check it still works if constructed from same class
+    b = cls(a)
+    assert list(b.names) == ["c", "b", "a"]
+    assert list(map(str, b.seqs)) == ["GGG", "CCC", "AAA"]
+    if cls is not ArrayAlignment:
+        # ArrayAlignment is allowed to strip Info objects
+        assert [i.info.x for i in b.seqs] == [5, 4, 3]
+
+
+@pytest.mark.parametrize("val", (None, 3))
+def test_init_empty(val):
+    """ArrayAlignment init should fail if empty."""
+    with pytest.raises((ValueError, TypeError)):
+        ArrayAlignment(val)
+        ArrayAlignment()
+
+
+def test_make_case():
+    data = {"a": "tata", "b": "tgtc", "c": "gcga", "d": "gaac", "e": "gagc"}
+    got = make_aligned_seqs(data=data, moltype="dna")
+    assert got == {k: v.upper() for k, v in data.items()}
diff --git a/tests/test_core/test_alphabet.py b/tests/test_core/test_alphabet.py
index 6ace1e2cd..e2ef141f3 100644
--- a/tests/test_core/test_alphabet.py
+++ b/tests/test_core/test_alphabet.py
@@ -4,7 +4,7 @@
 """
 import pickle
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy import unravel_index
 from numpy.testing import assert_equal
@@ -207,6 +207,13 @@ def test_pickling(self):
         self.assertIsInstance(got, type(r))
         self.assertEqual(got.get_word_alphabet(2), wa)
 
+    def test_pickling_moltype(self):
+        from cogent3.core.moltype import DNA
+
+        a = DNA.alphabet
+        got = pickle.loads(pickle.dumps(a))
+        assert got.moltype is not None
+
     def test_word_alphabet_order(self):
         bases = "TCAG"
         r = CharAlphabet(bases)
diff --git a/tests/test_core/test_annotation.py b/tests/test_core/test_annotation.py
index 890048003..0fcfcbc1a 100644
--- a/tests/test_core/test_annotation.py
+++ b/tests/test_core/test_annotation.py
@@ -160,7 +160,7 @@ def test_feature_projection_ungapped(ann_aln):
     assert ann_aln.annotation_db.num_matches() == num + 1
     assert str(seq_ltr.get_slice()) == expected
     assert seq_ltr.seqid == seq_name
-    assert seq_ltr.parent is ann_aln.get_seq(seq_name)
+    assert seq_ltr.parent == ann_aln.get_seq(seq_name)
 
 
 def test_feature_projection_gapped(ann_aln):
@@ -179,7 +179,7 @@ def test_feature_projection_gapped(ann_aln):
     expected = expected.replace("-", "")
     assert str(seq_ltr.get_slice()) == expected
     assert seq_ltr.seqid == seq_name
-    assert seq_ltr.parent is ann_aln.get_seq(seq_name)
+    assert seq_ltr.parent == ann_aln.get_seq(seq_name)
 
 
 @pytest.fixture()
diff --git a/tests/test_core/test_annotation_db.py b/tests/test_core/test_annotation_db.py
index 8755151df..b2a106921 100644
--- a/tests/test_core/test_annotation_db.py
+++ b/tests/test_core/test_annotation_db.py
@@ -143,7 +143,7 @@ def test_count_distinct(gff_db):
 def test_count_distinct_values(gb_db):
     # there are 8 biotypes in the c.elegans gff sample, 2 columns
     # all arguments returns, from our example, all the rows
-    got = {tuple(r) for r in gb_db.count_distinct(name=True).tolist()}
+    got = {tuple(r) for r in gb_db.count_distinct(name=True).to_list()}
     expect = {("CNA00110", 4), ("CNA00120", 3), ("cgg", 1), ("cat", 1), ("JEC21", 1)}
     assert got == expect
 
@@ -151,14 +151,14 @@ def test_count_distinct_values(gb_db):
 def test_count_distinct_gene_name(gb_db):
     expect = {("CNA00110", 1), ("CNA00120", 1)}
     assert {
-        tuple(r) for r in gb_db.count_distinct(biotype="gene", name=True).tolist()
+        tuple(r) for r in gb_db.count_distinct(biotype="gene", name=True).to_list()
     } == expect
 
     assert {
         tuple(r)
         for r in gb_db.count_distinct(
             seqid="AE017341", biotype="gene", name=True
-        ).tolist()
+        ).to_list()
     } == expect
 
 
@@ -945,3 +945,52 @@ def test_gbdb_get_children_fails_no_coords(gb_db):
 def test_gbdb_get_parent_fails_no_coords(gb_db):
     with pytest.raises(ValueError):
         _ = list(gb_db.get_feature_parent(name="CNA00110"))
+
+
+def test_load_annotations_invalid_path():
+    with pytest.raises(IOError):
+        load_annotations(path="invalidfile.gff3")
+
+
+def test_subset_gff3_db(gff_db):
+    subset = gff_db.subset(seqid="I", start=40, end=70, allow_partial=True)
+    # manual inspection of the original GFF3 file indicates 7 records
+    # BUT the CDS records get merged into a single row
+    assert len(subset) == 6
+
+
+def test_subset_empty_db(gff_db):
+    subset = gff_db.subset(seqid="X", start=40, end=70, allow_partial=True)
+    # no records
+    assert not len(subset)
+
+
+def test_subset_gff3_db_with_user(gff_db):
+    record = dict(
+        seqid="I", name="gene-01", biotype="gene", spans=[(23, 43)], strand="+"
+    )
+    gff_db.add_feature(**record)
+    subset = gff_db.subset(seqid="I", start=40, end=70, allow_partial=True)
+    # manual inspection of the original GFF3 file indicates 7 records
+    # BUT the CDS records get merged into a single row
+    assert len(subset) == 7
+
+
+def test_subset_gb_db(gb_db):
+    subset = gb_db.subset(biotype="gene")
+    # manual inspection of the original annotated gb file indicates 2 genes
+    assert len(subset) == 2
+
+
+def test_subset_gff3_db_source(gff_db, tmp_dir):
+    outpath = tmp_dir / "subset.gff3db"
+    subset = gff_db.subset(
+        seqid="I", start=40, end=70, allow_partial=True, source=outpath
+    )
+    subset.db.close()
+
+    # reload
+    subset = type(gff_db)(source=outpath)
+    # manual inspection of the original GFF3 file indicates 7 records
+    # BUT the CDS records get merged into a single row
+    assert len(subset) == 6
diff --git a/tests/test_core/test_genetic_code.py b/tests/test_core/test_genetic_code.py
index 1e0ace7d2..7b018452f 100644
--- a/tests/test_core/test_genetic_code.py
+++ b/tests/test_core/test_genetic_code.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 """ Unit tests for Genetic Code classes.
 """
-from unittest import TestCase, main
+from unittest import TestCase
 
 import pytest
 
diff --git a/tests/test_core/test_info.py b/tests/test_core/test_info.py
index 92a7c2f38..e922a8b60 100644
--- a/tests/test_core/test_info.py
+++ b/tests/test_core/test_info.py
@@ -3,7 +3,7 @@
 """
 import warnings
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.core.info import DbRef, DbRefs, Info, _make_list
 
@@ -147,5 +147,3 @@ def test_update(self):
 
 
 # run the following if invoked from command-line
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_core/test_location.py b/tests/test_core/test_location.py
index cccc384bf..6c940da11 100644
--- a/tests/test_core/test_location.py
+++ b/tests/test_core/test_location.py
@@ -1,15 +1,9 @@
-"""Unit tests for Range, Span and Point classes.
+"""Unit tests for Span classes.
 """
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3 import DNA
-from cogent3.core.location import (
-    Map,
-    Range,
-    RangeFromString,
-    Span,
-    gap_coords_to_map,
-)
+from cogent3.core.location import Map, Span, gap_coords_to_map
 
 
 class SpanTests(TestCase):
@@ -257,254 +251,6 @@ def test_ends_inside(self):
         self.assertFalse(e.ends_inside(Span(-1, 0)))
 
 
-class RangeInterfaceTests(object):  # SpanTests):
-    """A single-element Range should behave like the corresponding Span."""
-
-    def setUp(self):
-        """Define some standard Spans"""
-        self.empty = Range(Span(0, 0))
-        self.full = Range(Span(30, 35))
-        self.overlapping = Range(Span(32, 36))
-        self.inside = Range(Span(31, 32))
-        self.before = Range(Span(25, 30))
-        self.after = Range(Span(35, 40))
-        self.reverse = Range(Span(30, 35, reverse=True))
-        self.spans_zero = Range(Span(-5, 5))
-
-    def test_str(self):
-        """Range str should print start, stop, reverse for each Span"""
-        # note that the Range adds an extra level of parens, since it can
-        # contain more than one Span.
-        self.assertEqual(str(self.empty), "((0,0,False))")
-        self.assertEqual(str(self.full), "((30,35,False))")
-        self.assertEqual(str(self.reverse), "((30,35,True))")
-
-
-class RangeTests(TestCase):
-    """Tests of the Range object."""
-
-    def setUp(self):
-        """Set up a few standard ranges."""
-        self.one = Range(Span(0, 100))
-        self.two = Range([Span(3, 5), Span(8, 11)])
-        self.three = Range([Span(6, 7), Span(15, 17), Span(30, 35)])
-        self.overlapping = Range([Span(6, 10), Span(7, 3)])
-        self.single = Range(0)
-        self.singles = Range([3, 11])
-        self.twocopy = Range(self.two)
-        self.twothree = Range([self.two, self.three])
-        self.empty = Range([Span(6, 6), Span(8, 8)])
-
-    def test_init(self):
-        """Range init from Spans, numbers, or Ranges should work OK."""
-        # single span
-        self.assertEqual(self.one, Span(0, 100))
-        # list of spans
-        self.assertEqual(self.two.spans, [Span(3, 5), Span(8, 11)])
-        # another range
-        self.assertEqual(self.two, self.twocopy)
-        # list of ranges
-        self.assertEqual(
-            self.twothree.spans,
-            [Span(3, 5), Span(8, 11), Span(6, 7), Span(15, 17), Span(30, 35)],
-        )
-        # list of numbers
-        self.assertEqual(self.singles.spans, [Span(3, 4), Span(11, 12)])
-        # single number
-        self.assertEqual(self.single.spans, [Span(0, 1)])
-        # nothing
-        self.assertEqual(Range().spans, [])
-
-    def test_str(self):
-        """Range str should print nested with parens"""
-        self.assertEqual(str(self.one), "((0,100,False))")
-        self.assertEqual(
-            str(self.twothree),
-            "((3,5,False),(8,11,False),(6,7,False),(15,17,False),(30,35,False))",
-        )
-        self.assertEqual(str(self.single), "((0,1,False))")
-
-    def test_len(self):
-        """Range len should sum span lengths"""
-        self.assertEqual(len(self.one), 100)
-        self.assertEqual(len(self.single), 1)
-        self.assertEqual(len(self.empty), 0)
-        self.assertEqual(len(self.three), 8)
-
-    def test_cmp(self):
-        """Ranges should compare equal if they have the same spans"""
-        self.assertEqual(
-            self.twothree,
-            Range([Span(3, 5), Span(8, 11), Span(6, 7), Span(15, 17), Span(30, 35)]),
-        )
-        self.assertEqual(Range(), Range())
-
-    def test_start_end(self):
-        """Range start and end should behave as expected"""
-        self.assertEqual(self.one.start, 0)
-        self.assertEqual(self.one.end, 100)
-        self.assertEqual(self.overlapping.start, 3)
-        self.assertEqual(self.overlapping.end, 10)
-        self.assertEqual(self.three.start, 6)
-        self.assertEqual(self.three.end, 35)
-
-    def test_reverse(self):
-        """Range reverse method should reverse each span"""
-        for s in self.overlapping.spans:
-            self.assertFalse(s.reverse)
-        self.overlapping.reverses()
-        for s in self.overlapping.spans:
-            self.assertTrue(s.reverse)
-        self.overlapping.spans.append(Span(0, 100))
-        self.overlapping.reverses()
-        for s in self.overlapping.spans[0:1]:
-            self.assertFalse(s.reverse)
-        self.assertTrue(self.overlapping.spans[-1].reverse)
-
-    def test_Reverse(self):
-        """Range reverse property should return True if any span reversed"""
-        self.assertFalse(self.one.reverse)
-        self.one.reverses()
-        self.assertTrue(self.one.reverse)
-        self.assertFalse(self.two.reverse)
-        self.two.spans.append(Span(0, 100, reverse=True))
-        self.assertTrue(self.two.reverse)
-        self.two.reverses()
-        self.assertTrue(self.two.reverse)
-
-    def test_contains(self):
-        """Range contains an item if any span contains it"""
-        self.assertIn(50, self.one)
-        self.assertIn(0, self.one)
-        self.assertIn(99, self.one)
-        self.assertNotIn(100, self.one)
-        self.assertIn(6, self.three)
-        self.assertNotIn(7, self.three)
-        self.assertNotIn(8, self.three)
-        self.assertNotIn(14, self.three)
-        self.assertIn(15, self.three)
-        self.assertNotIn(29, self.three)
-        self.assertIn(30, self.three)
-        self.assertIn(34, self.three)
-        self.assertNotIn(35, self.three)
-        self.assertNotIn(40, self.three)
-        # should work if a span is added
-        self.three.spans.append(40)
-        self.assertIn(40, self.three)
-        # should work for spans
-        self.assertIn(Span(31, 33), self.three)
-        self.assertNotIn(Span(31, 37), self.three)
-        # span contains itself
-        self.assertIn(self.twocopy, self.two)
-        # should work for ranges
-        self.assertIn(Range([6, Span(15, 16), Span(30, 33)]), self.three)
-        # should work for copy, except when extra piece added
-        threecopy = Range(self.three)
-        self.assertIn(threecopy, self.three)
-        threecopy.spans.append(1000)
-        self.assertNotIn(threecopy, self.three)
-        self.three.spans.append(Span(950, 1050))
-        self.assertIn(threecopy, self.three)
-        self.assertNotIn(self.three, threecopy)
-
-    def test_overlaps(self):
-        """Range overlaps should return true if any component overlapping"""
-        self.assertTrue(self.two.overlaps(self.one))
-        self.assertTrue(self.one.overlaps(self.two))
-        self.assertTrue(self.three.overlaps(self.one))
-        # two and three are interleaved but not overlapping
-        self.assertFalse(self.two.overlaps(self.three))
-        self.assertFalse(self.three.overlaps(self.two))
-        self.assertTrue(self.one.overlaps(self.empty))
-        self.assertTrue(self.empty.overlaps(self.one))
-        self.assertTrue(self.singles.overlaps(self.two))
-
-    def test_overlaps_extent(self):
-        """Range overlaps_extent should return true for interleaved ranges"""
-        self.assertTrue(self.two.overlaps_extent(self.three))
-        self.assertTrue(self.three.overlaps_extent(self.two))
-        self.assertFalse(self.single.overlaps_extent(self.two))
-        self.assertFalse(self.single.overlaps_extent(self.three))
-        self.assertTrue(self.one.overlaps_extent(self.three))
-
-    def test_sort(self):
-        """Range sort should sort component spans"""
-        one = self.one
-        one.sort()
-        self.assertEqual(one.spans, [Span(100, 0)])
-        one.spans.append(Span(-20, -10))
-        self.assertEqual(one.spans, [Span(0, 100), Span(-20, -10)])
-        one.sort()
-        self.assertEqual(one.spans, [Span(-20, -10), Span(0, 100)])
-        one.spans.append(Span(-20, -10, reverse=True))
-        self.assertEqual(
-            one.spans, [Span(-20, -10), Span(0, 100), Span(-20, -10, reverse=True)]
-        )
-        one.sort()
-        self.assertEqual(
-            one.spans, [Span(-20, -10), Span(-20, -10, reverse=True), Span(0, 100)]
-        )
-
-    def test_iter(self):
-        """Range iter should iterate through each span in turn"""
-        self.assertEqual(list(iter(self.two)), [3, 4, 8, 9, 10])
-        self.two.spans.insert(1, Span(103, 101, reverse=True))
-        self.assertEqual(list(iter(self.two)), [3, 4, 102, 101, 8, 9, 10])
-
-    def test_extent(self):
-        """Range extent should span limits of range"""
-        self.assertEqual(self.one.extent, Span(0, 100))
-        self.assertEqual(self.three.extent, Span(6, 35))
-        self.assertEqual(self.singles.extent, Span(3, 12))
-        self.assertEqual(self.single.extent, Span(0, 1))
-        self.three.spans.append(Span(100, 105, reverse=True))
-        self.assertEqual(self.three.extent, Span(6, 105))
-        self.three.spans.append(Span(-100, -1000))
-        self.assertEqual(self.three.extent, Span(-1000, 105))
-
-    def test_simplify(self):
-        """Range reduce should group overlapping ranges"""
-        # consolidate should have no effect when no overlap
-        r = self.two
-        r.simplify()
-        self.assertEqual(r.spans, [Span(3, 5), Span(8, 11)])
-        # should consolidate an overlap of the same direction
-        r.spans.append(Span(-1, 4))
-        r.simplify()
-        self.assertEqual(r.spans, [Span(-1, 5), Span(8, 11)])
-        # should also consolidate _adjacent_ spans of the same direction
-        r.spans.append(Span(11, 14))
-        r.simplify()
-        self.assertEqual(r.spans, [Span(-1, 5), Span(8, 14)])
-        # bridge should cause consolidations
-        s = Range(r)
-        s.spans.append(Span(5, 8))
-        s.simplify()
-        self.assertEqual(s.spans, [Span(-1, 14)])
-        # ditto for bridge that overlaps everything
-        s = Range(r)
-        s.spans.append(Span(-100, 100))
-        s.simplify()
-        self.assertEqual(s.spans, [Span(-100, 100)])
-        # however, can't consolidate span in other orientation
-        s = Range(r)
-        s.spans.append(Span(-100, 100, reverse=True))
-        self.assertEqual(
-            s.spans, [Span(-1, 5), Span(8, 14), Span(-100, 100, reverse=True)]
-        )
-
-
-class RangeFromStringTests(TestCase):
-    """Tests of the RangeFromString factory function."""
-
-    def test_init(self):
-        self.assertEqual(RangeFromString(""), Range())
-        self.assertEqual(RangeFromString("  3  , 4\t, ,, 10  ,"), Range([3, 4, 10]))
-        self.assertEqual(
-            RangeFromString("3,4-10,1-5"), Range([Span(3), Span(4, 10), Span(1, 5)])
-        )
-
-
 class MapTests(TestCase):
     """tests of the Map class"""
 
diff --git a/tests/test_core/test_moltype.py b/tests/test_core/test_moltype.py
index bd4636194..382df337e 100644
--- a/tests/test_core/test_moltype.py
+++ b/tests/test_core/test_moltype.py
@@ -1,7 +1,8 @@
-#!/usr/bin/env python
 import pickle
 
-from unittest import TestCase, main
+from unittest import TestCase
+
+import pytest
 
 # ind some of the standard alphabets to reduce typing
 from numpy.testing import assert_allclose
@@ -968,19 +969,6 @@ def setUp(self):
 
         self.alpha = STANDARD_CODON
 
-    def test_ambiguous_gaps(self):
-        alpha = self.alpha.with_gap_motif()
-        self.assertEqual(len(alpha.resolve_ambiguity("AT?")), 4)
-        self.assertRaises(Exception, alpha.resolve_ambiguity, "at-")
-        self.assertEqual(len(alpha.resolve_ambiguity("???")), 62)
-        self.assertEqual(len(alpha.resolve_ambiguity("---")), 1)
-
-        alpha = self.alpha
-        self.assertEqual(len(alpha.resolve_ambiguity("AT?")), 4)
-        self.assertRaises(Exception, alpha.resolve_ambiguity, "at-")
-        self.assertEqual(len(alpha.resolve_ambiguity("???")), 61)
-        self.assertRaises(Exception, alpha.resolve_ambiguity, "---")
-
     def test_exclude(self):
         """testing excluding gap motif"""
         alpha = self.alpha
@@ -1128,5 +1116,26 @@ def test_constructing_from_func(self):
         self.assertEqual(alpha_int, alpha_name)
 
 
-if __name__ == "__main__":
-    main()
+def test_resolve_ambiguity_nucs():
+    got = DNA.resolve_ambiguity("AT?", allow_gap=False)
+    assert len(got) == 4
+    assert len(got[0]) == 3
+
+
+def test_resolve_ambiguity_codons():
+    from cogent3 import get_code
+
+    gc = get_code(1)
+    codon_alpha = gc.get_alphabet(include_stop=False)
+    codon_alpha_w_gap = codon_alpha.with_gap_motif()
+    assert len(DNA.resolve_ambiguity("AT?", alphabet=codon_alpha_w_gap)) == 4
+    assert len(DNA.resolve_ambiguity("???", alphabet=codon_alpha_w_gap)) == 62
+    assert len(DNA.resolve_ambiguity("---", alphabet=codon_alpha_w_gap)) == 1
+
+    assert len(DNA.resolve_ambiguity("AT?", alphabet=codon_alpha)) == 4
+    assert len(DNA.resolve_ambiguity("???", alphabet=codon_alpha)) == 61
+
+    with pytest.raises(AlphabetError):
+        DNA.resolve_ambiguity("at-")
+    with pytest.raises(AlphabetError):
+        DNA.resolve_ambiguity("---", alphabet=codon_alpha)
diff --git a/tests/test_core/test_profile.py b/tests/test_core/test_profile.py
index d6075c5d3..54522f58a 100644
--- a/tests/test_core/test_profile.py
+++ b/tests/test_core/test_profile.py
@@ -2,7 +2,7 @@
 """Provides tests for classes and functions in profile.py
 """
 from collections import Counter
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy import array, log2, nan, vstack
 from numpy.testing import assert_allclose
diff --git a/tests/test_core/test_seq_aln_integration.py b/tests/test_core/test_seq_aln_integration.py
index f3ab2f685..ef505e63a 100644
--- a/tests/test_core/test_seq_aln_integration.py
+++ b/tests/test_core/test_seq_aln_integration.py
@@ -30,7 +30,6 @@ def setUp(self):
         self.da = ArrayAlignment(
             [self.model1, self.model2, self.model3],
             moltype=RNA,
-            alphabet=RNA.alphabets.degen_gapped,
         )
 
         # seqs no name
@@ -46,7 +45,6 @@ def setUp(self):
         self.nn_da = ArrayAlignment(
             [self.nn_model1, self.nn_model2, self.nn_model3],
             moltype=RNA,
-            alphabet=RNA.alphabets.degen_gapped,
         )
 
     def test_printing_named_seqs(self):
@@ -117,7 +115,8 @@ def test_subset_positions_ArrayAlignment(self):
         model2 = ArraySequence("YCG", name="rna2", alphabet=RNA.alphabets.degen_gapped)
         model3 = ArraySequence("CAR", name="rna3", alphabet=RNA.alphabets.degen_gapped)
         sub_da = ArrayAlignment(
-            [model1, model2, model3], moltype=RNA, alphabet=RNA.alphabets.degen_gapped
+            [model1, model2, model3],
+            moltype=RNA,
         )
 
         sub_data = array([[0, 1, 3], [Y, 1, 3], [1, 2, R]])
@@ -192,7 +191,8 @@ def test_subset_seqs_ArrayAlignment(self):
         model2 = ArraySequence("YCG", name="rna2", alphabet=RNA.alphabets.degen_gapped)
         model3 = ArraySequence("CAR", name="rna3", alphabet=RNA.alphabets.degen_gapped)
         sub_da = ArrayAlignment(
-            [model1, model2, model3], moltype=RNA, alphabet=RNA.alphabets.degen_gapped
+            [model1, model2, model3],
+            moltype=RNA,
         )
 
         # take_seqs by name should have the same effect as
@@ -209,14 +209,14 @@ def test_aln_equality(self):
         self.assertEqual(self.da == self.da, True)
         # one sequence less
         other_da1 = ArrayAlignment(
-            [self.model1, self.model2], moltype=RNA, alphabet=RNA.alphabets.degen_gapped
+            [self.model1, self.model2],
+            moltype=RNA,
         )
         self.assertEqual(self.da == other_da1, False)
         # seqs in different order -- doesn't matter
         other_da2 = ArrayAlignment(
             [self.model1, self.model3, self.model2],
             moltype=RNA,
-            alphabet=RNA.alphabets.degen_gapped,
         )
         self.assertEqual(self.da == other_da2, True)
         # seqs in different encoding -- doesn't matter, only looks at data
diff --git a/tests/test_core/test_sequence.py b/tests/test_core/test_sequence.py
index f49a68e7f..0f1af2318 100644
--- a/tests/test_core/test_sequence.py
+++ b/tests/test_core/test_sequence.py
@@ -7,6 +7,7 @@
 from pickle import dumps
 from unittest import TestCase
 
+import numpy
 import pytest
 
 from numpy import array
@@ -2461,3 +2462,26 @@ def test_trim_terminal_stop_strict():
     seq = cogent3.make_seq("TCCAG", moltype="dna")
     with pytest.raises(AlphabetError):
         seq.trim_stop_codon(gc=gc, strict=True)
+
+
+@pytest.mark.parametrize("cast", (int, numpy.int32, numpy.int64, numpy.uint8))
+def test_index_a_seq(cast):
+    seq = cogent3.make_seq("TCCAG", moltype="dna")
+    got = seq[cast(1)]
+    assert isinstance(got, Sequence)
+
+
+@pytest.mark.parametrize("cast", (float, numpy.float32))
+def test_index_a_seq_float_fail(cast):
+    seq = cogent3.make_seq("TCCAG", moltype="dna")
+    index = cast(1)
+    with pytest.raises(TypeError):
+        seq[index]
+
+
+@pytest.mark.parametrize("moltype", ("dna", "protein"))
+def test_same_moltype(moltype):
+    moltype = get_moltype(moltype)
+    seq = moltype.make_seq("TCCAG")
+    got = seq.to_moltype(moltype)
+    assert got is seq
diff --git a/tests/test_core/test_tree.py b/tests/test_core/test_tree.py
index baa764b58..34b016dae 100644
--- a/tests/test_core/test_tree.py
+++ b/tests/test_core/test_tree.py
@@ -2196,6 +2196,100 @@ def test_get_distances_endpoints(num_tips):
     assert len(dists) == (num**2 - num)
 
 
+@pytest.mark.parametrize(
+    "method,expected",
+    (
+        ("lin_rajan_moret", 3),
+        ("lrm", 3),
+        ("matching", 3),
+        (None, 3),
+        ("unrooted_robinson_foulds", 4),
+        ("urf", 4),
+        ("rf", 4),
+    ),
+)
+def test_tree_distance_unrooted(method, expected):
+    tree1 = make_tree(treestring="(a,b,(c,(d,e)));")
+    tree2 = make_tree(treestring="((a,c),(b,d),e);")
+
+    assert tree1.tree_distance(tree2, method=method) == expected
+
+
+@pytest.mark.parametrize(
+    "method,expected",
+    (
+        ("matching_cluster", 10),
+        ("mc", 10),
+        ("matching", 10),
+        (None, 10),
+        ("rooted_robinson_foulds", 6),
+        ("rrf", 6),
+        ("rf", 6),
+    ),
+)
+def test_tree_distance_rooted(method, expected):
+    tree1 = make_tree(treestring="(a,(b,(c,(d,e))));")
+    tree2 = make_tree(treestring="(e,(d,(c,(b,a))));")
+
+    assert tree1.tree_distance(tree2, method=method) == expected
+
+
+@pytest.mark.parametrize(
+    "bad_method",
+    (
+        "matching_cluster ",
+        "m",
+        "mcc",
+        "rr",
+        "rff",
+        "ur",
+        "match",
+        " matching",
+        "robinson_foulds",
+        "None",
+    ),
+)
+def test_tree_distance_method_does_not_exist(bad_method):
+    unrooted1 = make_tree(treestring="(a,b,(c,(d,e)));")
+    unrooted2 = make_tree(treestring="((a,c),(b,d),e);")
+
+    with pytest.raises(ValueError):
+        unrooted1.tree_distance(unrooted2, method=bad_method)
+
+    rooted1 = make_tree(treestring="(a,(b,(c,(d,e))));")
+    rooted2 = make_tree(treestring="(e,(d,(c,(b,a))));")
+
+    with pytest.raises(ValueError):
+        rooted1.tree_distance(rooted2, method=bad_method)
+
+
+@pytest.mark.parametrize(
+    "method",
+    (
+        "matching_cluster",
+        "lin_rajan_moret",
+        "rooted_robinson_foulds",
+        "unrooted_robinson_foulds",
+        "mc",
+        "lrm",
+        "rrf",
+        "urf",
+        "matching",
+        "rf",
+        None,
+    ),
+)
+def test_tree_distance_incompatible_trees(method):
+    unrooted = make_tree(treestring="(a,b,(c,(d,e)));")
+    rooted = make_tree(treestring="(a,(b,(c,(d,e))));")
+
+    with pytest.raises(ValueError):
+        rooted.tree_distance(unrooted, method=method)
+
+    with pytest.raises(ValueError):
+        unrooted.tree_distance(rooted, method=method)
+
+
 def test_lrm_method():
     # this test just exercises the method, the tests on the underlying
     # function are in test_tree_distance.py
diff --git a/tests/test_data/test_molecular_weight.py b/tests/test_data/test_molecular_weight.py
index abea38fb6..4aae3c304 100644
--- a/tests/test_data/test_molecular_weight.py
+++ b/tests/test_data/test_molecular_weight.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 """Tests for molecular weight.
 """
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy.testing import assert_allclose
 
@@ -36,5 +36,3 @@ def test_call(self):
 
 
 # run if called from command-line
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_draw/test_dendrogram.py b/tests/test_draw/test_dendrogram.py
index e1579495f..35bc45757 100644
--- a/tests/test_draw/test_dendrogram.py
+++ b/tests/test_draw/test_dendrogram.py
@@ -1,4 +1,4 @@
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy.testing import assert_allclose
 
diff --git a/tests/test_draw/test_dotplot.py b/tests/test_draw/test_dotplot.py
index af1c1814c..6b08408ba 100644
--- a/tests/test_draw/test_dotplot.py
+++ b/tests/test_draw/test_dotplot.py
@@ -1,4 +1,4 @@
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3 import DNA, make_unaligned_seqs
 from cogent3.core.alignment import Aligned, ArrayAlignment
diff --git a/tests/test_draw/test_logo.py b/tests/test_draw/test_logo.py
index aa6d6eaf7..2b3cd7cd4 100644
--- a/tests/test_draw/test_logo.py
+++ b/tests/test_draw/test_logo.py
@@ -1,4 +1,4 @@
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.draw.letter import get_character
 from cogent3.draw.logo import _char_hts_as_lists, get_logo
diff --git a/tests/test_draw/test_shapes.py b/tests/test_draw/test_shapes.py
index 77a6f981d..f46e99a90 100644
--- a/tests/test_draw/test_shapes.py
+++ b/tests/test_draw/test_shapes.py
@@ -1,4 +1,4 @@
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy.testing import assert_allclose
 
diff --git a/tests/test_evolve/test_best_likelihood.py b/tests/test_evolve/test_best_likelihood.py
index e149a449d..7466c0752 100644
--- a/tests/test_evolve/test_best_likelihood.py
+++ b/tests/test_evolve/test_best_likelihood.py
@@ -2,7 +2,7 @@
 
 import math
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy.testing import assert_allclose
 
diff --git a/tests/test_evolve/test_coevolution.py b/tests/test_evolve/test_coevolution.py
index 8a3366eb2..e05dc8fb9 100755
--- a/tests/test_evolve/test_coevolution.py
+++ b/tests/test_evolve/test_coevolution.py
@@ -1,6 +1,6 @@
 from os import environ, remove
 from tempfile import NamedTemporaryFile, mktemp
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy import (
     arange,
@@ -92,6 +92,11 @@
     validate_tree,
 )
 from cogent3.maths.stats.number import CategoryCounter
+from cogent3.parse.fasta import MinimalFastaParser
+
+
+def _fasta_to_dict(data):
+    return dict(MinimalFastaParser(data.splitlines()))
 
 
 class CoevolutionTests(TestCase):
@@ -5294,7 +5299,8 @@ def test_resampled_mi_interface(self):
 >HH1R_RAT
 ----------MSFANTSSTFEDKMCEGNRTAMASPQLLPLVVVLSSISLVTVGLNLLVLYAVHSERKLHTVGNLYIVSLSVADLIVGAVVMPMNILYLIMTKWSLGRPLCLFWLSMDYVASTASIFSVFILCIDRYRSVQQPLYLRYRTKTRASATILGAWFFSFLWVIPILGWHHFM--EL-EDKCETD------FYNVTWFKIMTAIINFYLPTLLMLWFYVKIYKAVRRHLRSQYVSGLHLNRERKAAKQLGFIMAAFILCWIPYFIFFMVIAFCKSC-CSEPMHMFTIWLGYINSTLNPLIYPLCNENFKKTFKKILHIRS-----------------------"""
 
-gpcr_aln = ArrayAlignment(data=gpcr_ungapped.split("\n"), moltype=PROTEIN)
+
+gpcr_aln = ArrayAlignment(data=_fasta_to_dict(gpcr_ungapped), moltype=PROTEIN)
 
 myos_data = """>gi|107137|pir||A37102
 LSRIITRIQA
@@ -5525,10 +5531,7 @@ def test_resampled_mi_interface(self):
 >gi|9971579|dbj|BAB12571.1|
 LAALVTMTQA"""
 
-myos_aln = ArrayAlignment(data=myos_data.split("\n"), moltype=PROTEIN)
+myos_aln = ArrayAlignment(data=_fasta_to_dict(myos_data), moltype=PROTEIN)
 
 # a randomly generated tree to use in tests
 tree20_string = "(((0:0.5,1:0.5):0.5,(((2:0.5,3:0.5):0.5,(4:0.5,(5:0.5,6:0.5):0.5):0.5):0.5,((7:0.5,8:0.5):0.5,((9:0.5,((10:0.5,11:0.5):0.5,12:0.5):0.5):0.5,13:0.5):0.5):0.5):0.5):0.5,(((14:0.5,(15:0.5,16:0.5):0.5):0.5,17:0.5):0.5,(18:0.5,19:0.5):0.5):0.5);"
-
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_evolve/test_distance.py b/tests/test_evolve/test_distance.py
index c78b07073..8bc121d7b 100644
--- a/tests/test_evolve/test_distance.py
+++ b/tests/test_evolve/test_distance.py
@@ -1,9 +1,10 @@
 import os
 import warnings
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 import numpy
+import pytest
 
 from numpy.testing import assert_allclose, assert_equal
 
@@ -923,3 +924,79 @@ def test_to_table(self):
 
 if __name__ == "__main__":
     main()
+
+
+@pytest.fixture
+def min_working_example_dmat():
+    return DistanceMatrix(
+        {
+            ("A", "B"): 1,
+            ("A", "C"): 2,
+            ("B", "C"): 3,
+        }
+    )
+
+
+def test_max_pair_mwe(min_working_example_dmat):
+    assert min_working_example_dmat.max_pair() == ("B", "C")
+
+
+def test_min_pair_mwe(min_working_example_dmat):
+    assert min_working_example_dmat.min_pair() == ("A", "B")
+
+
+def test_max_pair_has_max_val():
+    aln = load_aligned_seqs("data/primate_brca1.fasta", moltype="dna")
+    dmat = aln.distance_matrix()
+    got = dmat[dmat.max_pair()]
+    expect = dmat.array.max()
+    assert got == expect
+
+
+def test_min_pair_has_min_val():
+    aln = load_aligned_seqs("data/primate_brca1.fasta", moltype="dna")
+    dmat = aln.distance_matrix()
+    got = dmat[dmat.min_pair()]
+    numpy.fill_diagonal(dmat.array, numpy.inf)
+    expect = dmat.array.min()
+    assert got == expect
+
+
+def test_min_max_pair_single_pair():
+    dmat = DistanceMatrix({("A", "B"): 2})
+    assert dmat.max_pair() == ("A", "B")
+    assert dmat.min_pair() == ("A", "B")
+
+
+def test_max_pair_tied():
+    dmat = DistanceMatrix(
+        {
+            ("A", "B"): 1,
+            ("A", "C"): 1,
+            ("A", "D"): 3,
+            ("B", "C"): 3,
+            ("B", "D"): 2,
+            ("C", "D"): 2,
+        }
+    )
+
+    got = set(dmat.max_pair())
+    expect = {frozenset(("B", "C")), frozenset(("A", "D"))}
+    assert got in expect
+
+
+def test_min_pair_tied():
+    dmat = DistanceMatrix(
+        {
+            ("A", "B"): 1,
+            ("A", "C"): 1,
+            ("A", "D"): 3,
+            ("B", "C"): 3,
+            ("B", "D"): 2,
+            ("C", "D"): 2,
+        }
+    )
+
+    got = set(dmat.min_pair())
+    expect = {frozenset(("A", "B")), frozenset(("A", "C"))}
+    assert got in expect
diff --git a/tests/test_evolve/test_likelihood_function.py b/tests/test_evolve/test_likelihood_function.py
index a232d245d..0a3d8acc3 100644
--- a/tests/test_evolve/test_likelihood_function.py
+++ b/tests/test_evolve/test_likelihood_function.py
@@ -515,7 +515,7 @@ def test_simulate_alignment1(self):
 
     def test_simulate_alignment2(self):
         "Simulate alignment with dinucleotide model"
-        al = make_aligned_seqs(data={"a": "ggaatt", "c": "cctaat"})
+        al = make_aligned_seqs(data={"a": "ggaatt", "c": "cctaat"}, moltype="dna")
         t = make_tree("(a,c);")
         sm = substitution_model.TimeReversibleDinucleotide(mprob_model="tuple")
         lf = sm.make_likelihood_function(t)
@@ -527,7 +527,7 @@ def test_simulate_alignment_root_sequence(self):
         """provide a root sequence for simulating an alignment"""
 
         def use_root_seq(root_sequence):
-            al = make_aligned_seqs(data={"a": "ggaatt", "c": "cctaat"})
+            al = make_aligned_seqs(data={"a": "ggaatt", "c": "cctaat"}, moltype="dna")
             t = make_tree(treestring="(a,c);")
             sm = substitution_model.TimeReversibleDinucleotide(mprob_model="tuple")
             lf = sm.make_likelihood_function(t)
@@ -772,7 +772,7 @@ def test_make_discrete_markov(self):
 
     def test_exercise_set_align(self):
         "lf.set_align should work for different models"
-        al = make_aligned_seqs(data={"a": "ggaatt", "c": "cctaat"})
+        al = make_aligned_seqs(data={"a": "ggaatt", "c": "cctaat"}, moltype="dna")
         t = make_tree(treestring="(a,c);")
         for klass in [CNFGTR, Y98, MG94HKY]:
             sm = klass()
@@ -2243,10 +2243,10 @@ def test_simulate_alignment3():
     t = make_tree(treestring="(a:0.4,b:0.3,(c:0.15,d:0.2)edge.0:0.1)root;")
     al = make_aligned_seqs(
         data={
-            "a": "g--cactat?",
-            "b": "---c-ctcct",
-            "c": "-a-c-ctat-",
-            "d": "-a-c-ctat-",
+            "a": "G--CACTAT?",
+            "b": "---C-CTCCT",
+            "c": "-A-C-CTAT-",
+            "d": "-A-C-CTAT-",
         }
     )
     sm = TimeReversibleNucleotide(recode_gaps=True)
diff --git a/tests/test_evolve/test_models.py b/tests/test_evolve/test_models.py
index fc3a8b6c2..6dc29e4b1 100644
--- a/tests/test_evolve/test_models.py
+++ b/tests/test_evolve/test_models.py
@@ -1,4 +1,4 @@
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.evolve import models as models_module
 from cogent3.evolve.models import (
@@ -110,7 +110,7 @@ def get_sample_model_types(mod_type=None):
 class AvailableModelsTest(TestCase):
     def test_model_abbreviation(self):
         """make sure getting model abbreviations that exist"""
-        got = set(available_models().tolist("Abbreviation"))
+        got = set(available_models().to_list("Abbreviation"))
         expect = set(["JC69", "CNFGTR", "DSO78"])
         self.assertTrue(expect < got)
 
@@ -124,7 +124,7 @@ def test_model_by_type(self):
     def test_model_description(self):
         """correctly grabs function descriptions"""
         all_available = available_models()
-        for abbrev, desc in all_available.tolist(["Abbreviation", "Description"]):
+        for abbrev, desc in all_available.to_list(["Abbreviation", "Description"]):
             func = getattr(models_module, abbrev)
             doc = func.__doc__.split()
             self.assertEqual(desc.split(), doc)
diff --git a/tests/test_evolve/test_newq.py b/tests/test_evolve/test_newq.py
index dc361065f..1df96fbc6 100644
--- a/tests/test_evolve/test_newq.py
+++ b/tests/test_evolve/test_newq.py
@@ -2,7 +2,7 @@
 
 import warnings
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy import dot, ones
 from numpy.testing import assert_allclose
diff --git a/tests/test_evolve/test_ns_substitution_model.py b/tests/test_evolve/test_ns_substitution_model.py
index 73451441b..d7dfb0fb0 100644
--- a/tests/test_evolve/test_ns_substitution_model.py
+++ b/tests/test_evolve/test_ns_substitution_model.py
@@ -1,6 +1,6 @@
 import warnings
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 import numpy
 
@@ -44,13 +44,11 @@ def _make_likelihood(model, tree, results, is_discrete=False):
     return lf
 
 
-_aln = """>Human
-ATGCGGCTCGCGGAGGAGCGGGCCGCGCTC------GCGGCGGAGAACGCGGATGGGGAACCCGGC---GCCGACCGACGACTGCGACTCCTGGGGACCTACGTGGCCATGAGCCTGCGGCCGGCTGCGGGCGCCTGGGAGCGTTGCGCGGGGAGTGCTGAGGCGGAGCAGCTGCTCCAGGCCTTCCTG---GGCCGCGATGCTGCCGAGGGGCCGCGGCCG------CTGCTGGTGGTGCGGCCCGGGCCCAGGGGCCTGGCAATACGCCCCGGGCTGGAGGTGGGACCTGAGTCGGGCCTGGCTGGCGCTAAGGCGCTTTTTTTCCTTCGCACCGGG---CCCGAGCCTCCAGGGCCCGACAGCTTCCGCGGCGCAGTGGTCTGCGGGGACCTGCCCGCGGCACCTCTGGAGCACCTAGCCGCGCTGTTCTCGGAGGTTGTTCTACCCGTCCTGGCCAATGAGAAGAATCGCCTAAACTGGCCCCACATGATATGTGAGGATGTCAGGCGGCACGCCCACAGCCTCCAATGTGACCTCTCAGTTATACTTGAGCAAGTGAAGGGAAAAACTTTGCTGCCTCTTCCAGCAGGCTCAGAAAAAATGGAGTTTGCGGATTCCAAAAGTGAGACAGTCTTGGATTCTATAGATAAGTCAGTCATCTATGCCATTGAGTCTGCAGTGATCAAATGGAGCTACCAAGTCCAGGTGGTACTCAAGAGAGAGTCTTCCCAGCCACTCTTACAAGGGGAGAATCCCACCCCTAAGGTGGAGTTGGAGTTCTGGAAGAGCAGGTATGAAGATCTGAAATACATCTATAATCAACTGAGAACAATAACGGTGAGGGGCATGGCCAAGCTCCTGGACAAGCTTCAGAGTAGCTACTTTCCAGCTTTCAAAGCCATGTACAGAGATGTTGTTGCAGCTCTAGCAGAGGCACAGGACATCCATGTGCACCTGATACCGCTCCAGCGCCACCTGGAAGCTCTGGAGAATGCAGAATTTCCGGAGGTGAAGCCCCAGCTGCGGCCCCTGCTCCACGTGGTCTGTCTGATTTGGGCCACATGCAAGTCCTACCGCTCCCCGGGAAGGCTGACTGTGCTGCTCCAGGAGATTTGCAACCTTCTCATCCAGCAGGCCTCTAATTATCTCAGCCCAGAAGACCTGCTGAGAAGTGAGGTAGAAGAAAGTCAGAGAAAACTGCAAGTGGTCTCAGACACTTTGAGCTTCTTCAAGCAAGAGTTTCAGGACAGAAGGGAGAATCTCCACACTTACTTCAAAGAGAACCAGGAAGTCAAGGAATGGGATTTCCAGTCTTCTTTGGTCTTTGTGCGATTGGATGGCTTCCTGGGACAACTGCACGTGGTGGAGGGTCTTCTGAAGACGGCCCTGGATTTCCACAAACTGGGAAAGGTGGAGTTCAGCGGCGTCAGAGGGAATGCTCTGAGTCAGCAGGTCCAGCAAATGCATGAAGAATTTCAAGAGATGTACAGGCTTCTCTCAGGATCCTCCTCCGACTGCCTGTACCTCCAAAGCACGGACTTTGAAAATGACGTCTCTGAATTTAACCAGAAAGTAGAAGATCTTGACCGAAGATTGGGGACTATCTTTATTCAAGCTTTTGATGATGCACCTGGCTTGGAGCATGCCTTTAAGCTGCTAGACATAGCAGGAAACCTCCTTGAAAGACCGCTGGTAGCGAGGGATACATCTGATAAATACCTGGTCCTCATCCAAATGTTCAACAAAGATCTGGATGCAGTGAGGATGATCTACAGTCAGCACGTCCAGGAGGAAGCAGAACTTGGGTTCTCCCCGGTGCACAAGAACATGCCCACCGTGGCTGGCGGCCTCCGCTGGGCACAGGAGCTGAGGCAGCGCATCCAGGGTCCTTTCAGCAACTTTGGACGCATCACACACCCTTGCATGGAATCTGCAGAAGGAAAGCGAATGCAACAAAAATATGAAGATATGCTGTCATTGCTAGAAAAGTATGAGACAAGACTTTATGAGGATTGGTGCCGGACAGTATCAGAGAAGTCACAGTACAATCTTTCCCAACCACTTCTAAAACGTGACCCAGAGACGAAGGAGATCACTATCAACTTTAACCCACAGCTGATTTCAGTGCTGAAAGAAATGAGCTATCTTGAACCCAGAGAGATGAAACACATGCCTGAGACAGCAGCAGCCATGTTCTCCTCCAGGGATTTCTATCGGCAGCTTGTGGCTAATTTAGAGTTGATGGCAAATTGGTACAACAAGGTTATGAAAACTCTGCTGGAGGTGGAATTTCCATTAGTGGAGGAAGAGCTGCAAAATATTGATCTCCGCCTCAGAGCAGCAGAGGAGACTTTGAACTGGAAAACAGAAGGCATTTGCGATTATGTCACTGAAATCACCAGTAGTATTCATGATCTTGAACAAAGAATTCAGAAAACTAAAGACAATGTGGAAGAGATCCAAAACATCATGAAAACATGGGTGACTCCAATATTTAAGACAAAAGATGGAAAAAGGGAATCCCTTCTTTCTCTGGATGATCGGCATGATCGAATGGAAAAATATTACAATCTCATCAAGGAATCTGGCCTTAAGATCCACGCCCTTGTTCAGGAAAACCTGGGTCTATTTTCAGCAGACCCAACCTCCAATATCTGGAAGACTTATGTTAACTCTATTGACAATTTGTTGCTGAATGGATTCTTTCTTGCCATTGAGTGCTCCCTCAAGTATCTTCTGGAAAATACTGAGTGTAAGGCAGGACTTACCCCAATATTTGAAGCACAACTGAGTCTAGCCATCCCAGAGCTAGTTTTCTATCCGTCTCTGGAGTCTGGAGTGAAGGGGGGTTTCTGTGACATTGTTGAGGGTCTCATCACCAGCATTTTTAGGATACCATCTCTGGTGCCACGGCTTTCCCCACAAAATGGCTCTCCTCACTATCAGGTCGACCTGGACGGTATACCAGATTTGGCAAACATGCGGCGCACACTCATGGAGAGAGTCCAGAGAATGATGGGCCTCTGCTGTGGCTATCAGAGCACCTTCAGCCAGTATTCGTACCTCTATGTGGAGGACCGGAAGGAGGTTCTGGGTCAGTTTCTGCTGTACGGGCACATCCTCACTCCGGAAGAAATTGAAGACCATGTGGAAGATGGCATCCCAGAGAACCCTCCCCTCCTTTCTCAGTTTAAAGTGCAAATCGACTCCTATGAAACGCTCTATGAAGAGGTGTGCAGGCTGGAACCCATCAAGGTGTTTGACGGCTGGATGAAAATTGATATTCGACCCTTTAAGGCATCTCTGCTGAATATTATTAAGAGGTGGAGCCTCCTGTTCAAACAGCATCTTGTGGACCACGTCACTCACAGCTTGGCCAACCTGGATGCGTTTATAAAGAAGAGTGAGAGCGGCTTACTCAAGAAAGTTGAAAAAGGAGATTTCCAAGGCTTGGTTGAGATCATGGGACACCTTATGGCTGTTAAAGAACGGCAGAGTAACACTGATGAGATGTTTGAGCCCTTAAAGCAGACTATTGAATTGCTGAAGACCTATGAACAAGAATTGCCAGAAACAGTGTTTAAGCAGCTGGAGGAGCTGCCTGAGAAATGGAACAACATAAAAAAGGTGGCCATTACTGTGAAGCAGCAGGTGGCCCCACTGCAGGCAAATGAAGTGACACTCCTCCGCCAGAGGTGCACAGCCTTCGATGCAGAACAGCAGCAATTCTGGGAGCAATTCCACAAAGAAGCCCCGTTCAGGTTTGATAGCATCCACCCTCATCAAATGCTGGATGCCAGGCACATCGAGATCCAGCAGATGGAATCCACTATGGCCTCCATTTCTGAGTCTGCCAGCTTATTTGAAGTCAATGTCCCTGACTATAAGCAGCTGAGGCAGTGCAGGAAGGAGGTCTGCCAGCTGAAGGAGCTCTGGGACACCATTGGAATGGTGACCTCCAGCATCCATGCCTGGGAGACCACACCCTGGAGGAATATCAACGTGGAAGCCATGGAGTTGGAGTGCAAACAGTTTGCCCGGCATATCCGAAACCTGGACAAGGAGGTCAGGGCCTGGGATGCATTCACAGGCCTGGAAAGCACTGTGTGGAACACGCTGAGCTCCCTGAGGGCAGTAGCTGAGCTGCAGAATCCAGCCATCCGGGAGCGGCACTGGAGGCAGCTGATGCAGGCCACCGGTGTGAGCTTCACTATGGACCAGGACACCACCCTAGCGCACCTGCTGCAGCTCCAGCTGCACCACTATGAGGATGAGGTCCGGGGCATTGTGGACAAAGCTGCAAAAGAGATGGGTATGGAGAAAACCTTAAAGGAGCTGCAGACTACCTGGGCTGGCATGGAATTCCAGTATGAGCCCCACCCACGGACCAATGTCCCCCTCCTGTGCTCTGATGAGGACCTCATAGAGGTTCTGGAGGATAATCAAGTTCAACTTCAGAACCTGGTGATGTCCAAGTATGTTGCTTTCTTCTTGGAGGAGGTGTCGGGCTGGCAGAAGAAGCTGTCCACAGTGGACGCTGTCATCTCTATCTGGTTTGAAGTGCAGCGAACATGGACTCACCTGGAAAGCATATTCACTGGATCTGAAGATATTCGGGCACAGCTACCCCAGGATTCTAAAAGGTTTGAAGGCATCGACATTGACTTTAAAGAGCTAGCTTATGATGCCCAGAAAATTCCAAATGTAGTGCAAACCACCAACAAGCCAGGCCTGTATGAAAAGCTGGAGGATATTCAGGGCAGATTGTGCCTGTGTGAGAAGGCCCTGGCAGAGTACCTCGACACCAAGAGGCTTGCCTTCCCGCGGTTTTACTTTCTCTCCTCCTCCGATCTGTTAGACATCCTTTCCAACGGCACAGCTCCACAACAGGTTCAACGTCACCTTTCCAAACTCTTTGACAACATGGCCAAGATGCGATTCCAGCTAGATGCCAGTGGGGAACCAACCAAGACAAGCCTCGGCATGTACAGCAAAGAAGAGGAGTATGTGGCTTTCAGTGAGCCCTGTGACTGCAGCGGGCAGGTAGAAATATGGCTGAACCATGTCCTTGGTCACATGAAGGCCACTGTGAGGCATGAGATGACAGAAGGTGTAACTGCCTATGAAGAAAAGCCGAGGGAGCAGTGGCTTTTTGACCACCCAGCTCAGGTGGCCCTGACCTGTACTCAGATCTGGTGGACAACAGAAGTGGGCATGGCATTTGCCAGGCTGGAGGAAGGCTATGAGAGTGCCATGAAGGACTATTATAAGAAGCAAGTGGCCCAGCTCAAAACCCTTATCACCATGCTGATTGGCCAGCTCTCCAAGGGAGACCGGCAGAAGATTATGACTATATGCACCATCGATGTGCATGCCCGGGATGTGGTAGCCAAGATGATTGCTCAGAAGGTAGACAATGCCCAGGCTTTCCTCTGGCTGTCTCAGCTGCGCCATCGTTGGGATGACGAGGTCAAACACTGCTTTGCCAACATCTGTGATGCCCAGTTTTTGTATTCCTATGAGTACCTGGGAAACACACCTCGCTTGGTGATCACACCTTTGACT------GACAGGTGCTACATCACCCTCACCCAGTCCCTGCACCTGACCATGAGTGGGGCTCCCGCAGGACCTGCAGGCACAGGCAAGACCGAGACCACCAAGGACCTGGGCCGCGCACTGGGCATCCTGGTCTATGTGTTCAACTGCTCGGAGCAGATGGATTACAAGTCTTGTGGCAACATCTACAAAGGCCTTGCTCAGACTGGTGCCTGGGGCTGCTTTGATGAGTTTAATCGAATCTCCGTGGAGGTCTTGTCAGTGGTGGCAGTGCAGGTAAAAAGCATTCAAGATGCGATTAGAGATAAGAAGCAGTGGTTCAGCTTCCTTGGGGAGGAGATCAGCCTGAATCCTTCTGTCGGTATCTTCATCACCATGAACCCAGGCTATGCTGGCCGCACAGAGCTGCCAGAGAATCTCAAGTCTCTCTTCAGGCCTTGTGCAATGGTGGTTCCAGACTTTGAATTGATCTGTGAAATCATGCTGGTGGCAGAAGGATTCATTGAAGCCCAGTCATTAGCCAGAAAGTTCATCACTCTTTACCAGTTGTGCAAAGAGCTTCTCTCCAAACAGGATCACTACGACTGGGGCCTACGGGCCATCAAGTCCGTGCTGGTGGTGGCAGGATCCCTGAAGAGAGGAGACCCTGACCGGCCTGAGGACCAGGTCCTGATGCGCTCCTTGCGGGATTTCAACATCCCCAAGATTGTGACTGATGACATGCCCATCTTCATGGGCCTGATCGGGGACCTCTTTCCCGCCCTGGATGTCCCCCGGAGGAGAGACCCCAACTTCGAAGCTTTGGTTAGGAAGGCGATAGTGGATCTGAAGCTCCAGGCTGAGGACAACTTTGTGCTCAAGGTGGTCCAGCTGGAGGAGCTCCTGGCTGTGCGGCACTCTGTA---TTTGTGGTGGGTGGCGCTGGT------ACCGGCAAGTCACAGGTGCTGAGGTCCTTGCACAAGACCTATCAG------ATCATGAAACGGCGCCCCGTCTGGACTGACCTCAATCCCAAAGCAGTCACAAATGATGAGCTCTTT---------------------------------------GGCATCATCAATCCAGCCACAGGAGAATGGAAGGATGGA---TTGTTCTCTTCCATCATGCGGGAGCTTGCCAACATCACCCATGATGGGCCCAAGTGGATTTTACTGGATGGCGACATAGATCCAATGTGGATTGAATCCCTGAATACTGTCATGGATGATAACAAGGTGCTGACATTGGCCAGCAATGAGAGGATTCCTCTGAACCCCACCATGAAGCTCCTCTTTGAGATCAGCCACCTGCGCACAGCCACTCCAGCAACTGTCTCTAGAGCAGGGATCTTGTACATCAACCCGGCAGACTTGGGATGGAACCCTCCAGTGAGCAGCTGGATTGAGAAGAGGGAAATCCAGACAGAGAGAGCCAACTTAACCATTTTGTTCGACAAGTATCTTCCAACCTGCCTAGACACACTCAGAACCAGGTTTAAGAAGATCATTCCCATCCCAGAGCAGAGCATGGTTCAGATGGTGTGTCACCTTCTGGAATGTCTCCTGACCACGGAGGACATCCCTGCAGACTGCCCTAAGGAAATTTATGAGCATTATTTTGTGTTTGCTGCCATCTGGGCTTTCGGCGGAGCAATGGTCCAAGATCAGCTTGTGGACTACCGGGCAGAGTTCAGCAAATGGTGGCTGACTGAGTTCAAAACAGTCAAGTTTCCTTCCCAAGGAACCATCTTTGACTATTACATCGACCCAGAGACCAAGAAATTCGAGCCTTGGTCCAAGCTCGTCCCCCAGTTCGAATTTGACCCCGAGATGCCCTTGCAGGCGTGTTTGGTGCACACGAGTGAGACCATCCGTGTGTGCTACTTCATGGAGCGGTTGATGGCGCGGCAGCGGCCTGTCATGCTGGTGGGCACGGCTGGCACTGGCAAGTCGGTGCTGGTGGGAGCTAAGCTGGCCAGCCTTGACCCCGAGGCATACCTGGTGAAAAACGTGCCATTCAACTACTACACCACGTCAGCAATGCTGCAGGCTGTCCTGGAGAAGCCTCTGGAAAAGAAGGCTGGCAGAAACTATGGCCCTCCAGGGAACAAGAAACTCATCTATTTCATTGATGACATGAACATGCCTGAGGTGGATGCCTACGGGACGGTGCAGCCCCACACCATCATCCGGCAGCATCTGGACTATGGCCACTGGTATGATCGGAGCAAGCTGTCCCTAAAGGAGATCACAAATGTACAGTATGTTTCCTGTATGAACCCCACGGCAGGCAGCTTCACCATCAACCCCCGGCTTCAGCGTCACTTCAGCGTGTTTGTCCTCTCCTTCCCGGGGGCAGATGCCCTGTCCTCTATCTACAGCATCATCCTCACTCAGCATCTGAAGCTCGGAAACTTCCCGGCGTCCCTGCAGAAATCCATCCCCCCACTGATCGATCTGGCCCTCGCCTTCCACCAGAAAATTGCTACCACCTTCCTACCCACAGGAATCAAATTCCACTACATCTTCAACCTCAGAGATTTTGCCAACATTTTCCAGGGCATTCTCTTCTCCTCAGTGGAATGTGTGAAATCCACATGGGATCTTATAAGGCTCTATCTGCATGAATCAAATCGAGTTTATCGGGATAAGATGGTAGAAGAAAAGGACTTTGATCTTTTTGATAAAATCCAGACAGAAGTGCTCAAGAAAACTTTTGATGATATTGAAGACCCTGTGGAGCAGACCCAAAGCCCGAACCTGTATTGTCACTTTGCAAATGGTATTGGGGAGCCCAAATACATGCCTGTACAGTCTTGGGAACTTTTGACCCAGACTCTGGTGGAGGCCTTGGAGAACCACAATGAAGTCAACACAGTGATGGACCTAGTTCTCTTTGAGGATGCCATGCGCCATGTCTGCCATATCAATCGCATCTTGGAGTCCCCGCGGGGAAATGCTCTGCTGGTTGGTGTAGGTGGGAGCGGCAAGCAGAGCCTGACAAGGCTGGCAGCTTTCATCAGCTCCATGGATGTCTTCCAGATCACACTGCGCAAAGGCTACCAGATCCAGGACTTCAAGATGGACCTGGCCAGCCTGTGTCTGAAAGCTGGAGTGAAGAATCTCAACACAGTGTTTCTCATGACTGATGCCCAAGTGGCTGATGAGAGGTTCCTTGTGCTCATCAATGATCTTTTGGCATCTGGGGAGATCCCAGATCTCTACTCTGATGATGAAGTTGAAAACATCATAAGCAATGTGAGGAATGAAGTCAAGAGCCAGGGTCTGGTTGACAACAGAGAGAACTGTTGGAAGTTCTTTATAGATCGGATCCGGCGACAGCTGAAGGTGACTCTCTGTTTCTCCCCTGTGGGAAACAAGCTAAGAGTCCGCAGCAGGAAGTTCCCAGCCATTGTGAACTGCACAGCCATCCACTGGTTCCACGAGTGGCCTCAGCAAGCATTGGAGTCTGTCAGCCTCCGCTTCTTGCAGAACACAGAGGGCATTGAGCCCACAGTAAAGCAGTCGATTAGCAAATTCATGGCCTTTGTCCACACAAGTGTCAACCAAACATCCCAGTCTTATCTGAGCAATGAACAGCGCTACAACTATACAACTCCCAAGTCCTTTCTGGAGTTCATCAGACTCTACCAGAGCTTGTTGCACAGGCACAGAAAAGAGCTCAAGTGCAAGACAGAGCGGTTGGAGAACGGGCTGCTGAAGCTGCATAGCACCTCTGCCCAGGTGGATGATCTGAAAGCAAAGCTGGCTGCCCAGGAAGTAGAGCTGAAGCAGAAAAATGAAGATGCAGACAAACTGATTCAGGTCGTGGGTGTGGAGACTGACAAAGTGAGCAGAGAGAAAGCCATGGCAGATGAAGAGGAGCAGAAGGTGGCCGTCATCATGCTAGAGGTGAAACAGAAGCAGAAGGACTGTGAGGAGGACCTGGCAAAGGCTGAGCCAGCACTCACAGCAGCGCAGGCAGCTCTCAACACCCTGAACAAGACCAACCTGACAGAGCTGAAGTCATTTGGCTCTCCGCCTCTGGCCGTCAGCAATGTCAGCGCTGCGGTGATGGTACTGATGGCTCCCAGGGGTAGGGTGCCCAAGGACCGGAGCTGGAAGGCTGCTAAGGTCACCATGGCCAAAGTGGATGGCTTCCTGGACTCGCTAATAAACTTCAACAAAGAGAACATTCACGAGAACTGCCTCAAAGCCATCAGGCCGTATCTGCAAGACCCCGAGTTCAATCCTGAGTTTGTGGCCACCAAATCCTATGCGGCTGCAGGCCTCTGCTCCTGGGTCATCAATATTGTGAGATTTTATGAGGTGTTCTGTGATGTGGAACCCAAGCGCCAGGCACTGAACAAAGCCACCGCGGACCTCACAGCTGCCCAGGAGAAGCTGGCTGCCATCAAAGCCAAGATCGCTCACCTTAATGAAAACCTGGCAAAGCTCACAGCCAGGTTTGAGAAAGCAACAGCAGACAAACTCAAATGTCAGCAAGAAGCCGAAGTGACCGCAGTCACCATCTCCCTTGCCAACCGCCTGGTTGGAGGACTCGCTTCTGAAAACGTGAGGTGGGCAGATGCCGTGCAGAACTTCAAACAGCAGGAAAGGACGTTATGTGGAGACATTTTACTTATAACGGCTTTCATTTCCTACCTTGGCTTCTTCACAAAGAAATACCGGCAGAGCCTCCTGGACAGAACTTGGAGGCCCTACCTGAGCCAGCTGAAAACTCCCATTCCAGTCACCCCAGCCCTGGATCCCCTGAGGATGCTGATGGATGATGCTGACGTGGCTGCCTGGCAGAACGAGGGCCTCCCAGCCGACCGCATGTCCGTGGAGAATGCCACCATTCTCATCAACTGTGAGCGCTGGCCACTCATGGTTGACCCTCAGCTACAAGGCATCAAATGGATCAAGAATAAATATGGTGAAGATCTCCGGGTCACGCAGATTGGTCAGAAAGGCTACCTTCAAATCATAGAGCAGGCCCTGGAAGCTGGAGCTGTGGTGCTGATTGAAAATCTAGAGGAGTCCATTGATCCTGTTCTGGGACCCCTGCTTGGGAGAGAAGTCATTAAAAAAGGACGATTCATTAAAATTGGAGACAAAGAATGTGAATACAATCCCAAGTTCCGGCTCATCCTCCACACCAAGCTGGCTAATCCTCACTACCAGCCTGAGCTGCAGGCTCAGGCCACCCTGATCAACTTCACCGTGACCAGGGATGGCCTGGAGGACCAGTTGCTGGCCGCTGTGGTCAGCATGGAGAGGCCAGACTTGGAGCAGCTGAAGTCCGATCTCACAAAGCAGCAGAATGGATTCAAAATTACCCTGAAAACGTTGGAAGACAGTCTTCTCTCTCGCCTCTCCTCCGCCTCTGGGAACTTCCTGGGAGAAACAGTGCTGGTGGAAAACCTAGAGATCACCAAGCAGACTGCTGCCGAAGTTGAGAAAAAGGTCCAGGAGGCCAAGGTGACTGAAGTGAAAATCAACGAGGCCCGAGAGCACTACCGGCCAGCAGCTGCCAGGGCCTCACTGCTCTACTTCATCATGAACGACCTCAGCAAGATCCATCCAATGTACCAGTTTTCTCTCAAGGCCTTCAGTATCGTCTTCCAGAAGGCTGTGGAG------AGGGCTGCTCCTGACGAAAGCCTCAGGGAGCGGGTGGCCAACCTAATAGACAGCATAACCTTCTCTGTGTACCAGTACACCATCCGCGGGCTCTTTGAGTGTGATAAGCTGACCTACCTTGCCCAGCTCACCTTTCAGATTCTCCTCATGAACCGAGAAGTCAATGCAGTGGAGTTGGATTTCCTGCTTCGATCTCCAGTGCAGACGGGCACCGCCAGCCCCGTGGAGTTCCTCTCCCATCAGGCGTGGGGAGCTGTCAAGGTACTTTCATCAATGGAAGAATTCTCTAATCTGGATCGGGACATAGAGGGATCTGCTAAGAGCTGGAAAAAGTTTGTGGAGTCCGAATGTCCTGAGAAAGAGAAGCTCCCACAGGAGTGGAAGAACAAGACAGCCCTGCAGCGCCTCTGCATGCTGAGAGCCATGCGGCCCGACCGGATGACCTATGCTTTGCGAGATTTTGTTGAAGAGAAGTTAGGAAGCAAATACGTGGTGGGAAGAGCCCTAGATTTTGCAACCTCATTTGAAGAATCGGGACCAGCCACTCCTATGTTTTTCATCCTGTCTCCAGGGGTGGACCCACTGAAGGATGTAGAAAGTCAAGGAAGAAAACTTGGATACACCTTCAACAATCAGAACTTTCACAACGTGTCTTTGGGGCAAGGACAGGAAGTGGTGGCTGAGGCTGCGCTGGACCTCGCTGCCAAGAAAGGTCACTGGGTTATTTTGCAGAACATTCACCTGGTGGCCAAGTGGCTCAGCACCCTGGAGAAGAAGCTGGAGGAGCACAGTGAGAACAGCCACCCAGAGTTCAGGGTCTTCATGAGTGCAGAGCCAGCACCCTCCCCTGAGGGCCACATCATCCCCCAGGGCATCCTGGAGAACTCCATTAAGATCACCAATGAGCCCCCCACGGGCATGCATGCCAACCTGCACAAGGCCCTGGACAACTTCACTCAGGACACTCTGGAGATGTGTTCTCGGGAGACGGAGTTTAAGAGCATCCTCTTTGCTCTTTGTTACTTCCATGCGGTGGTGGCAGAAAGACGAAAATTTGGGCCCCAGGGATGGAATCGCTCATACCCCTTTAACACTGGAGACCTCACTATCTCTGTGAATGTCCTCTACAACTTCCTGGAGGCCAACGCAAAGGTCCCCTATGATGATTTGCGCTACCTGTTTGGAGAGATCATGTATGGAGGCCATATCACAGATGACTGGGACAGAAGACTCTGCAGAACCTACCTGGGGGAATTCATTCGACCAGAAATGTTAGAAGGAGAACTGTCTTTGGCCCCAGGGTTCCCACTCCCAGGCAACATGGACTACAATGGTTATCATCAGTACATCGATGCTGAGCTGCCCCCAGAATCCCCCTACCTCTATGGCCTCCACCCGAACGCAGAGATTGGCTTCCTGACCCAAACCTCAGAAAAGCTCTTCCGCACTGTGCTGGAGCTGCAGCCTCGGGACAGCCAGGCCAGAGACGGAGCGGGCGCCACAAGAGAAGAAAAGGTCAAGGCACTTCTGGAAGAAATATTGGAGCGGGTGACAGACGAGTTTAACATCCCAGAACTGATGGCCAAAGTGGAGGAGCGCACCCCTTACATTGTAGTTGCCTTCCAGGAGTGTGGCCGGATGAATATCCTCACCAGAGAGATTCAGCGCTCACTGAGGGAGCTGGAGCTCGGCTTAAAGGGGGAGCTGACTATGACCAGCCACATGGAGAACTTACAGAATGCCCTGTACTTCGATATGGTGCCAGAGTCCTGGGCTAGACGAGCCTACCCTTCCACAGCAGGCCTGGCAGCCTGGTTTCCAGACCTCCTCAACAGAATCAAGGAGCTAGAGGCTTGGACGGGTGACTTTACAATGCCCTCCACTGTGTGGCTGACAGGCTTCTTCAACCCCCAGTCGTTCCTGACTGCCATCATGCAGTCCACGGCTCGCAAGAATGAGTGGCCACTGGACCAGATGGCCCTGCAATGTGACATGACGAAGAAGAACAGAGAAGAGTTTAGGAGTCCTCCTCGGGAAGGGGCCTACATCCATGGCCTCTTCATGGAAGGTGCCTGCTGGGACACACAGGCTGGGATCATTACAGAGGCAAAGCTGAAGGATCTGACACCCCCTATGCCTGTGATGTTCATCAAGGCCATTCCTGCAGATAAGCAGGACTGCCGCAGTGTCTATTCCTGTCCTGTGTACAAGACTAGTCAGCGGGGACCCACCTACGTGTGGACTTTCAACCTGAAGACTAAGGAAAACCCATCCAAGTGGGTTCTGGCTGGAGTAGCCTTGCTTCTCCAGATT
->Mouse
-ATGCCCGGCGCCAAGGAGCAGGCAGCGCTG---------GCGGAGTCTGGGGACGAGGAGCCTGGA------GACCCGAGGCTGCGGCTTCTGGGGACTTTTGTGGCTCGGAGCCTGCGTCCGGCCGCGGGCACCTGGGAGCGCTGTGCAGGCACAGCCGAGGCGGGGAGGCTGCTGCAGGCCTTCCTG---GATCACAACGCTGCCTCGGATCCGCGGCCA------CTGCTGGTGGTTCAGTCCGGGCCCGGGGGCCTGGTGGTGACACCCGGTCTAGACGCAGGACCAGAGCCCAGCCGAGCTCGCGCCAAGGGGCTCTTTTTCTTGCGCACTAAG---TCCGAGCCTCCGGGAAATCACAGCCTCCGCGGCACGGTGCTCTGCGGGGACCTACCCGCGGTGCCACTGGAGCACCTGGCCCCGCTGCTCTCAGAGGTCATTATTCCTGTCCTGGCAAATGAAAAGAACCATTTAGAATGGCCCCACATGGTATGTCAAGACATCAGACATCATGCCCACACCCTGAAGTCTGACCTCCTAGTGATCTTTGAGCACATGAAGGGGAGAACCTTGCTGCCTCTTCCAGTTGGCTCAGAAAAACTGGAGTTTGTGGATGGCCACAGTGAGCCAGTCTCAGATGCCATAGACAAGTCAACTCTCTATGCTGTGGAGTCTGCAGTGATCAAATGGAGCCACCAAGTCCAGGTGGTACTCAAGAGGGAGTCTTCTCAGGCACTCATACAAGGACAAAATCCCACCCCCAAGGTGGAGCTGGAGTTCTGGAAGAGCAGGTGTGAGGACCTGGAACACATTTATAATCAACTAATGACAATCAAGGTGAAGGGAATGGCTGAACTCCTGGACAAACTTCAGAGCAGCTACTTGCCAGCTTTCAAAGCCATGTTCAGAGACGTTGAAGCAGCCCTGACCGAGGCCCAGGACATCCATGTGCACCTGTTACCTCTCCAGCAACACCTGGACATCCTGGAAAACGTGGAGTTTCCCAAGGTGAAGGGCAGGCTGCGGCCTCTGCTCCATGTGGTCTGTCTGATTTGGGCCACCTGCAAATGGTACCGTTCCCCTGGGAGGCTCACAGTGCTGCTCCAAGAAATCTGCAACCTCCTCATCCAGCAGGCCTCTAATTACCTCAGCCCAGAAGACCTCCTGAGAAGTGAGGTGGAAGAGAGTCAGAAAAAACTGCAAGTGGTCTCAGATACCTTAAGCTTCTTCAAACAGGCATTCCAGGACAGAAGGGAGCACCTCCACACTTACTTCAAGGAGGATTCTGAAGTCAGGGTGTGGGATTTCCAAGCATCTCTGGTGTTTGTGCGACTGGATGGCTTTCTGGGCCGAGTGCACATGGTGGAGGATCTTCTGAAGACAGCCTTGGATCTCAACAATCTGGAAAAGCTTGAGTTCAGTGGCCTCAGAGGAAACTCCCTGAGTCAGAAAGTCCAACGCATGCATGAGGAATTTGAGGAGATGTACAAGGTCTTCTTGGACTGCTCCTATGACTGTTTGGACCCCAAGGGCACGGAATTTGAAAATGATGTCTGTGAGTTTAACAAAAGAGTGGAAGATCTTGACCGGAGACTGGGGACTATCTTAATTCAAGCTTTTGATGATGCACCTGATGTGGAACATGCCTTTAAGCTACTGGACATCACAGGAACCCTCATCAAAAGACCCCTGGTAGCACAGGATGTATCACAAAAATACCTGGCCCTCATCCGAATGTTCAGCACAGAACTGGATGCTGTGAGGGTCATCTACAGTCAGCACATCCAGAAGGAGGCAGAGCATGGATTCTCCCCCGTGCACAAGAACATGCCCACTATGGCTGGCGGCATCTGCTGGGCACAGGAACTGAGGCAGCGCGTCAAGGGTCCCTTTGGCAACTTCAAAAACATACCACATCTGTACTTGCAATCTGCTGAAGGAAAGCGAATGATACAAAAATACGAAGACTTGCTCTCCCTGCTAGAAGAGTATGAGAGAAGACTTTATGAGGACTGGTGTCAGACGGTATCTGAAAAGTCACAGTACAATCTTTCCCTACCTCTTTTGCATCGTGACCCCAACACAAAGCAGCTCTCTGTCAACTTTAACCCACAGCTGATTTCAGTGTTGAAAGAAATGAACTATCTTCAGCCCAGTGAGGTGAAAACCATCCCCGAGACCGCAGCAGCCATGTTCTCCTCCAGGGAATTCTATCGTCAGCTTGTGGCCAACTTGGAGTTGATGGCAAATTGGTACAACAAGGTTATAAAAATTCTGCTGGAGGTGGAATTTCCACTAGTGGAGGAAGAACTGCAAAATATTGATCTCCGCCTGAGAGCTGCAGAGGAGACTCTGAGCTGGAAAACAGAAGGCATTTGGGATTATGCTATGCAAATAACCAATAGCATTCATGACCTGGAACAAAGAATTCAGAAGACAAAAGACAATGTGGAAGAGATTCAAAACATCATGAAAACATGGGTGTCTCCAATATTCAAGAGAAAAGATGGGAAAAAAGAATGGCCCCTTTCTCTGGATGATCAGCAGGATCACATGGAAAAATACTACAGTCTCATCCAGGAATCTGGCCTTAAGATTCACGCTCTTGTTCAGGAAAACCTGGTTCTGTTTGCAGCAGACCCAGCATCCAGCATTTGGAAGTCTTACGTGAACTACATTGATTCCATGTTGTTGGATGGATTTTTTCTTGCCATTGAGTGTTCTCTCAAATATCTATTGGAAAACACTGAATGCAAGCCTGGACTCACCCCAGTATTTGAAGCACAGCTCAACCTTGTCACCCCAGAATTAGTTTTCCACCCCTCTCTGGACTCTGGGGTAAAGGGAGGCTTATATGACATTGTCCAGAGTCTTGTCACCAGAATTTTTGCTATGCCATCCCTCGTGCCACGGCTTTCCCCACACAGTGGCTCTCCTCACTATCAGGGTGACCTAGAGGACATGGCCGACTTAGCTGGTCTTCGGAGTGTGCTCATGGAGAGGGTACAGAATATGATGACCCTCTGCTGTGGCTATAGAAATACCCTCAGCCAGTATTCTTACCTGTATGTGGAGGATAGGAAGGAGATTCTTGGTCAGTTTCTGCTCTATGGGCATGTCCTCACACCTGAAGAGATAGAAGCCCATGCCGAAGACGGCATTCCAGAAAATCCACCCCTCCTCCATCACTTCAAAGACCAGATAGACTCCTATGAAAAGCTCTATGAGGAGGTGGTCAGCCTGGAACCCACCAAGGTGTTTGATGGCTGGATGCGAGTGGATGTGAGACCCTTCAAAGCATCTCTGCTGAACACAATAAAGAAGTGGAGCCTCATGTTCAAGCAACATCTTGTTGACTTTGTCACAAACAGCCTGTCTGACCTTGACTCATTCATAAGGAGCACCGAGAGTGGTTTGCTCAAGAGGGTGGAGAAAGGAGATTTCCAAGGATTGGTTGAGATCATGGGACATCTTGTCACCCTTAAAGAACGGCAGAGCAGCACCGATGACATGTTTGAGCCCCTGAAGCAAACGATTGAACTGCTGAAGTCCTACGAACAAGAGCTGCCAGAAACCGTGTTTAAGCAACTGGAGGAGCTTCCTGAGAAGTGGAAGAACATGAAGAAGATGGCCATCACTGTGAGGCAACAGGTGGCCCCTCTGCAGGCAAATGAAGTGGCCCTACTCCGCCAGCGGTGCTCAGCCTTCGATGATGAGCAGCAGCAATTCCAGGAGAGGTTCCGCAAAGAGGCCCCTTTCAGGTTTGATAGTATCAATCCACACCAAATGCTGGATGCCTGGCACGTGGAGATCCAGCACATGGAATCCACCATGGCAACCATCTCTAAGTCGGCTGATTTGTTTGAAGTCAATGTTCCTGACTACAAGCAGCTGAGGCAGTGCAGGAAGGAGGCCTGCCAGTTAAAGGAGCTCTGGGACACCATTGGAATGGTGACCTCCAGCATCCGTGCCTGGGAGGCCACCAGCTGGAGGAATATCAGTGTGGAAGCCATGGACTCAGAGTGCAAGCAGTTCGCCCGGCACATCCGCAACCTAGATAAGGAGTTCAGGTCCTGGGATGCATTCACGGGCCTGGAAAGCACAGTGTTGAACACCCTGACGTCCCTGAGGGCTGTGGCGGAGCTGCAGAATCCTGCCATCCGGGATCGGCACTGGAGGCAGCTGATGCAGGCCACCGGGGTGAATTTCACCATGAATCAGGATACCACCTTAGCTCATCTCCTGCAGCTTCAGCTCCACCACTTCGAGGATGAGGTCCGAGGCATTGTGGACAGAGCTGTCAAAGAGATGAGTATGGAGAAGACCTTAAAGGAATTGCAGACTACCTGGGCCAGCATGGAATTCCAGTACGAGTCCCACGCAAGAACCCGCGTACCCTTGCTGCAGTCAGATGAGGATCTCATTGAGGTCCTAGAGGACAATCAAGTGCAACTTCAGAACCTGATGATGTCCAAATATGTTGCTTTCTTCCTGGAAGAAGTGTCGAGCTGGCAGAAGAAGCTGTCCACGGCTGACTCGGTCATCTCTATCTGGTTTGAGGTGCAGCGCACCTGGTCTCACCTAGAGAGCATATTCATTGGCTCAGAAGATATCCGGGCTCAGCTACCCCAGGACTCTAAGAGATTTGAAGGCATTGACTCTGACTTCAGAGAGCTGGCGTATGATGCTCAGAAAACCCCAAATGTGGTGGAAGCCACAAATAAGTCAGGTCTCTATGAAAAGCTGGAGGATATACAAAGCAGATTATGCCTGTGTGAGAAAGCCCTAGCAGAGTATCTAGACACCAAGAGGCTCAGCTTCCCTCGCTTTTACTTCCTGTCCTCCTCTGACCTGCTGGACATCCTTTCCAATGGCACAGCTCCACAACAGGTTCAACGGCACCTCTCCAAGCTCTTTGACAACATGGCTAAGATGCAGTTCCAGTTAGATGCCAGTCAGAACCCAACCAAGACGAGCCTTGGCATGTACAGCAAAGAGGAGGAATATGTGGCCTTCAGTGAGGCCTGTGACTGCAGTGGGCAGGTTGAAATATGGCTGAACCGTGTTCTTCGTCACATGAAAGCCACTGTGAGGCATGAGATGACAGAGGGGGTCACTGCCTATGAGGAAAAGCCCAGGGATCAGTGGCTGTTTGATTACCCGGCTCAGGTGGCTCTGACCTGCACTCAGATCTGGTGGACGACAGAGGTGGGCATTGCATTTGCCAGGCTGGAGGAAGGCTATGAGAGTGCCATGAAGGACTACTATAAGAAGCAAGTGGCCCAACTCAAAACCCTTATCACCATGCTAATTGGGCCGCTCTCCAAGGGGGACAGGCAAAAGATCATGACCATATGCACCATCGATGTGCATGCCCGGGATGTGGTAGCCAAGATGATTGCTCAAAAGGTTGACAATGCCCAGGCTTTCCTCTGGCTGTCACAGCTACGACATCGTTGGGATGATGAGGCCAAGCACTGCTTTGCTAACATCTGTGACGCCCAGTTTCTATATTCCTATGAGTACTTGGGAAATACACCTCGCCTAGTGATCACGCCTCTGACT------GATAGGTGCTACATCACTCTCACCCAGTCTCTACACTTGACCATGAGTGGGGCTCCAGCAGGACCTGCAGGCACAGGCAAGACAGAGACCACCAAGGACCTGGGCAGAGCACTCGGCATCATGGTCTATGTGTTTAACTGTTCTGAGCAGATGGACTACAAGTCCTGTGGCAACATCTACAAAGGCCTGGCTCAGACTGGTGCCTGGGGCTGTTTTGATGAGTTTAACCGAATCTCTGTGGAGGTCTTGTCGGTGGTGGCTGTCCAGGTAAAAAGCATCCAGGATGCAATCAGAGACAAGAAGCAGAGGTTCAGCTTCCTTGGAGAGGAGATTAGCCTTGACCCTTCAGTGGGCATCTTCATTACCATGAACCCAGGCTATGCTGGCCGCACAGAACTGCCAGAGAACCTCAAGGCCCTTTTCAGGCCCTGTGCAATGGTAGTTCCAGACTTTGAGCTGATCTCTGAGATTATGCTGGTAGCAGAAGGATTCATTGAAGCCCGGTTGTTGGCCAGGAAGTTCATTACCCTTTACCGGCTGTGTAAAGAACTTCTCTCCAAACAGGATCACTATGACTGGGGACTTCGTGCCATTAAGTCTGTCCTCGTGGTAGCAGGATCACTGAAACGGGGAGACCCTGACCGCCCAGAGGACCAAGTCCTGATGCGTTCTTTGAGAGACTTCAACATCCCAAAGATCGTGACAGATGACATGCCGGTGTTCATGGGTCTGATAGGTGACCTCTTTCCTGCTTTGGATGTCCCCAGGAAGAGAGATCTGGACTTTGAGGCTGTGGTTCGGAAAGCAATCGTGGACCTTAAGCTCCAGGCTGAGGACAACTTTGTGCTCAAGGTGGTCCAGCTGGAGGAGCTGCTAGCTGTAAGGCACTCTGTG---TTCGTGGTGGGCGGTGCTGGT------ACCGGGAAGTCACAGGTACTGAGGTCTTTACACAAGACCTATCAG------ATCATGAGACGTCGCCCTGTGTGGACTGACCTCAACCCCAAAGCTGTCACAAATGATGAACTCTTT---------------------------------------GGCATCATCAATCCAGCCACTCGAGAATGGAAGGATGGA---CTGTTCTCTTCCATCATGAGAGAGCTTGCCATCATCTCTCATGATGGGCCCAAGTGGATCTTACTGGATGGCGATATAGACCCGATGTGGATAGAGTCTCTGAACACAGTCATGGATGATAACAAGGTACTGACCCTGGCAAGCAACGAGAGAATCCCCCTTAACCCCACAATGCGTCTTCTTTTCGAGATCAGCCACCTGCGCACAGCCACACCAGCAACCGTCTCCAGAGCAGGGATCCTGTACATAAACCCTGCAGACCTGGGATGGAACCCTCCAGTAAGCAGCTGGATTGATCAGAGAGAAGTCCAGACTGAGAGAGCCAACTTGACCATCCTGTTTGACAAATATCTTCCTACCTGCTTGGACACCCTCAGAACCAGATTTAAGAAAATAATTCCAGTCCCAGAGCAGAGTATGATCCAGATGCTGTGCTACCTCCTTGAGTGCCTCCTGACAAAGGAGGATATCCCTGCAGACTGCCCCAAGGAAATATATGAACTCTATTTTGTGTTTGCTGCCATCTGGGCATTTGGCAGTGCTGTGATCCAAGATCAGCTTGTAGACTACCGGGCAGAGTTCAGCAAATGGTGGCTGACTGAGTTTAAAACAGTCAAGTTTCCTTCCCAAGGAACTGTCTTTGACTACTACATAGACCCAGAGACCAAGAAATTTGAGCCCTGGGCCAAGCTCATCCCCCAGTTTGAATTTGACCCAGAGATGCCTTTGCAGGCTTGTTTGGTACACACAAGTGAGACCATCCGGGTGTGCTACTTCATGGAGCGGCTCATGCAATGGAGGCGGCCGGTTATGCTGGTTGGCCCTGCAGGCTCAGGCAAGTCTGTGCTGGTGGGAGCAAAGCTGTCCAGCCTTAACCCTGAGGAATACATGGTGAAAAATGTGCCCTTCAACTACTATACTACGTCAGCAATGCTGCAAGCTGTCTTGGAGAAACCTCTAGAAAAGAAAGCTGGCAGGAATTATGGCCCTCCAGGCAACAGGAAACTCATCTATTTCATCGATGACATGAATATGCCCGAGGTGGATGCCTATGGCACAGTACAGCCCCACACTGTCATCAGGCAGCACCTAGACTATGGCCACTGGTATGATCGGAACAAGCTGTCTCTGAAGGAGATCATGAATGTACAATACATCTCCTGTATGAACCCCACTGCAGGCAGCTTTACCATCAACCCAAGGCTTCAGCGCCACTTCAGCGTGTTTGCCCTCTGCTTCCCAGGAGCTGATGCCCTCTCTTCCATCTATAGCACCATCTTGACCCATCATCTGAAGTTTGGAAACTTTCCCACCACCCTGCAGAAATCCATCCCTCCTCTGATAAACCTGGCTGTCACCTTCCATCAGAAAATTGCCACCACGTTTCTGCCCACAGCAATCAAATTTCACTACATCTTCAATCTCAGAGATTTTGCCAATATTTTCCAAGGCATTCTTTTCTCCTCCGTGGAATGTGTAAAGTCCACACAGGACCTAGTGAAACTCTATCTGCACGAGTCAAGTCGGGTTTATCGGGATAAGATGGTGGAAGAAAAGGATTTCAATCTTTTTGACAAAATCCAAACAGAATTCCTCAAGAAAAATTTTGATGATAGTGAAGAGGTGCTGAAGCAGACCCAGAACCTGAACATGTATTGTCACTTTGCAAATGGCATTGGTGAGCCCAAGTACATGCCTGTGCAATCATGGGACCTTCTGAATCAGACTCTGGTGGAAGCCCTGGAGAGCCACAATGAAGTGAATGCTGTGATGGACCTAGTTCTCTTTGAGGATGCCATACGTCACATCTGTCACATCAACCGAATCCTGGAGTCCCCTCGAGGAAATGCCCTGCTAGTTGGTGTAGGTGGGAGTGGTAAGCAGAGTCTGACAAAGCTGGCAGCTTTTATCAGTTCCATGGATGTATTCCAGATCACCCTTCGCAAAGGCTACCAAATCCCTGACTTCAAGGTGGACCTGGCCAGCCTCTGTCTGAAAGCTGGGGTAAAAAATCTCAGTACAGTGTTCCTTATGACTGATGCCCATGTGGCTGACGAGAGGTTCCTGGTGCTCATCAATGACCTCCTGGCATCTGGTGAGATCCCAGATCTCTACTCTGATGAGGAAGAGGAGAACATCATAAACAATGTGAGAAATGAGGTCAAAAGCCAGGGACTCATGGACAGCAGGGAGAACTGCTGGAAATTCTTCATAGAGAGAGTCCAGCGACAACTTAAGGTGACTCTCTGTTTCTCCCCTGTGGGGAACAAGCTGAGAATTCGAAGCAGGAAGTTCCCAGCCATTGTGAACTGTACTGCTATCAACTGGTTCCATGAGTGGCCTCAGGAGGCCCTAGAGTCTGTGAGCCTCCGATTCTTGCAGAATACAAAGAACATTGAGCCTGCGGTGAAGCAGTCAATTAGCAAGTTCATGGCCTTTGTCCACATAAGTGTCAACAAGACATCCCAGTCATACCTGACCAATGAGCAGCGATACAACTACACAACACCCAAGTCCTTTCTGGAGTTCATCAGACTGTACCAGAGCTTGCTGGAGAGAAATGGAAAAGAGCTCCAGGCCAAGGTGGAGAGGCTGGAGAACGGGCTGTTGAAACTGCACAGCACCTCGGCCCAGGTGGATGATCTGAAAGCGAAGCTTGCCACCCAGGAAGTGGAGCTGAGGCACAAGAATGAAGATACAGACAAGCTGATTCAGGTGGTGGGTGTGGAGACCAGCAAAGTGAGCAGAGAGAAAGCCATTGCTGACGAGGAGGAGCAGAAGGTGGCCCTGATCATGCTGGAGGTGCAGCAGAAACAGAAAGACTGTGAAGAGGACCTGGCTAAGGCCGAGCCAGCCCTGACCGCAGCGCAGGCGGCCCTCAACACTCTCAACAAGACCAACCTGACAGAGCTGAAGTCATTTGGTTCCCCACCTCTGGCTGTCAGCAATGTCAGCGCCGCGGTGATGGTTCTCATGGCCCCAGGGGGCAAGGTGCCCAAGGACCGCAGCTGGAAGGCTGCCAAAATCACCATGGCCAAGGTGGACAGCTTCCTGGATTCCCTAATCCACTTCGACAAGGAGAACATTCATGAGAATTGCCTCAAAGCCATCAGGCCATACCTGCAAGATCCTGCATTCAACCCAGAGTTTGTGGCCACCAAGTCCTATGCAGCTGCAGGCCTCTGCTCTTGGGTAATCAATATTGTGAGGTTCTATGAGGTCTTCTGTGATGTGGAACCAAAGCGCCAGGCTTTGAACAAAGCCACCTCAGACCTCACAACTGCCCAAGAGAAGCTGGCAGCCATCAAAGCCAAGATCACACACCTTAATGAAAACCTGGCGAAGCTCACCACCAAGTTTGAGAAAGCAACAGCAGAGAAGCTCAAGTGTCAGCAAGAAGCTGAACTGACCGCAGGCACCATTTCGCTTGCAAACCGTCTGGTTGGAGGCCTTGCATCTGAGAACATAAGGTGGGCAGAGGCTGTGCAGAACTTCAGACAGCAGGAAAGGACGTTATGTGGCGACATTCTGCTTACTACAGCTTTCATCTCCTACTTGGGCTTCTTTACCAAAAAGTACCGAAAGAGCCTCATGGATGGGACCTGGAGACCCTATCTGAGCCAACTGAAAGTTCCCATTCCAACCACCCCAACTCTGGACCCCCTGAGGATGCTAACCGATGATGCTGAAGTGGCTGCCTGGCAGAATGAGGGTCTCCCTGCTGACCGCATGTCCATGGAGAATGCTACCATCCTCATCAACTGTGAGCGCTGGCCTCTCATGGTCGACCCTCAACTGCAAGGCATTAAATGGATCAAGAACAAATATGGAGAAGAACTCCGGGTCACCCAGATTGGCCAAAAGGGCTGCCTTCAAACCATAGAGCGAGCCCTGGAAGCTGGAGATGTGGTACTGATTGAGAACCTTGAGGAGTCCATTGATCCCGTCCTGGGACCTCTGCTTGGGAGAGAAGTCATTAAGAAAGGACGGTTTATCAAGATTGGAGACAAGGAGTGTGAATTCAATCCCAAGTTCCGGCTCATCCTTCATACCAAGCTGGCCAACCCTCACTACCAGCCTGAGCTGCAGGCTCAGGCTACCCTGATCAACTTCACGGTGACCAGGGATGGCCTGGAGGACCAGCTGCTGGCTGCTGTGGTCAGCATGGAGAGACCAGACCTGGAACAGCTGAAGTCCGATCTCACAAAGCAGCAGAACGGGTTCAAAATCACCCTCAAAACCTTAGAGGACAACCTGCTATCTCGCCTCTCTTCAGCCTCGGGGAACTTCCTGGGAGAAACAGCCTTGGTGGAGAACCTGGAGGTCACCAAGCAGACTGCTGCAGATGTGGAGGAAAAGGTCCAAGAAGCCAAATTGACAGAAGTAAAAATTAATGAGGCCCGAGAGCACTATAGGCCAGCAGCTGCCCGGGCATCTCTGCTCTACTTCATCATGAATGACCTCAGCAAGATCCATCCAATGTATCAGTTCTCCCTCAAGGCCTTCAGCATTGTCTTCCAGAAAGCTGTGGAG------AAGGCAGCTCCCAGTGAAAGTGTCACAGAGCGAGTGACTAATCTAATAGACAGCATAACTTTCTCAGTGTACCAGTATACCACACGTGGCCTCTTTGAGTGTGATAAGCTGACCTACCTAGCCCAGCTCACCTTTCAGATTCTCCTCGTGAACCAGGAAGTTAATGCAGCAGAGTTGGATTTCTTGCTTAGGGCTCCAGTACAGACAGGGACTCCCAGCCCAATGGAGTTCCTGTCCCACCAGGCCTGGGGAGGCATCAAGGCACTCTCATCAATGGAGGAATTCTGCAATCTGGACCGAGACATTGAAGGCTCTGCCAAGAGCTGGAAAAAGTTTGTGGAGTCAGAGTGTCCCGAGAAGGAGAAGTTTCCCCAGGAGTGGAAGAACAAGACAGCCCTGCAGCGCCTCTGCATGATGAGAGCCATGAGGCCTGACCGGATGACCTATGCCATGCGAGATTTTGTTGAGGAGAAGTTGGGAAGCAAATACGTGATGGGAAGAGCACTCGATTTTGTAACCTCATTTGAAGAGTCAGGACCAGCCACTCCCATGTTTTTCATCCTGTCTCCAGGGGTGGATCCACTGAAGGATGTGGAAAATCAAGGAAAGAAACTTGGATATACATTCAACAATCGGAACTTCCACAACGTGTCCCTAGGGCAAGGACAAGAGGTAGTTGCTGAGGCTGCACTGGACTTGGCTGCTAAGAAGGGTCACTGGGTGATTCTGCAGAACATCCACCTGGTGGCCAAGTGGCTCAGTACCCTGGAGAAGAAACTGGAGGAGCTCAGCGAGGAAAGTCACCCAGACTTCAGGGTCTTCATCAGCGCAGAGCCTGCACCCTCCCCTGAGGGCCACATCATTCCCCAGGGCATTCTGGAAAACTCCATTAAGATCACCAATGAGCCTCCCACAGGCATGCACGCCAACTTACACAAAGCCCTGGACAACTTCACTCAGGACACTCTGGAGATGTGTTCCCGGGAGACAGAGTTCAAGACCATCCTCTTTGCTTTGTGCTACTTTCATGCGGTGGTTGCCGAGAGACGGAAGTTTGGGCCACAGGGCTGGAATCGGTCCTATCCATTTAACACTGGGGACCTCACCATCTCTGTGAATGTGCTATATAATTTCCTGGAGGCTAACACAAAGGTACCCTATGACGACTTGCGTTACCTGTTCGGTGAGATCATGTACGGTGGCCATATCACAGATGACTGGGACAGGAGACTCTGCAGAACCTATTTAGAGGAATTCATTCGGCCAGAGATGCTAGAAGGAGAGCTCTCCCTGGCCCCGGGGTTCCCACTCCCAGGAAACATGGACTACAGTGGCTATCACCAGTATATTGATGCTGAGCTGCCCCCTGAGTCTCCCTACCTATATGGCCTCCATCCAAACGCCGAGATTGGCTTCCTGACCCAGACATCGGAAAAACTCTTCCGAACCGTGCTGGAGATGCAGCCTCGGGACAGCCAGGCTGGAGATGGAGCTGGCATCACAAGGGAAGAAAAGGTCAAAACCTTTCTGGAAGAAATACTGGATCGGATGACAGATGAATTTAACATCGCGGAGCTAATGGCTAAGGTGGAGGAACGCACCCCCTACATTGTAGTTGCCCTCCAGGAGTGTGAACGCATGAACATCCTTACCAGAGAGATCCAGCGCTCACTAAGAGAGCTGCATCTTGGCTTACAGGGGGAGCTGACCATGACCAGTGAGATGGAAAACCTACAGAATGCCCTATATCTAGATGTGGTCCCAGAGTCCTGGGCCAGGCGAGCCTACCCTTCCACAGCAGGCCTGGCAGCCTGGTTTCTAGACCTGCTTAACAGAATCAAAGAGCTGGAGTCCTGGACAGGCGACTTCTTGATGCCCTCAACTGTGTGGCTGACGGGCTTCTTCAACCCCCAGTCCTTCCTGACTGCCATCATGCAGTCCATGGCCCGCAAGAATGAATGGCCACTAGACCAGATGGCCCTGCAGTGTGATGTGACAAAGAAGAACAGAGAGGAGTTCCGGAGCCCTCCTCGGGAAGGGGCCTACATCTATGGGCTCTTCATGGAAGGTGCCTGCTGGGACACACAGACTGGGATCATTGCAGAGGCGAAACTGAAGGACCTGACACCCCCCATGCCTGTGATGTTCCTCAAGGCCATTCCAGCAGATAAGCAAGATTGTCGAAGTGTCTATGCTTGTCCTGTGTACAAGACTTGTCAGCGGGGACCCACCTACGTGTGGACTTTCAATCTGAAGACTAAAGAAAACCCATCCAAGTGGGTTCTGGCTGGTGTTGCCTTGCTTCTCCAGATT
->Opossum
-ATGCCAGTGAAAGTG------GCGGCGGTGTTCTCCGAGGCTGAGGATGGAGATAGCGAGAAAGGG---CCAGATCCTAGAATACGGCTCCTGGGCACCTACGTGATTCGGTGCTTGCGGCCAGCGGCTGGCGCCTGGAAGCGCTGTGTGAGCACGGCCGAATCTGAGCAGCTGCTCCAGGCCTTCCTGACAGGCTACTGCCCTCAAGAG---------CCG------ATGTTGGTGGTGCGTCCTGGGGGTGGGGGTCTGCAGCTGAGCTCCGGG------------CCATACCCCGATTTGTCTCGAGCCAAAGCGCTGTTCTTCCTGCGC---GGTTCCCCCAATCCCCCAGGACCCTTGGGTCCCAAAGGCGCAGTGCTCTGCGGGGAGCTGGCCCCGGCACCGTTGGAGCATCTGGCCACGCTGGTGGCTGAGGTAGTGATGCCTGTCCTGACCAATGAAAAGAATCACCAAGACTGGCCAAATGCTTTATATCAGGATGTAAGACGACACATCCACCACCTTCAAAGTGACCTCATTATCTTCCTGGGTCAAGTGACAGGAAAAACATTGCTGCCTCTTCCAGTAGGCTCAGAAAACATGGAATGTGCGGATTATGAAAATGAAAAAGACTTGGATTTCACAGATAAATCAATTGTCTATGCTATTGAGTCCACAGTGATCAAATGGAGCCACCAGATTCAAATAGTATTAAAGAGGGAATCTTCAGAACCACTCTTGCAGGGAGGAAATCCCACCCCAAAAGTGGAGCTGGAATTCTGGAAGAGAAGGTGTGAAGATCTGGAATATATTTATAATCAGCTGAGAGCCATAGAAGTGAGGGGCATAGCTGCACTTTTAAACAGACTTCAGAGCAGCTACTTCCCAGCCTTCAAAACCATGTTCAAAGATGTAATAGCAGCTTTAAGAGAGGCCCAGGATATCTATGTACATCTGAAGCCCCTGCAACGCCATGTGGAAAACATAGAGAATGTGGAATTTAGTGAAGTGAAGCCACTGGTGGGACCCCTGCTTCACGTAATCTTCTTGATTTGGTCCACATCTAAATACTACTGCTGTCCAGTACGGATCATTGTGCTACTGCAGGAAATATATAACCTTCTCATTCAGCAGGCCTGTACATATCTTAGCCCAGAAGATCTACTGAAAGGGGAGATAGAAGAGAGTCAGAGGAAAATACAGGTGGTGATGGACATTCTTAACTTTTCCAAAGAGATGTTTGAGAATAGGAGGAACAATCTCAAGACTTACTTCAAACAGGTCCGGGAGGTGAAGGAATGGGATTTCAATTCTTCTATGATCTTTGTGCGACTAAATAACTTCCTCAAAAGACTCAAGATGGTAGAGAATCTTCTGAAGACAACCTTGGATTTTCTCAAGCTAGAGAAACTTGAATTCAGTGGAATTAGAGGGAATGCTTTGAGTCAACAAGTCCAGGGCATGTATGAAGAATTCCAGAATGTGTACAAAGCCTTTTCAGAATGCTTCTATGACTGTCTGGACCCTAAACACACAGAATTCGAAAATTATGTTGCTGAATTCAACCACAAAGTAGAAGATTTGGACCGAAGATTAGGGATGGTCTTCCTCCAAGCTTTTAATGATGTATCTGGCTTAGAGCATGCTTTTAAGTTGCTTGAAATATTTGGGAGTCTGCTTGAAAGACCAGTAGTAGCCATGGATGTATTTGATAAATACCCAAGACTGATCACAATGTTCAGCAGTGACTTGGATGCTGTTAAGACAATCTATTGTCAGCATGTCCAGGAGGAGTCAGAACTTGGGTTTTCCCAATTACACAAGAACATGCCGGCAGTAGCTGGGGGTCTCCGCTGGGCTCAGGAACTCAGAGAACGAATTGAAATTTCATTCAATAACCTAAGACATATCAATCACCCCTATATGGAGTCCACTGAAAGTAAAGAGATGTTCCAAAAGTATGGTGAAATGTTTACATTGTTAGAAAAGTATGAGACAAAACTTTATGATAGCTGGTGCCAGACAGTGTCAGAGAAGTCACAATACAATCTCACTCAGCCACTCCTCTGTCATGATCCAGAAACTAAGCAGATCATTGTCAACTTTAACCCACAG---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------GAAAAC---ATGAAA------------------------------------------------------------------------------------------------------------------------------------------------------CTCTTCCTTGCAGATCCAGCATCCAGTATGTGGAAGGCATACATTGACTACCTTGATAACATGGTTCTTCATGGATTCTTCATTGCTATTGAATCTTCTCTAAATTATCTCCTGGAAAATACTGATTCTAAGAACAGACTTCCTCCCTTTTTTGAAACACAATTGGATCTAGTTATACCAGAACTGATCTTTTGTCCCTCTCTGGACCCTTTTGCTAATGGTGGTTTTCAAAGCATTGTGGAGGGACTTATCAATGACATTTTCAAAATATCTTCTATGGTCCCACGACTT---GCACAGAAAAGTTCCCCTCATTATCAGGCTGACATGGAGGACACAGAAGATCTGTCAAACCTGAGGAATATATTAATGGAGAGAGTCAAGAATATGATGACCATCTGCTGTGACTATCGGAACTCCTTTGACCATTACTCTTTCCTTTACATGGATGACCGAAAGGAATTTATGCGACAATTTCTTTTATATGGACATATGCTCACACCAGAGGATTTAGAAGCCCATGGAGAAGATGAGATCCCTGAAAATCCTCCCACTCTTCAGCAATTTAAATTGCAGATTGATTCCTATGAAAAGATTTATGAAGAGGTGAACCAGCTAGAGCCTTTCAGAATTTTTGACAACTGGATGAAAATTGATATTCGTCCATTTAAGATGACTCTACTAAATGTAATTAAGAGATGGAGCTTCATGCTTAAACAGTATCTCATTGATCATGTCACTCACAGTTTGGCAGACCTTGAAGTCTTTATAAGAAATGGTGAGAGTGGTTTAAACAAGAAGATAGAAAAAGGTGATTTTGCTGGTTTGGTTGAAATTATGAGAACTCTAATGGCTCTTAAAGAACGACAGAGCAGCACTGATGAGATGTTTGAGCCACTGAAACACACAATTGATTTACTGAAGATCTATGAACAAGAATTACCTGATTCAGTATTTAAACAGCTAGAGGAATTGCCAGAAAAATGGAGCAACATAAAGAAGATGGCAGTCATTGTAAAACAGCATGTAGCTCCCCTGCAGGAAGATGAAGTAACAGCTCTCCGTCAAAAATGTGCCATTTTTGCTATTGAACAGAATATATTCCAAGATCAGTTCCACAAAGAAGCTCCTTTCAGGTATGATAGCATTAAGCCTCACCAAGTCTTGGATGCCAAAAACATTCAAATCCAACAGATGGAATCTTTCATGTCCTCCATTTCAGACTCTACAAATTTGTTTGAAGTCAACATTCCTGATTACAAACATTTAAAACAATGCAGGAAGGAGATCTGCTTGTTGAAGGAGCTTTGGGATATGATTGACCTAGTGAATTTTAGCATCAATAACTGGAAGGTAACCAAATGGAGGAATATTAATGTAGAGAATATGGACTTAGAATGTAAAAGTTTTGCCAAACAAATATGGAAACTTGGCAAGGATGTGAGAGCCTGGGATGCTTTCATGGGGCTGGACAACACTCTGAAGAACATCCTAACATCCTTACGGGCTGTGTCTGAACTTCAAAATCCAGCCATAAGAGAGAGGCACTGGAACCAGCTGATGCAGGCTACAGGTATGAAATTCATCATGAATGATGATACCACCCTTGAAGACTTACTTAAGCTTGAGCTACACAACTTTGAAGAGGCGATCCAGGGCATTGTAGACAAAGCTGTGAAAGAGATGGACATGGAAAAAGTTCTAAAGGAACTAAAAGCCACTTGGGCAGGGATGGAATTTCATTATGAGCCCCACCCCCGGACAATGGTCCCACTGATGAGGTCTGATGAAGACCTTATTGAAACTCTTGAAGATAACCAAGTCCAGCTGCAGAATCTAATGACATCCAAGTACATAGCCTTCTTCCTGGAGGAAGTATCTGACTGGCAGAAGAAGCTCTCAATAGCTGATGCTGTCATTTCAATCTGGTTTGAAGTACAGCGTACATGGTCTCATCTTGAAAGCATTTTCATTGGATCTGAAGATATACGAGCACAGCTTCCTAAGGACTCTAAACATTTTGAAGACATTGATACTGATTTTAAAGAGTTAGTTTGTGATGCTCAGAAGACACCAAATGTGGTAGAAGCTACCAACAAACCAGGTGTTCATGAACAATTAGAAGATATTCAGAACAGGTTGAGCCAGTGTGAGAAAGCCTTGACTGAATATCTAGATACCAAGAGACTGGTCTTCCCCAGGTTTTATTTCCTCTCCTCTTCCGACCTTTTAGACATCCTTTCCAACGGCACAAATCCACAACAAGTTCAACGCCATCTTTCTAAGTTATTTGACAGTATGGCTAGGATGAAGTTCCAGGTGGACTCCAGTCAAAAACCAACCAAGAAAAGCCTCGGCATGTATAGCAAAGAGGAAGAGTATGTGAATTTCAGTGAGCCTTGTGACTGTAGTGGCCAGGTTGAACTCTGGTTGAATAATGTGCTCAATCACATGAGGGCCACCGTGAGACATGAAATGACAGAAGGTGTCACTGCCTATGAAGAGAAACCTAGGGAACAGTGGCTCTTTGACTACCCTGCTCAGGTGGCCTTGACATGTACCCAGATCTGGTGGACCACTGAGGTGGGAATTTCCTTTGCCAGGCTGGAAGAAGGATATGAAAGTGCCATGAAGGACTATTATAAGAAACAAGTTACCCAACTGAACACCCTGATCACCATGCTAATTGGCCAGCTCTCCATGGGTGACCGTCAAAAAATCATGACCATCTGTACTATTGATGTGCATGCTCGAGATGTAGTTGCCAAGATGATCTCTCAGAAGGTAGAGAACACTCAGGCTTTTCTCTGGATGTCCCAGATGCGCCATCGGTGGGATGATGAGAAAAAGCACTGCTTTGCCAATATTTGTGATGCTGAATTCCTGTATTCCTATGAGTACCTGGGTAACACACCTCGCCTGGTGATTACCCCTCTGACA------GACAGATGTTACATTACCCTCACCCAGTCTTTGCATTTGACCATGAGTGGGGCTCCAGCAGGACCTGCAGGCACTGGCAAAACTGAGACTACCAAAGACCTGGGTCGAGCATTGGGTATCATGGTATATGTGTTTAACTGTTCTGAGCAAATGGACTACAAGTCTTGTGGCAATATCTACAAAGGCCTTTCTCAGACTGGTGCCTGGGGCTGTTTTGATGAATTTAACAGAATTTCTGTGGAGGTTCTTTCCGTGGTGGCAGTACAGGTGAAAAGTATTCAAGATGCAATCAGAGATAAGAAGCTGAATTTCAATTTCCTTGGAGAGGAGATTAAATTGAATCCCTCAGTGGGAATCTTCATCACTATGAACCCAGGATATGCTGGCCGTACAGAGCTTCCAGAGAACCTTAAGGCTCTCTTCAGGCCTTGTGCTATGGTGGTACCAGATTTTGAATTGATCTGTGAAATCATGTTGGTTGCTGAAGGATTCATCGAGGCACAATCATTGGCCAAAAAATTCATTACCCTTTATCAGCTTTGCAAAGAGCTCCTCTCCAAACAGGACCATTATGACTGGGGTCTGCGAGCTATCAAGTCTGTGCTTGTGGTAGCAGGATCTCTGAAGAGAGGAGACCCTGACCGACCTGAAGACCAAGTCCTAATGCGTTCCCTCAGAGACTTCAATATTCCCAAAATCACAACTGATGACATGCCTGTGTTTATAGGCCTAATTGGAGACCTTTTCCCTGCCCTTGACATCCCCAGAAAGAGAGACCTTCACTTTGAAAGTTTTGTAAAACAGGCAATATTAGAGCTAAAACTTCAGGCTGAGGACAACTTTATGCTCAAAGTGGTACAGCTAGAAGAACTTTTGGCTGTGCGACACTCTGTG---TTTGTGGTGGGGAATGCTGGT------ACAGGAAAATCCCAGGTACTAAGATCTTTGCAAAAGACATACCAG------AACATGAAACGACGACCAGTCTGGACTGATCTCAACCCCAAAGCTGTCACTAATGATGAACTATTT---------------------------------------GGCATCATCAACCCAGCCACAAGAGAATGGAAAGATGGA---CTTTTCTCATCAATCTTGCGTGAACTTGCCAATATAATACATGATGGGCCCAAGTGGATTTTACTAGATGGCGATATTGATCCAATGTGGATTGAATCTTTGAACACTGTCATGGATGATAACAAGGTTCTGACTCTGGCAAGCAATGAAAGAATCTCTCTCAATCCAACTATGCGGCTCCTGTTTGAGATCAACCACTTGCACACAGCTACCCCTGCCACAGTCTCCAGAGCAGGAATACTATACATCAATCCTGCAGACCTGGGTTGGAATCCACCAGTGAGTAGTTGGATTGACAAGAGAGAGATACAGTCTGAACGAGCTAACCTGACCATCCTATTTGACAAATATTTGCCACCTTGCCTGGATGTAGTCAAAACAAGATTTAAAAAGATTGTTCCAATACCAGAGCAGAGCATGATTCAAATGCTGTGTTACATTCTTGAGTGTCTTCTAACAAAGGAAAACAGTCCCCCAGACTCTCCCAAGGAACTTCATGAACTTTATTTTGTGTTTGCTTCTATCTGGGCTTTTGGTGGAGTATTGATCCAAGACCAGCTTGTGGATTACAGAGCAGAGTTCAGTAAATGGTGGATAACTGAATTCAAGACAATCAAGTTTCCTTCCCAAGGAACAATCTTTGACTTTTACATAGAACCAGAAACAAAGAAATTTGAGCCGTGGTCCAAACTTATCCCCAAATTTGAATTTGACCCAGACCTACCTTTGCAGACTTGCCTGGTACACACTGTTGAGACCATTCGTGTGTGCTATTTCATGGAGCAGCTCCTGAAACACCGGAGACCTATCATGTTGGTGGGGAATGCAGGCACTGGGAAATCTGTTCTGGTAGGGGCAAAATTGGCTACCCTGGATGCAGATGAATACATGGTGAAGAATATCCCATTTAATTATTATACTACATCTGCAATGCTACAAGCTGTCCTAGAGAAACCTCTAGAAAAGAAAGCTGGAAGAAATTATGGCCCACCTGGCACCAAGAAACTCATCTATTTCATTGATGATATGAACATGCCTGAGGTAGATGCATATGGAACAGTGCAACCCCACACACTCATCAGACAGCATATGGACTATGGGCACTGGTATGACAGAAATAAACTGTTCCTCAAAGAAATCATGAATGTACAATATGTGGCCTGTATGAATCCGACAGCTGGTGGCTTCTCTATCAATCCTCGGCTACAGCGTCATTTTAGCATCTTTGTGCTCTCCTTCCCTGGAGTAGATGCATTGTATTCAATCTATAGCACCATCTTGACTCAGCATCTAAAACTTGGTAACTTTCCAGCATCACTGCTGAATTCCACCCCCCGACTCATTAACTTGGCCATTACCTTCCATCAGAAGATTGCTGCCACTTTTCTCCCCACAGCAATAAAATTCCATTATATCTTCAATCTCCGAGATTTCTCCAACATTTTCCAAGGCATTCTCTTTTCAACAGTGGAAAGTGTTAAAACCACATCAGACCTTGTGAAGCTCTATCTTCATGAGTCCAATCGGGTTTATCGCGATAAGATGGTTGAAGACAATGACTTTGACAACTTTGATAAAATCCAAATTGAAGTGGTAAATAATTTCTTTGATGATATGGACAAGACTCTAGAGGAAATCAAGAGATTGAATATGTACTGCCACTTTGCAAATGGTATTGGTGAGCCCAAATATATGCCAGTGAAGACATGGGAACTGCTTACCCAAATCCTGGTGGAAGCCTTAGAGAACCACAATGAAGTCAATCAAGTGATGAATCTGGTTCTTTTTGAGGATGCCATGTGCCATGTTTGTCGTATCAATCGCATCCTAGAATCCCCAAGGGGGAATGCTTTGCTGGTTGGAGTAGGTGGAAGTGGCAAACAGAGCCTGACAAGACTTGCAGCTTTTATCAGCTCCATGGATGTTTTCCAGATCACTCTAAGAAAAGGTTATTGTATTTCTGATCTTAAGATGGATTTAGCCAATCAGTGCCTAAAAGCTGGAGTAAAGAATGTAAGCACTGTATTTCTCATGACAGATGCCCAAGTTGCTGATGAAAAGTTCCTTGTCCTTATCAATGATCTGTTGGCATCTGGAGAGATTCCAGATCTATACTCTGATGATGAAGTTGAAAACATCATAAACAATATGAGAAATGAAGTCAAGAGCCTGGGTTTGTTTGACAGCAGGGAAACTTGCTGGAAAGTCTTCATAGAAAGGGTCCAAAAACAATTAAAGGTTATACTCTGCTTCTCCCCTGTGGGGAATAAGCTAAGAGTCCGCAGCAGGAAATTTCCAGCCATTGTGAACTGCACAGCCATTGATTGGTTTCATGAGTGGCCTCAGCAAGCACTAGAATCAGTGAGCCTGCGCTTCTTACAAAACATAGAGAATATTGATCCAGCAGTAAAAGAGTCAATTAGTAAATTCATGGCTTATGTACACACGAGTGTCAACCAAATGTCCCAGTCCTACCTGAGCAATGAACGGCGCTATAATTACACCACCCCAAAATCCTTCCTGGAACAGATCAGACTCTATCAGAACCTACTGGTCAAGAATGGCAATGAGCTGACATCTAAAATGAAGAGGCTTAAGAATGGACTGCAGAAGCTTCACAGTACATCTTCCCAGGTAGATGACCTGAAAGCTAAACTGGCACTCCAGGAAATAGAGCTCAGGCAGAAGAATGAAGATGCAGACAAACTAATTCAAGTGGTTGGAGTAGAGACAGAGAAAGTAAGCAAAGAGAAAGCCATTGCTGATGAAGAGGAACATAAAGTGGCTCTAATCATGTTGGAGGTCAAGCAGAAGCAAAAGGACTGTGAGGAAGATCTGGCCAAAGCAGAGCCATCTCTCACAGCAGCCCAAGAAGCTCTCAACACACTCAATAAGACCAACCTAACAGAGCTCAAGTCTTTTGGTTCACCACCTTTGGCTGTCAGCAATGTCACTGCTGCAGTAATGGTTCTCATGGCCCCTGGAGGGAAGGTACCCAGAGATCGAAGTTGGAAAGCTGCCAAGGCTACCATGGCCCGAGTTGATGGCTTTCTGGACGCCCTGGTCAACTTTAACAAAGAGAACATACCTGAGAGCTGCCTCAAAGCTATCCAACCATATATTCAAGATCCAGAATTTAAACCTGAGTTTGTGGCCTCTAAGTCTTTTGCAGCAGCTGGCCTCTGTTCCTGGGTCATAAATATTGTGAGGTTTTATGAGGTTTTCTGTGATGTGGAACCCAAGCGGCAAGCCCTGAGTAAAGCAAGTTTGGATCTTGCTATTGCCCAAGAAAAATTGGCAACCATTAAAATCAAGATTGCTCACCTTAATGAAAACTTGGCAAAACTTACAACCAAATTTGAGAAAGCAACTGCAGAAAAACTCAAATGTCAGCAAGAAGCTGAACTGACCACAGGTACCATCTCACTTGCAAATCGCCTGGTTGGAGGTCTTGCCTCTGAAAATGTAAGATGGGCAGAAGCTATTAAGGACTTCAGACAGCAAGAGAATACATTGTGTGGAGATATTTTACTGATTACAGCTTTCATTTCTTACCTGGGATACTTTACCAAGAAATATCGTCAAAATCTTATGGATTGTAGCTGGAGACCTTACCTGAATCAGTTAAAAGTGCCTTTTCCAGTCACCCCTACTTTAGATCCTCTAAAGATGCTTACTGATGATGTAGACATAGCCACCTGGCAAAATGAGGGTCTTCCTGCTGACCGTGTGTCCACAGAGAATGCTACAATCCTTATCAACAGTGAACGATGGCCACTTATAGTTGACCCTCAACTACAAGGAGTCAAATGGATTAAGAAGAAATATGGCAAAGACCTTCGAGTCATCCAGATTGGAGAGAAAGGATACCTTGATATCATAGAACATGCCCTAGCAGATGGTGATGTAGTGCTGATTGAAAACATAGGAGAGACAGTGGACCCTGTTCTAGGACCCTTGTTGGGAAGAGAAGTAATTAAAAAAGGAAGATTCATTAAAATTGGAGACAAGGAGTGTGAATATAATCCCAGGTTCCGCCTCATTCTTCACACCAAGCTTGCCAATCCTCACTATCAGCCTGAGCTCCAAGCCCAGGCAACGCTTATCAACTTCACTGTGACTAGAGATGGCTTAGAGGATCAGCTTCTGGCAGCAGTGGTCAACATGGAGAGGCCAGACTTAGAAGAACTGAGATCAGATCTGACAAAGCAACAGAATGCATTCAAGATCACACTGAAAACCTTGGAAGACAACTTGCTGTCTTGCCTCTCCTCCGCATCTGGGAACTTCCTGGGAGATATGGCTTTAGTAGAAAACTTAGAGATCACCAAAAAGACAGCTACAGAAATTCAGAAGAAGGTCCAGGAAGCCAAGATAACAGAAATAAAAATTAATGATGCCAGAGAGCATTACCGGCCAGTGGCAGCACGTGCGTCTTTGCTGTACTTCATCATGAATGACCTCAGTAAAATCCATCCCATGTATCAGTTTTCTCTCAAGGCTTTCAGCCTTGTCTTCCAAAAGGCTATACAGAAGACCAAGGCAGATGCTGATGAGAACCTCCAACAGAGAGTAGTCAGTTTGATTGACAACATCACCTTCTCTGTATATCAGTATACTACTAGGGGACTTTTTGAATGTGATAAGCTGACTTACATCACCCAGGTCACCTTTCAGACACTCATGATGAATCAGGAAATCAATGCTGCTGAATTGGACTTCCTTCTCCGGTACCCAGCACAGTCCAATGTTCTGAGCCCTGTGGATTTCCTTTCCAACCAATCCTGGGGAGGTATCAAGACTCTTTCATTAATGGAAGAATTTTGTAATCTGGATCGAGACATTGAAGGGTCTGCCAAACATTGGAAAAAATTTGTGGACTCAGAGTGTCCTGAAAAGGAGAAATTCCCCCAAGCATGGAAAAACAAGTCATCTCTACAAAAACTGTGTATGATGAGGGCAATGAGACCTGACCGAATGACATATGCTATGAGAGATTTTGTGGAAGAAAAGCTAGGAAGCAAATATGTGGTGGGCAGAGCATTAGATCTCTCTACCTCTTTAGAAGAATCAGGGTCTGCAACTCCCATGTTCTTCATACTGTCGCCAGGTGTTGACCCACTGAAAGATGTGGAGAAGGAAGGGAAGAAACTTGGTTATACCTTCAACAACCAGAACTTCCATAATGTGTCCTTGGGTCAAGGACAAGAGGTAGTAGCAGAGGCTGCATTGGATCTGGCTGCCAAGAATGGCCACTGGGTTATCTTACAGAACATCCATCTGGTAGCCAAATGGCTTGGATCCCTTGAGAAGAAGTTGGAACAACATAGCAAGAGCAGTCATCATGAGTTCAGAGTCTTCATGAGTGCTGAGCCTGCAGCTTCTCCTGATGGTCACATCATTCCCCAAGGCATCCTAGAAAATTCAATAAAGATCACTAATGAGCCTCCCATGGGCATGCATGCCAAACTGCACAAGGCCCTGGACAACTTCACTCAGAATACTCTGGAAATGTGTACCCGAGAAACAGAGTTTAAGAGTATTTTATTTGCACTTTGTTACTTCCACGCTGTTGTAGCAGAAAGGCACAAATTTGGACCTCAAGGCTGGAATCGCACTTACCCTTTTAACACTGGAGACCTTACCATCTCAGTGAATGTGCTCTATAACTTTCTTGAGGCCAATGCCAAGGTACCCTATGATGACTTGCGCTATCTCTTTGGTGAGATCATGTATGGAGGTCACATCACAGATAACTGGGACAGGAGGCTTTGTAGAACATACCTGGAGGAATTCATTAAGCCAGAAATGTTAGAAGGAGAAATGTTTCTGGCTCCAGGGTTCCCGATGCCAGGCAATATGGATTACAACAGTTATCACCAGTTCATTGATGACATGCTGCCAACAGAGTCACCATATTTGTATGGGCTTCACCCCAATGCTGAAATTGGCTTTCTGACCCACACCGCAGAAAAGCTCTTCCATACAGTGCTAGAAATGCAGCCTCGGGACAGCCAAGATGGAGATGGAGGAGGGATCACAAGAGAAGAAAAGGTAAAAGCCTTTCTGGATGAAATATTAGAGAAGATAACTGAAGAGTTTAACATTGCAGAGTTGATGGCTAGGGTAGAAGAACGTACCCCCTACGTTGTGGTTACCTTTCAAGAATGTGAAAGAATGAACCTCCTTATCAGAGAAATACAGCACTCGTTGAAAGAACTGGATCTGAGTCTGAAGGGTGAGCTAACAATGACCAGCAACATGGAGAGCTTACAGAGAGCTCTGTACCTAGATACTGTACCAGCGTCGTGGGCCAAGAAAGCATACCCATCAACAGCAGGCCTGGCAAGCTGGATTGTGGATCTGCTCACCCGAATTAAAGAGCTGGAAAGATGGATGGGAGACTTTGCATTACCCTCTGCTGTCTGGCTAGCAGGATTTTTTAACCCCCAATCATTCCTAACGGCCATCATGCAGTCCATGGCTCATAAGAATAAGTGGCCACTGGATAAGATAGCCCTGCAGTGTGAAGTGACTAAGAAGAATCGGGAAGATTTCAGTAGCCCACCTCGGGAAGGGGCCTATATCTATGGTCTGTTCATGGAAGGGGCCTGTTGGGATACACAGGCTGGAAATATCACAGAGGCTAGACTGAAGGATCTGACTCCACTCATGCCTGTGATATTCATCAAAGCTGTCCCTGTGGACAAGCAAGACAATCGAAATATCTATCCTTGTCCAGTATACAAGACTTGTCAGCGGGGACCCACTTATATTTGGACATTTGGTCTGAAAACTAAAGAAGCTCCATCCAAATGGGTATTAGCTGGTGTGGCCTTGCTTTTGCAGATT
-"""
+_aln = {
+    "Human": "ATGCGGCTCGCGGAGGAGCGGGCCGCGCTC------GCGGCGGAGAACGCGGATGGGGAACCCGGC---GCCGACCGACGACTGCGACTCCTGGGGACCTACGTGGCCATGAGCCTGCGGCCGGCTGCGGGCGCCTGGGAGCGTTGCGCGGGGAGTGCTGAGGCGGAGCAGCTGCTCCAGGCCTTCCTG---GGCCGCGATGCTGCCGAGGGGCCGCGGCCG------CTGCTGGTGGTGCGGCCCGGGCCCAGGGGCCTGGCAATACGCCCCGGGCTGGAGGTGGGACCTGAGTCGGGCCTGGCTGGCGCTAAGGCGCTTTTTTTCCTTCGCACCGGG---CCCGAGCCTCCAGGGCCCGACAGCTTCCGCGGCGCAGTGGTCTGCGGGGACCTGCCCGCGGCACCTCTGGAGCACCTAGCCGCGCTGTTCTCGGAGGTTGTTCTACCCGTCCTGGCCAATGAGAAGAATCGCCTAAACTGGCCCCACATGATATGTGAGGATGTCAGGCGGCACGCCCACAGCCTCCAATGTGACCTCTCAGTTATACTTGAGCAAGTGAAGGGAAAAACTTTGCTGCCTCTTCCAGCAGGCTCAGAAAAAATGGAGTTTGCGGATTCCAAAAGTGAGACAGTCTTGGATTCTATAGATAAGTCAGTCATCTATGCCATTGAGTCTGCAGTGATCAAATGGAGCTACCAAGTCCAGGTGGTACTCAAGAGAGAGTCTTCCCAGCCACTCTTACAAGGGGAGAATCCCACCCCTAAGGTGGAGTTGGAGTTCTGGAAGAGCAGGTATGAAGATCTGAAATACATCTATAATCAACTGAGAACAATAACGGTGAGGGGCATGGCCAAGCTCCTGGACAAGCTTCAGAGTAGCTACTTTCCAGCTTTCAAAGCCATGTACAGAGATGTTGTTGCAGCTCTAGCAGAGGCACAGGACATCCATGTGCACCTGATACCGCTCCAGCGCCACCTGGAAGCTCTGGAGAATGCAGAATTTCCGGAGGTGAAGCCCCAGCTGCGGCCCCTGCTCCACGTGGTCTGTCTGATTTGGGCCACATGCAAGTCCTACCGCTCCCCGGGAAGGCTGACTGTGCTGCTCCAGGAGATTTGCAACCTTCTCATCCAGCAGGCCTCTAATTATCTCAGCCCAGAAGACCTGCTGAGAAGTGAGGTAGAAGAAAGTCAGAGAAAACTGCAAGTGGTCTCAGACACTTTGAGCTTCTTCAAGCAAGAGTTTCAGGACAGAAGGGAGAATCTCCACACTTACTTCAAAGAGAACCAGGAAGTCAAGGAATGGGATTTCCAGTCTTCTTTGGTCTTTGTGCGATTGGATGGCTTCCTGGGACAACTGCACGTGGTGGAGGGTCTTCTGAAGACGGCCCTGGATTTCCACAAACTGGGAAAGGTGGAGTTCAGCGGCGTCAGAGGGAATGCTCTGAGTCAGCAGGTCCAGCAAATGCATGAAGAATTTCAAGAGATGTACAGGCTTCTCTCAGGATCCTCCTCCGACTGCCTGTACCTCCAAAGCACGGACTTTGAAAATGACGTCTCTGAATTTAACCAGAAAGTAGAAGATCTTGACCGAAGATTGGGGACTATCTTTATTCAAGCTTTTGATGATGCACCTGGCTTGGAGCATGCCTTTAAGCTGCTAGACATAGCAGGAAACCTCCTTGAAAGACCGCTGGTAGCGAGGGATACATCTGATAAATACCTGGTCCTCATCCAAATGTTCAACAAAGATCTGGATGCAGTGAGGATGATCTACAGTCAGCACGTCCAGGAGGAAGCAGAACTTGGGTTCTCCCCGGTGCACAAGAACATGCCCACCGTGGCTGGCGGCCTCCGCTGGGCACAGGAGCTGAGGCAGCGCATCCAGGGTCCTTTCAGCAACTTTGGACGCATCACACACCCTTGCATGGAATCTGCAGAAGGAAAGCGAATGCAACAAAAATATGAAGATATGCTGTCATTGCTAGAAAAGTATGAGACAAGACTTTATGAGGATTGGTGCCGGACAGTATCAGAGAAGTCACAGTACAATCTTTCCCAACCACTTCTAAAACGTGACCCAGAGACGAAGGAGATCACTATCAACTTTAACCCACAGCTGATTTCAGTGCTGAAAGAAATGAGCTATCTTGAACCCAGAGAGATGAAACACATGCCTGAGACAGCAGCAGCCATGTTCTCCTCCAGGGATTTCTATCGGCAGCTTGTGGCTAATTTAGAGTTGATGGCAAATTGGTACAACAAGGTTATGAAAACTCTGCTGGAGGTGGAATTTCCATTAGTGGAGGAAGAGCTGCAAAATATTGATCTCCGCCTCAGAGCAGCAGAGGAGACTTTGAACTGGAAAACAGAAGGCATTTGCGATTATGTCACTGAAATCACCAGTAGTATTCATGATCTTGAACAAAGAATTCAGAAAACTAAAGACAATGTGGAAGAGATCCAAAACATCATGAAAACATGGGTGACTCCAATATTTAAGACAAAAGATGGAAAAAGGGAATCCCTTCTTTCTCTGGATGATCGGCATGATCGAATGGAAAAATATTACAATCTCATCAAGGAATCTGGCCTTAAGATCCACGCCCTTGTTCAGGAAAACCTGGGTCTATTTTCAGCAGACCCAACCTCCAATATCTGGAAGACTTATGTTAACTCTATTGACAATTTGTTGCTGAATGGATTCTTTCTTGCCATTGAGTGCTCCCTCAAGTATCTTCTGGAAAATACTGAGTGTAAGGCAGGACTTACCCCAATATTTGAAGCACAACTGAGTCTAGCCATCCCAGAGCTAGTTTTCTATCCGTCTCTGGAGTCTGGAGTGAAGGGGGGTTTCTGTGACATTGTTGAGGGTCTCATCACCAGCATTTTTAGGATACCATCTCTGGTGCCACGGCTTTCCCCACAAAATGGCTCTCCTCACTATCAGGTCGACCTGGACGGTATACCAGATTTGGCAAACATGCGGCGCACACTCATGGAGAGAGTCCAGAGAATGATGGGCCTCTGCTGTGGCTATCAGAGCACCTTCAGCCAGTATTCGTACCTCTATGTGGAGGACCGGAAGGAGGTTCTGGGTCAGTTTCTGCTGTACGGGCACATCCTCACTCCGGAAGAAATTGAAGACCATGTGGAAGATGGCATCCCAGAGAACCCTCCCCTCCTTTCTCAGTTTAAAGTGCAAATCGACTCCTATGAAACGCTCTATGAAGAGGTGTGCAGGCTGGAACCCATCAAGGTGTTTGACGGCTGGATGAAAATTGATATTCGACCCTTTAAGGCATCTCTGCTGAATATTATTAAGAGGTGGAGCCTCCTGTTCAAACAGCATCTTGTGGACCACGTCACTCACAGCTTGGCCAACCTGGATGCGTTTATAAAGAAGAGTGAGAGCGGCTTACTCAAGAAAGTTGAAAAAGGAGATTTCCAAGGCTTGGTTGAGATCATGGGACACCTTATGGCTGTTAAAGAACGGCAGAGTAACACTGATGAGATGTTTGAGCCCTTAAAGCAGACTATTGAATTGCTGAAGACCTATGAACAAGAATTGCCAGAAACAGTGTTTAAGCAGCTGGAGGAGCTGCCTGAGAAATGGAACAACATAAAAAAGGTGGCCATTACTGTGAAGCAGCAGGTGGCCCCACTGCAGGCAAATGAAGTGACACTCCTCCGCCAGAGGTGCACAGCCTTCGATGCAGAACAGCAGCAATTCTGGGAGCAATTCCACAAAGAAGCCCCGTTCAGGTTTGATAGCATCCACCCTCATCAAATGCTGGATGCCAGGCACATCGAGATCCAGCAGATGGAATCCACTATGGCCTCCATTTCTGAGTCTGCCAGCTTATTTGAAGTCAATGTCCCTGACTATAAGCAGCTGAGGCAGTGCAGGAAGGAGGTCTGCCAGCTGAAGGAGCTCTGGGACACCATTGGAATGGTGACCTCCAGCATCCATGCCTGGGAGACCACACCCTGGAGGAATATCAACGTGGAAGCCATGGAGTTGGAGTGCAAACAGTTTGCCCGGCATATCCGAAACCTGGACAAGGAGGTCAGGGCCTGGGATGCATTCACAGGCCTGGAAAGCACTGTGTGGAACACGCTGAGCTCCCTGAGGGCAGTAGCTGAGCTGCAGAATCCAGCCATCCGGGAGCGGCACTGGAGGCAGCTGATGCAGGCCACCGGTGTGAGCTTCACTATGGACCAGGACACCACCCTAGCGCACCTGCTGCAGCTCCAGCTGCACCACTATGAGGATGAGGTCCGGGGCATTGTGGACAAAGCTGCAAAAGAGATGGGTATGGAGAAAACCTTAAAGGAGCTGCAGACTACCTGGGCTGGCATGGAATTCCAGTATGAGCCCCACCCACGGACCAATGTCCCCCTCCTGTGCTCTGATGAGGACCTCATAGAGGTTCTGGAGGATAATCAAGTTCAACTTCAGAACCTGGTGATGTCCAAGTATGTTGCTTTCTTCTTGGAGGAGGTGTCGGGCTGGCAGAAGAAGCTGTCCACAGTGGACGCTGTCATCTCTATCTGGTTTGAAGTGCAGCGAACATGGACTCACCTGGAAAGCATATTCACTGGATCTGAAGATATTCGGGCACAGCTACCCCAGGATTCTAAAAGGTTTGAAGGCATCGACATTGACTTTAAAGAGCTAGCTTATGATGCCCAGAAAATTCCAAATGTAGTGCAAACCACCAACAAGCCAGGCCTGTATGAAAAGCTGGAGGATATTCAGGGCAGATTGTGCCTGTGTGAGAAGGCCCTGGCAGAGTACCTCGACACCAAGAGGCTTGCCTTCCCGCGGTTTTACTTTCTCTCCTCCTCCGATCTGTTAGACATCCTTTCCAACGGCACAGCTCCACAACAGGTTCAACGTCACCTTTCCAAACTCTTTGACAACATGGCCAAGATGCGATTCCAGCTAGATGCCAGTGGGGAACCAACCAAGACAAGCCTCGGCATGTACAGCAAAGAAGAGGAGTATGTGGCTTTCAGTGAGCCCTGTGACTGCAGCGGGCAGGTAGAAATATGGCTGAACCATGTCCTTGGTCACATGAAGGCCACTGTGAGGCATGAGATGACAGAAGGTGTAACTGCCTATGAAGAAAAGCCGAGGGAGCAGTGGCTTTTTGACCACCCAGCTCAGGTGGCCCTGACCTGTACTCAGATCTGGTGGACAACAGAAGTGGGCATGGCATTTGCCAGGCTGGAGGAAGGCTATGAGAGTGCCATGAAGGACTATTATAAGAAGCAAGTGGCCCAGCTCAAAACCCTTATCACCATGCTGATTGGCCAGCTCTCCAAGGGAGACCGGCAGAAGATTATGACTATATGCACCATCGATGTGCATGCCCGGGATGTGGTAGCCAAGATGATTGCTCAGAAGGTAGACAATGCCCAGGCTTTCCTCTGGCTGTCTCAGCTGCGCCATCGTTGGGATGACGAGGTCAAACACTGCTTTGCCAACATCTGTGATGCCCAGTTTTTGTATTCCTATGAGTACCTGGGAAACACACCTCGCTTGGTGATCACACCTTTGACT------GACAGGTGCTACATCACCCTCACCCAGTCCCTGCACCTGACCATGAGTGGGGCTCCCGCAGGACCTGCAGGCACAGGCAAGACCGAGACCACCAAGGACCTGGGCCGCGCACTGGGCATCCTGGTCTATGTGTTCAACTGCTCGGAGCAGATGGATTACAAGTCTTGTGGCAACATCTACAAAGGCCTTGCTCAGACTGGTGCCTGGGGCTGCTTTGATGAGTTTAATCGAATCTCCGTGGAGGTCTTGTCAGTGGTGGCAGTGCAGGTAAAAAGCATTCAAGATGCGATTAGAGATAAGAAGCAGTGGTTCAGCTTCCTTGGGGAGGAGATCAGCCTGAATCCTTCTGTCGGTATCTTCATCACCATGAACCCAGGCTATGCTGGCCGCACAGAGCTGCCAGAGAATCTCAAGTCTCTCTTCAGGCCTTGTGCAATGGTGGTTCCAGACTTTGAATTGATCTGTGAAATCATGCTGGTGGCAGAAGGATTCATTGAAGCCCAGTCATTAGCCAGAAAGTTCATCACTCTTTACCAGTTGTGCAAAGAGCTTCTCTCCAAACAGGATCACTACGACTGGGGCCTACGGGCCATCAAGTCCGTGCTGGTGGTGGCAGGATCCCTGAAGAGAGGAGACCCTGACCGGCCTGAGGACCAGGTCCTGATGCGCTCCTTGCGGGATTTCAACATCCCCAAGATTGTGACTGATGACATGCCCATCTTCATGGGCCTGATCGGGGACCTCTTTCCCGCCCTGGATGTCCCCCGGAGGAGAGACCCCAACTTCGAAGCTTTGGTTAGGAAGGCGATAGTGGATCTGAAGCTCCAGGCTGAGGACAACTTTGTGCTCAAGGTGGTCCAGCTGGAGGAGCTCCTGGCTGTGCGGCACTCTGTA---TTTGTGGTGGGTGGCGCTGGT------ACCGGCAAGTCACAGGTGCTGAGGTCCTTGCACAAGACCTATCAG------ATCATGAAACGGCGCCCCGTCTGGACTGACCTCAATCCCAAAGCAGTCACAAATGATGAGCTCTTT---------------------------------------GGCATCATCAATCCAGCCACAGGAGAATGGAAGGATGGA---TTGTTCTCTTCCATCATGCGGGAGCTTGCCAACATCACCCATGATGGGCCCAAGTGGATTTTACTGGATGGCGACATAGATCCAATGTGGATTGAATCCCTGAATACTGTCATGGATGATAACAAGGTGCTGACATTGGCCAGCAATGAGAGGATTCCTCTGAACCCCACCATGAAGCTCCTCTTTGAGATCAGCCACCTGCGCACAGCCACTCCAGCAACTGTCTCTAGAGCAGGGATCTTGTACATCAACCCGGCAGACTTGGGATGGAACCCTCCAGTGAGCAGCTGGATTGAGAAGAGGGAAATCCAGACAGAGAGAGCCAACTTAACCATTTTGTTCGACAAGTATCTTCCAACCTGCCTAGACACACTCAGAACCAGGTTTAAGAAGATCATTCCCATCCCAGAGCAGAGCATGGTTCAGATGGTGTGTCACCTTCTGGAATGTCTCCTGACCACGGAGGACATCCCTGCAGACTGCCCTAAGGAAATTTATGAGCATTATTTTGTGTTTGCTGCCATCTGGGCTTTCGGCGGAGCAATGGTCCAAGATCAGCTTGTGGACTACCGGGCAGAGTTCAGCAAATGGTGGCTGACTGAGTTCAAAACAGTCAAGTTTCCTTCCCAAGGAACCATCTTTGACTATTACATCGACCCAGAGACCAAGAAATTCGAGCCTTGGTCCAAGCTCGTCCCCCAGTTCGAATTTGACCCCGAGATGCCCTTGCAGGCGTGTTTGGTGCACACGAGTGAGACCATCCGTGTGTGCTACTTCATGGAGCGGTTGATGGCGCGGCAGCGGCCTGTCATGCTGGTGGGCACGGCTGGCACTGGCAAGTCGGTGCTGGTGGGAGCTAAGCTGGCCAGCCTTGACCCCGAGGCATACCTGGTGAAAAACGTGCCATTCAACTACTACACCACGTCAGCAATGCTGCAGGCTGTCCTGGAGAAGCCTCTGGAAAAGAAGGCTGGCAGAAACTATGGCCCTCCAGGGAACAAGAAACTCATCTATTTCATTGATGACATGAACATGCCTGAGGTGGATGCCTACGGGACGGTGCAGCCCCACACCATCATCCGGCAGCATCTGGACTATGGCCACTGGTATGATCGGAGCAAGCTGTCCCTAAAGGAGATCACAAATGTACAGTATGTTTCCTGTATGAACCCCACGGCAGGCAGCTTCACCATCAACCCCCGGCTTCAGCGTCACTTCAGCGTGTTTGTCCTCTCCTTCCCGGGGGCAGATGCCCTGTCCTCTATCTACAGCATCATCCTCACTCAGCATCTGAAGCTCGGAAACTTCCCGGCGTCCCTGCAGAAATCCATCCCCCCACTGATCGATCTGGCCCTCGCCTTCCACCAGAAAATTGCTACCACCTTCCTACCCACAGGAATCAAATTCCACTACATCTTCAACCTCAGAGATTTTGCCAACATTTTCCAGGGCATTCTCTTCTCCTCAGTGGAATGTGTGAAATCCACATGGGATCTTATAAGGCTCTATCTGCATGAATCAAATCGAGTTTATCGGGATAAGATGGTAGAAGAAAAGGACTTTGATCTTTTTGATAAAATCCAGACAGAAGTGCTCAAGAAAACTTTTGATGATATTGAAGACCCTGTGGAGCAGACCCAAAGCCCGAACCTGTATTGTCACTTTGCAAATGGTATTGGGGAGCCCAAATACATGCCTGTACAGTCTTGGGAACTTTTGACCCAGACTCTGGTGGAGGCCTTGGAGAACCACAATGAAGTCAACACAGTGATGGACCTAGTTCTCTTTGAGGATGCCATGCGCCATGTCTGCCATATCAATCGCATCTTGGAGTCCCCGCGGGGAAATGCTCTGCTGGTTGGTGTAGGTGGGAGCGGCAAGCAGAGCCTGACAAGGCTGGCAGCTTTCATCAGCTCCATGGATGTCTTCCAGATCACACTGCGCAAAGGCTACCAGATCCAGGACTTCAAGATGGACCTGGCCAGCCTGTGTCTGAAAGCTGGAGTGAAGAATCTCAACACAGTGTTTCTCATGACTGATGCCCAAGTGGCTGATGAGAGGTTCCTTGTGCTCATCAATGATCTTTTGGCATCTGGGGAGATCCCAGATCTCTACTCTGATGATGAAGTTGAAAACATCATAAGCAATGTGAGGAATGAAGTCAAGAGCCAGGGTCTGGTTGACAACAGAGAGAACTGTTGGAAGTTCTTTATAGATCGGATCCGGCGACAGCTGAAGGTGACTCTCTGTTTCTCCCCTGTGGGAAACAAGCTAAGAGTCCGCAGCAGGAAGTTCCCAGCCATTGTGAACTGCACAGCCATCCACTGGTTCCACGAGTGGCCTCAGCAAGCATTGGAGTCTGTCAGCCTCCGCTTCTTGCAGAACACAGAGGGCATTGAGCCCACAGTAAAGCAGTCGATTAGCAAATTCATGGCCTTTGTCCACACAAGTGTCAACCAAACATCCCAGTCTTATCTGAGCAATGAACAGCGCTACAACTATACAACTCCCAAGTCCTTTCTGGAGTTCATCAGACTCTACCAGAGCTTGTTGCACAGGCACAGAAAAGAGCTCAAGTGCAAGACAGAGCGGTTGGAGAACGGGCTGCTGAAGCTGCATAGCACCTCTGCCCAGGTGGATGATCTGAAAGCAAAGCTGGCTGCCCAGGAAGTAGAGCTGAAGCAGAAAAATGAAGATGCAGACAAACTGATTCAGGTCGTGGGTGTGGAGACTGACAAAGTGAGCAGAGAGAAAGCCATGGCAGATGAAGAGGAGCAGAAGGTGGCCGTCATCATGCTAGAGGTGAAACAGAAGCAGAAGGACTGTGAGGAGGACCTGGCAAAGGCTGAGCCAGCACTCACAGCAGCGCAGGCAGCTCTCAACACCCTGAACAAGACCAACCTGACAGAGCTGAAGTCATTTGGCTCTCCGCCTCTGGCCGTCAGCAATGTCAGCGCTGCGGTGATGGTACTGATGGCTCCCAGGGGTAGGGTGCCCAAGGACCGGAGCTGGAAGGCTGCTAAGGTCACCATGGCCAAAGTGGATGGCTTCCTGGACTCGCTAATAAACTTCAACAAAGAGAACATTCACGAGAACTGCCTCAAAGCCATCAGGCCGTATCTGCAAGACCCCGAGTTCAATCCTGAGTTTGTGGCCACCAAATCCTATGCGGCTGCAGGCCTCTGCTCCTGGGTCATCAATATTGTGAGATTTTATGAGGTGTTCTGTGATGTGGAACCCAAGCGCCAGGCACTGAACAAAGCCACCGCGGACCTCACAGCTGCCCAGGAGAAGCTGGCTGCCATCAAAGCCAAGATCGCTCACCTTAATGAAAACCTGGCAAAGCTCACAGCCAGGTTTGAGAAAGCAACAGCAGACAAACTCAAATGTCAGCAAGAAGCCGAAGTGACCGCAGTCACCATCTCCCTTGCCAACCGCCTGGTTGGAGGACTCGCTTCTGAAAACGTGAGGTGGGCAGATGCCGTGCAGAACTTCAAACAGCAGGAAAGGACGTTATGTGGAGACATTTTACTTATAACGGCTTTCATTTCCTACCTTGGCTTCTTCACAAAGAAATACCGGCAGAGCCTCCTGGACAGAACTTGGAGGCCCTACCTGAGCCAGCTGAAAACTCCCATTCCAGTCACCCCAGCCCTGGATCCCCTGAGGATGCTGATGGATGATGCTGACGTGGCTGCCTGGCAGAACGAGGGCCTCCCAGCCGACCGCATGTCCGTGGAGAATGCCACCATTCTCATCAACTGTGAGCGCTGGCCACTCATGGTTGACCCTCAGCTACAAGGCATCAAATGGATCAAGAATAAATATGGTGAAGATCTCCGGGTCACGCAGATTGGTCAGAAAGGCTACCTTCAAATCATAGAGCAGGCCCTGGAAGCTGGAGCTGTGGTGCTGATTGAAAATCTAGAGGAGTCCATTGATCCTGTTCTGGGACCCCTGCTTGGGAGAGAAGTCATTAAAAAAGGACGATTCATTAAAATTGGAGACAAAGAATGTGAATACAATCCCAAGTTCCGGCTCATCCTCCACACCAAGCTGGCTAATCCTCACTACCAGCCTGAGCTGCAGGCTCAGGCCACCCTGATCAACTTCACCGTGACCAGGGATGGCCTGGAGGACCAGTTGCTGGCCGCTGTGGTCAGCATGGAGAGGCCAGACTTGGAGCAGCTGAAGTCCGATCTCACAAAGCAGCAGAATGGATTCAAAATTACCCTGAAAACGTTGGAAGACAGTCTTCTCTCTCGCCTCTCCTCCGCCTCTGGGAACTTCCTGGGAGAAACAGTGCTGGTGGAAAACCTAGAGATCACCAAGCAGACTGCTGCCGAAGTTGAGAAAAAGGTCCAGGAGGCCAAGGTGACTGAAGTGAAAATCAACGAGGCCCGAGAGCACTACCGGCCAGCAGCTGCCAGGGCCTCACTGCTCTACTTCATCATGAACGACCTCAGCAAGATCCATCCAATGTACCAGTTTTCTCTCAAGGCCTTCAGTATCGTCTTCCAGAAGGCTGTGGAG------AGGGCTGCTCCTGACGAAAGCCTCAGGGAGCGGGTGGCCAACCTAATAGACAGCATAACCTTCTCTGTGTACCAGTACACCATCCGCGGGCTCTTTGAGTGTGATAAGCTGACCTACCTTGCCCAGCTCACCTTTCAGATTCTCCTCATGAACCGAGAAGTCAATGCAGTGGAGTTGGATTTCCTGCTTCGATCTCCAGTGCAGACGGGCACCGCCAGCCCCGTGGAGTTCCTCTCCCATCAGGCGTGGGGAGCTGTCAAGGTACTTTCATCAATGGAAGAATTCTCTAATCTGGATCGGGACATAGAGGGATCTGCTAAGAGCTGGAAAAAGTTTGTGGAGTCCGAATGTCCTGAGAAAGAGAAGCTCCCACAGGAGTGGAAGAACAAGACAGCCCTGCAGCGCCTCTGCATGCTGAGAGCCATGCGGCCCGACCGGATGACCTATGCTTTGCGAGATTTTGTTGAAGAGAAGTTAGGAAGCAAATACGTGGTGGGAAGAGCCCTAGATTTTGCAACCTCATTTGAAGAATCGGGACCAGCCACTCCTATGTTTTTCATCCTGTCTCCAGGGGTGGACCCACTGAAGGATGTAGAAAGTCAAGGAAGAAAACTTGGATACACCTTCAACAATCAGAACTTTCACAACGTGTCTTTGGGGCAAGGACAGGAAGTGGTGGCTGAGGCTGCGCTGGACCTCGCTGCCAAGAAAGGTCACTGGGTTATTTTGCAGAACATTCACCTGGTGGCCAAGTGGCTCAGCACCCTGGAGAAGAAGCTGGAGGAGCACAGTGAGAACAGCCACCCAGAGTTCAGGGTCTTCATGAGTGCAGAGCCAGCACCCTCCCCTGAGGGCCACATCATCCCCCAGGGCATCCTGGAGAACTCCATTAAGATCACCAATGAGCCCCCCACGGGCATGCATGCCAACCTGCACAAGGCCCTGGACAACTTCACTCAGGACACTCTGGAGATGTGTTCTCGGGAGACGGAGTTTAAGAGCATCCTCTTTGCTCTTTGTTACTTCCATGCGGTGGTGGCAGAAAGACGAAAATTTGGGCCCCAGGGATGGAATCGCTCATACCCCTTTAACACTGGAGACCTCACTATCTCTGTGAATGTCCTCTACAACTTCCTGGAGGCCAACGCAAAGGTCCCCTATGATGATTTGCGCTACCTGTTTGGAGAGATCATGTATGGAGGCCATATCACAGATGACTGGGACAGAAGACTCTGCAGAACCTACCTGGGGGAATTCATTCGACCAGAAATGTTAGAAGGAGAACTGTCTTTGGCCCCAGGGTTCCCACTCCCAGGCAACATGGACTACAATGGTTATCATCAGTACATCGATGCTGAGCTGCCCCCAGAATCCCCCTACCTCTATGGCCTCCACCCGAACGCAGAGATTGGCTTCCTGACCCAAACCTCAGAAAAGCTCTTCCGCACTGTGCTGGAGCTGCAGCCTCGGGACAGCCAGGCCAGAGACGGAGCGGGCGCCACAAGAGAAGAAAAGGTCAAGGCACTTCTGGAAGAAATATTGGAGCGGGTGACAGACGAGTTTAACATCCCAGAACTGATGGCCAAAGTGGAGGAGCGCACCCCTTACATTGTAGTTGCCTTCCAGGAGTGTGGCCGGATGAATATCCTCACCAGAGAGATTCAGCGCTCACTGAGGGAGCTGGAGCTCGGCTTAAAGGGGGAGCTGACTATGACCAGCCACATGGAGAACTTACAGAATGCCCTGTACTTCGATATGGTGCCAGAGTCCTGGGCTAGACGAGCCTACCCTTCCACAGCAGGCCTGGCAGCCTGGTTTCCAGACCTCCTCAACAGAATCAAGGAGCTAGAGGCTTGGACGGGTGACTTTACAATGCCCTCCACTGTGTGGCTGACAGGCTTCTTCAACCCCCAGTCGTTCCTGACTGCCATCATGCAGTCCACGGCTCGCAAGAATGAGTGGCCACTGGACCAGATGGCCCTGCAATGTGACATGACGAAGAAGAACAGAGAAGAGTTTAGGAGTCCTCCTCGGGAAGGGGCCTACATCCATGGCCTCTTCATGGAAGGTGCCTGCTGGGACACACAGGCTGGGATCATTACAGAGGCAAAGCTGAAGGATCTGACACCCCCTATGCCTGTGATGTTCATCAAGGCCATTCCTGCAGATAAGCAGGACTGCCGCAGTGTCTATTCCTGTCCTGTGTACAAGACTAGTCAGCGGGGACCCACCTACGTGTGGACTTTCAACCTGAAGACTAAGGAAAACCCATCCAAGTGGGTTCTGGCTGGAGTAGCCTTGCTTCTCCAGATT",
+    "Mouse": "ATGCCCGGCGCCAAGGAGCAGGCAGCGCTG---------GCGGAGTCTGGGGACGAGGAGCCTGGA------GACCCGAGGCTGCGGCTTCTGGGGACTTTTGTGGCTCGGAGCCTGCGTCCGGCCGCGGGCACCTGGGAGCGCTGTGCAGGCACAGCCGAGGCGGGGAGGCTGCTGCAGGCCTTCCTG---GATCACAACGCTGCCTCGGATCCGCGGCCA------CTGCTGGTGGTTCAGTCCGGGCCCGGGGGCCTGGTGGTGACACCCGGTCTAGACGCAGGACCAGAGCCCAGCCGAGCTCGCGCCAAGGGGCTCTTTTTCTTGCGCACTAAG---TCCGAGCCTCCGGGAAATCACAGCCTCCGCGGCACGGTGCTCTGCGGGGACCTACCCGCGGTGCCACTGGAGCACCTGGCCCCGCTGCTCTCAGAGGTCATTATTCCTGTCCTGGCAAATGAAAAGAACCATTTAGAATGGCCCCACATGGTATGTCAAGACATCAGACATCATGCCCACACCCTGAAGTCTGACCTCCTAGTGATCTTTGAGCACATGAAGGGGAGAACCTTGCTGCCTCTTCCAGTTGGCTCAGAAAAACTGGAGTTTGTGGATGGCCACAGTGAGCCAGTCTCAGATGCCATAGACAAGTCAACTCTCTATGCTGTGGAGTCTGCAGTGATCAAATGGAGCCACCAAGTCCAGGTGGTACTCAAGAGGGAGTCTTCTCAGGCACTCATACAAGGACAAAATCCCACCCCCAAGGTGGAGCTGGAGTTCTGGAAGAGCAGGTGTGAGGACCTGGAACACATTTATAATCAACTAATGACAATCAAGGTGAAGGGAATGGCTGAACTCCTGGACAAACTTCAGAGCAGCTACTTGCCAGCTTTCAAAGCCATGTTCAGAGACGTTGAAGCAGCCCTGACCGAGGCCCAGGACATCCATGTGCACCTGTTACCTCTCCAGCAACACCTGGACATCCTGGAAAACGTGGAGTTTCCCAAGGTGAAGGGCAGGCTGCGGCCTCTGCTCCATGTGGTCTGTCTGATTTGGGCCACCTGCAAATGGTACCGTTCCCCTGGGAGGCTCACAGTGCTGCTCCAAGAAATCTGCAACCTCCTCATCCAGCAGGCCTCTAATTACCTCAGCCCAGAAGACCTCCTGAGAAGTGAGGTGGAAGAGAGTCAGAAAAAACTGCAAGTGGTCTCAGATACCTTAAGCTTCTTCAAACAGGCATTCCAGGACAGAAGGGAGCACCTCCACACTTACTTCAAGGAGGATTCTGAAGTCAGGGTGTGGGATTTCCAAGCATCTCTGGTGTTTGTGCGACTGGATGGCTTTCTGGGCCGAGTGCACATGGTGGAGGATCTTCTGAAGACAGCCTTGGATCTCAACAATCTGGAAAAGCTTGAGTTCAGTGGCCTCAGAGGAAACTCCCTGAGTCAGAAAGTCCAACGCATGCATGAGGAATTTGAGGAGATGTACAAGGTCTTCTTGGACTGCTCCTATGACTGTTTGGACCCCAAGGGCACGGAATTTGAAAATGATGTCTGTGAGTTTAACAAAAGAGTGGAAGATCTTGACCGGAGACTGGGGACTATCTTAATTCAAGCTTTTGATGATGCACCTGATGTGGAACATGCCTTTAAGCTACTGGACATCACAGGAACCCTCATCAAAAGACCCCTGGTAGCACAGGATGTATCACAAAAATACCTGGCCCTCATCCGAATGTTCAGCACAGAACTGGATGCTGTGAGGGTCATCTACAGTCAGCACATCCAGAAGGAGGCAGAGCATGGATTCTCCCCCGTGCACAAGAACATGCCCACTATGGCTGGCGGCATCTGCTGGGCACAGGAACTGAGGCAGCGCGTCAAGGGTCCCTTTGGCAACTTCAAAAACATACCACATCTGTACTTGCAATCTGCTGAAGGAAAGCGAATGATACAAAAATACGAAGACTTGCTCTCCCTGCTAGAAGAGTATGAGAGAAGACTTTATGAGGACTGGTGTCAGACGGTATCTGAAAAGTCACAGTACAATCTTTCCCTACCTCTTTTGCATCGTGACCCCAACACAAAGCAGCTCTCTGTCAACTTTAACCCACAGCTGATTTCAGTGTTGAAAGAAATGAACTATCTTCAGCCCAGTGAGGTGAAAACCATCCCCGAGACCGCAGCAGCCATGTTCTCCTCCAGGGAATTCTATCGTCAGCTTGTGGCCAACTTGGAGTTGATGGCAAATTGGTACAACAAGGTTATAAAAATTCTGCTGGAGGTGGAATTTCCACTAGTGGAGGAAGAACTGCAAAATATTGATCTCCGCCTGAGAGCTGCAGAGGAGACTCTGAGCTGGAAAACAGAAGGCATTTGGGATTATGCTATGCAAATAACCAATAGCATTCATGACCTGGAACAAAGAATTCAGAAGACAAAAGACAATGTGGAAGAGATTCAAAACATCATGAAAACATGGGTGTCTCCAATATTCAAGAGAAAAGATGGGAAAAAAGAATGGCCCCTTTCTCTGGATGATCAGCAGGATCACATGGAAAAATACTACAGTCTCATCCAGGAATCTGGCCTTAAGATTCACGCTCTTGTTCAGGAAAACCTGGTTCTGTTTGCAGCAGACCCAGCATCCAGCATTTGGAAGTCTTACGTGAACTACATTGATTCCATGTTGTTGGATGGATTTTTTCTTGCCATTGAGTGTTCTCTCAAATATCTATTGGAAAACACTGAATGCAAGCCTGGACTCACCCCAGTATTTGAAGCACAGCTCAACCTTGTCACCCCAGAATTAGTTTTCCACCCCTCTCTGGACTCTGGGGTAAAGGGAGGCTTATATGACATTGTCCAGAGTCTTGTCACCAGAATTTTTGCTATGCCATCCCTCGTGCCACGGCTTTCCCCACACAGTGGCTCTCCTCACTATCAGGGTGACCTAGAGGACATGGCCGACTTAGCTGGTCTTCGGAGTGTGCTCATGGAGAGGGTACAGAATATGATGACCCTCTGCTGTGGCTATAGAAATACCCTCAGCCAGTATTCTTACCTGTATGTGGAGGATAGGAAGGAGATTCTTGGTCAGTTTCTGCTCTATGGGCATGTCCTCACACCTGAAGAGATAGAAGCCCATGCCGAAGACGGCATTCCAGAAAATCCACCCCTCCTCCATCACTTCAAAGACCAGATAGACTCCTATGAAAAGCTCTATGAGGAGGTGGTCAGCCTGGAACCCACCAAGGTGTTTGATGGCTGGATGCGAGTGGATGTGAGACCCTTCAAAGCATCTCTGCTGAACACAATAAAGAAGTGGAGCCTCATGTTCAAGCAACATCTTGTTGACTTTGTCACAAACAGCCTGTCTGACCTTGACTCATTCATAAGGAGCACCGAGAGTGGTTTGCTCAAGAGGGTGGAGAAAGGAGATTTCCAAGGATTGGTTGAGATCATGGGACATCTTGTCACCCTTAAAGAACGGCAGAGCAGCACCGATGACATGTTTGAGCCCCTGAAGCAAACGATTGAACTGCTGAAGTCCTACGAACAAGAGCTGCCAGAAACCGTGTTTAAGCAACTGGAGGAGCTTCCTGAGAAGTGGAAGAACATGAAGAAGATGGCCATCACTGTGAGGCAACAGGTGGCCCCTCTGCAGGCAAATGAAGTGGCCCTACTCCGCCAGCGGTGCTCAGCCTTCGATGATGAGCAGCAGCAATTCCAGGAGAGGTTCCGCAAAGAGGCCCCTTTCAGGTTTGATAGTATCAATCCACACCAAATGCTGGATGCCTGGCACGTGGAGATCCAGCACATGGAATCCACCATGGCAACCATCTCTAAGTCGGCTGATTTGTTTGAAGTCAATGTTCCTGACTACAAGCAGCTGAGGCAGTGCAGGAAGGAGGCCTGCCAGTTAAAGGAGCTCTGGGACACCATTGGAATGGTGACCTCCAGCATCCGTGCCTGGGAGGCCACCAGCTGGAGGAATATCAGTGTGGAAGCCATGGACTCAGAGTGCAAGCAGTTCGCCCGGCACATCCGCAACCTAGATAAGGAGTTCAGGTCCTGGGATGCATTCACGGGCCTGGAAAGCACAGTGTTGAACACCCTGACGTCCCTGAGGGCTGTGGCGGAGCTGCAGAATCCTGCCATCCGGGATCGGCACTGGAGGCAGCTGATGCAGGCCACCGGGGTGAATTTCACCATGAATCAGGATACCACCTTAGCTCATCTCCTGCAGCTTCAGCTCCACCACTTCGAGGATGAGGTCCGAGGCATTGTGGACAGAGCTGTCAAAGAGATGAGTATGGAGAAGACCTTAAAGGAATTGCAGACTACCTGGGCCAGCATGGAATTCCAGTACGAGTCCCACGCAAGAACCCGCGTACCCTTGCTGCAGTCAGATGAGGATCTCATTGAGGTCCTAGAGGACAATCAAGTGCAACTTCAGAACCTGATGATGTCCAAATATGTTGCTTTCTTCCTGGAAGAAGTGTCGAGCTGGCAGAAGAAGCTGTCCACGGCTGACTCGGTCATCTCTATCTGGTTTGAGGTGCAGCGCACCTGGTCTCACCTAGAGAGCATATTCATTGGCTCAGAAGATATCCGGGCTCAGCTACCCCAGGACTCTAAGAGATTTGAAGGCATTGACTCTGACTTCAGAGAGCTGGCGTATGATGCTCAGAAAACCCCAAATGTGGTGGAAGCCACAAATAAGTCAGGTCTCTATGAAAAGCTGGAGGATATACAAAGCAGATTATGCCTGTGTGAGAAAGCCCTAGCAGAGTATCTAGACACCAAGAGGCTCAGCTTCCCTCGCTTTTACTTCCTGTCCTCCTCTGACCTGCTGGACATCCTTTCCAATGGCACAGCTCCACAACAGGTTCAACGGCACCTCTCCAAGCTCTTTGACAACATGGCTAAGATGCAGTTCCAGTTAGATGCCAGTCAGAACCCAACCAAGACGAGCCTTGGCATGTACAGCAAAGAGGAGGAATATGTGGCCTTCAGTGAGGCCTGTGACTGCAGTGGGCAGGTTGAAATATGGCTGAACCGTGTTCTTCGTCACATGAAAGCCACTGTGAGGCATGAGATGACAGAGGGGGTCACTGCCTATGAGGAAAAGCCCAGGGATCAGTGGCTGTTTGATTACCCGGCTCAGGTGGCTCTGACCTGCACTCAGATCTGGTGGACGACAGAGGTGGGCATTGCATTTGCCAGGCTGGAGGAAGGCTATGAGAGTGCCATGAAGGACTACTATAAGAAGCAAGTGGCCCAACTCAAAACCCTTATCACCATGCTAATTGGGCCGCTCTCCAAGGGGGACAGGCAAAAGATCATGACCATATGCACCATCGATGTGCATGCCCGGGATGTGGTAGCCAAGATGATTGCTCAAAAGGTTGACAATGCCCAGGCTTTCCTCTGGCTGTCACAGCTACGACATCGTTGGGATGATGAGGCCAAGCACTGCTTTGCTAACATCTGTGACGCCCAGTTTCTATATTCCTATGAGTACTTGGGAAATACACCTCGCCTAGTGATCACGCCTCTGACT------GATAGGTGCTACATCACTCTCACCCAGTCTCTACACTTGACCATGAGTGGGGCTCCAGCAGGACCTGCAGGCACAGGCAAGACAGAGACCACCAAGGACCTGGGCAGAGCACTCGGCATCATGGTCTATGTGTTTAACTGTTCTGAGCAGATGGACTACAAGTCCTGTGGCAACATCTACAAAGGCCTGGCTCAGACTGGTGCCTGGGGCTGTTTTGATGAGTTTAACCGAATCTCTGTGGAGGTCTTGTCGGTGGTGGCTGTCCAGGTAAAAAGCATCCAGGATGCAATCAGAGACAAGAAGCAGAGGTTCAGCTTCCTTGGAGAGGAGATTAGCCTTGACCCTTCAGTGGGCATCTTCATTACCATGAACCCAGGCTATGCTGGCCGCACAGAACTGCCAGAGAACCTCAAGGCCCTTTTCAGGCCCTGTGCAATGGTAGTTCCAGACTTTGAGCTGATCTCTGAGATTATGCTGGTAGCAGAAGGATTCATTGAAGCCCGGTTGTTGGCCAGGAAGTTCATTACCCTTTACCGGCTGTGTAAAGAACTTCTCTCCAAACAGGATCACTATGACTGGGGACTTCGTGCCATTAAGTCTGTCCTCGTGGTAGCAGGATCACTGAAACGGGGAGACCCTGACCGCCCAGAGGACCAAGTCCTGATGCGTTCTTTGAGAGACTTCAACATCCCAAAGATCGTGACAGATGACATGCCGGTGTTCATGGGTCTGATAGGTGACCTCTTTCCTGCTTTGGATGTCCCCAGGAAGAGAGATCTGGACTTTGAGGCTGTGGTTCGGAAAGCAATCGTGGACCTTAAGCTCCAGGCTGAGGACAACTTTGTGCTCAAGGTGGTCCAGCTGGAGGAGCTGCTAGCTGTAAGGCACTCTGTG---TTCGTGGTGGGCGGTGCTGGT------ACCGGGAAGTCACAGGTACTGAGGTCTTTACACAAGACCTATCAG------ATCATGAGACGTCGCCCTGTGTGGACTGACCTCAACCCCAAAGCTGTCACAAATGATGAACTCTTT---------------------------------------GGCATCATCAATCCAGCCACTCGAGAATGGAAGGATGGA---CTGTTCTCTTCCATCATGAGAGAGCTTGCCATCATCTCTCATGATGGGCCCAAGTGGATCTTACTGGATGGCGATATAGACCCGATGTGGATAGAGTCTCTGAACACAGTCATGGATGATAACAAGGTACTGACCCTGGCAAGCAACGAGAGAATCCCCCTTAACCCCACAATGCGTCTTCTTTTCGAGATCAGCCACCTGCGCACAGCCACACCAGCAACCGTCTCCAGAGCAGGGATCCTGTACATAAACCCTGCAGACCTGGGATGGAACCCTCCAGTAAGCAGCTGGATTGATCAGAGAGAAGTCCAGACTGAGAGAGCCAACTTGACCATCCTGTTTGACAAATATCTTCCTACCTGCTTGGACACCCTCAGAACCAGATTTAAGAAAATAATTCCAGTCCCAGAGCAGAGTATGATCCAGATGCTGTGCTACCTCCTTGAGTGCCTCCTGACAAAGGAGGATATCCCTGCAGACTGCCCCAAGGAAATATATGAACTCTATTTTGTGTTTGCTGCCATCTGGGCATTTGGCAGTGCTGTGATCCAAGATCAGCTTGTAGACTACCGGGCAGAGTTCAGCAAATGGTGGCTGACTGAGTTTAAAACAGTCAAGTTTCCTTCCCAAGGAACTGTCTTTGACTACTACATAGACCCAGAGACCAAGAAATTTGAGCCCTGGGCCAAGCTCATCCCCCAGTTTGAATTTGACCCAGAGATGCCTTTGCAGGCTTGTTTGGTACACACAAGTGAGACCATCCGGGTGTGCTACTTCATGGAGCGGCTCATGCAATGGAGGCGGCCGGTTATGCTGGTTGGCCCTGCAGGCTCAGGCAAGTCTGTGCTGGTGGGAGCAAAGCTGTCCAGCCTTAACCCTGAGGAATACATGGTGAAAAATGTGCCCTTCAACTACTATACTACGTCAGCAATGCTGCAAGCTGTCTTGGAGAAACCTCTAGAAAAGAAAGCTGGCAGGAATTATGGCCCTCCAGGCAACAGGAAACTCATCTATTTCATCGATGACATGAATATGCCCGAGGTGGATGCCTATGGCACAGTACAGCCCCACACTGTCATCAGGCAGCACCTAGACTATGGCCACTGGTATGATCGGAACAAGCTGTCTCTGAAGGAGATCATGAATGTACAATACATCTCCTGTATGAACCCCACTGCAGGCAGCTTTACCATCAACCCAAGGCTTCAGCGCCACTTCAGCGTGTTTGCCCTCTGCTTCCCAGGAGCTGATGCCCTCTCTTCCATCTATAGCACCATCTTGACCCATCATCTGAAGTTTGGAAACTTTCCCACCACCCTGCAGAAATCCATCCCTCCTCTGATAAACCTGGCTGTCACCTTCCATCAGAAAATTGCCACCACGTTTCTGCCCACAGCAATCAAATTTCACTACATCTTCAATCTCAGAGATTTTGCCAATATTTTCCAAGGCATTCTTTTCTCCTCCGTGGAATGTGTAAAGTCCACACAGGACCTAGTGAAACTCTATCTGCACGAGTCAAGTCGGGTTTATCGGGATAAGATGGTGGAAGAAAAGGATTTCAATCTTTTTGACAAAATCCAAACAGAATTCCTCAAGAAAAATTTTGATGATAGTGAAGAGGTGCTGAAGCAGACCCAGAACCTGAACATGTATTGTCACTTTGCAAATGGCATTGGTGAGCCCAAGTACATGCCTGTGCAATCATGGGACCTTCTGAATCAGACTCTGGTGGAAGCCCTGGAGAGCCACAATGAAGTGAATGCTGTGATGGACCTAGTTCTCTTTGAGGATGCCATACGTCACATCTGTCACATCAACCGAATCCTGGAGTCCCCTCGAGGAAATGCCCTGCTAGTTGGTGTAGGTGGGAGTGGTAAGCAGAGTCTGACAAAGCTGGCAGCTTTTATCAGTTCCATGGATGTATTCCAGATCACCCTTCGCAAAGGCTACCAAATCCCTGACTTCAAGGTGGACCTGGCCAGCCTCTGTCTGAAAGCTGGGGTAAAAAATCTCAGTACAGTGTTCCTTATGACTGATGCCCATGTGGCTGACGAGAGGTTCCTGGTGCTCATCAATGACCTCCTGGCATCTGGTGAGATCCCAGATCTCTACTCTGATGAGGAAGAGGAGAACATCATAAACAATGTGAGAAATGAGGTCAAAAGCCAGGGACTCATGGACAGCAGGGAGAACTGCTGGAAATTCTTCATAGAGAGAGTCCAGCGACAACTTAAGGTGACTCTCTGTTTCTCCCCTGTGGGGAACAAGCTGAGAATTCGAAGCAGGAAGTTCCCAGCCATTGTGAACTGTACTGCTATCAACTGGTTCCATGAGTGGCCTCAGGAGGCCCTAGAGTCTGTGAGCCTCCGATTCTTGCAGAATACAAAGAACATTGAGCCTGCGGTGAAGCAGTCAATTAGCAAGTTCATGGCCTTTGTCCACATAAGTGTCAACAAGACATCCCAGTCATACCTGACCAATGAGCAGCGATACAACTACACAACACCCAAGTCCTTTCTGGAGTTCATCAGACTGTACCAGAGCTTGCTGGAGAGAAATGGAAAAGAGCTCCAGGCCAAGGTGGAGAGGCTGGAGAACGGGCTGTTGAAACTGCACAGCACCTCGGCCCAGGTGGATGATCTGAAAGCGAAGCTTGCCACCCAGGAAGTGGAGCTGAGGCACAAGAATGAAGATACAGACAAGCTGATTCAGGTGGTGGGTGTGGAGACCAGCAAAGTGAGCAGAGAGAAAGCCATTGCTGACGAGGAGGAGCAGAAGGTGGCCCTGATCATGCTGGAGGTGCAGCAGAAACAGAAAGACTGTGAAGAGGACCTGGCTAAGGCCGAGCCAGCCCTGACCGCAGCGCAGGCGGCCCTCAACACTCTCAACAAGACCAACCTGACAGAGCTGAAGTCATTTGGTTCCCCACCTCTGGCTGTCAGCAATGTCAGCGCCGCGGTGATGGTTCTCATGGCCCCAGGGGGCAAGGTGCCCAAGGACCGCAGCTGGAAGGCTGCCAAAATCACCATGGCCAAGGTGGACAGCTTCCTGGATTCCCTAATCCACTTCGACAAGGAGAACATTCATGAGAATTGCCTCAAAGCCATCAGGCCATACCTGCAAGATCCTGCATTCAACCCAGAGTTTGTGGCCACCAAGTCCTATGCAGCTGCAGGCCTCTGCTCTTGGGTAATCAATATTGTGAGGTTCTATGAGGTCTTCTGTGATGTGGAACCAAAGCGCCAGGCTTTGAACAAAGCCACCTCAGACCTCACAACTGCCCAAGAGAAGCTGGCAGCCATCAAAGCCAAGATCACACACCTTAATGAAAACCTGGCGAAGCTCACCACCAAGTTTGAGAAAGCAACAGCAGAGAAGCTCAAGTGTCAGCAAGAAGCTGAACTGACCGCAGGCACCATTTCGCTTGCAAACCGTCTGGTTGGAGGCCTTGCATCTGAGAACATAAGGTGGGCAGAGGCTGTGCAGAACTTCAGACAGCAGGAAAGGACGTTATGTGGCGACATTCTGCTTACTACAGCTTTCATCTCCTACTTGGGCTTCTTTACCAAAAAGTACCGAAAGAGCCTCATGGATGGGACCTGGAGACCCTATCTGAGCCAACTGAAAGTTCCCATTCCAACCACCCCAACTCTGGACCCCCTGAGGATGCTAACCGATGATGCTGAAGTGGCTGCCTGGCAGAATGAGGGTCTCCCTGCTGACCGCATGTCCATGGAGAATGCTACCATCCTCATCAACTGTGAGCGCTGGCCTCTCATGGTCGACCCTCAACTGCAAGGCATTAAATGGATCAAGAACAAATATGGAGAAGAACTCCGGGTCACCCAGATTGGCCAAAAGGGCTGCCTTCAAACCATAGAGCGAGCCCTGGAAGCTGGAGATGTGGTACTGATTGAGAACCTTGAGGAGTCCATTGATCCCGTCCTGGGACCTCTGCTTGGGAGAGAAGTCATTAAGAAAGGACGGTTTATCAAGATTGGAGACAAGGAGTGTGAATTCAATCCCAAGTTCCGGCTCATCCTTCATACCAAGCTGGCCAACCCTCACTACCAGCCTGAGCTGCAGGCTCAGGCTACCCTGATCAACTTCACGGTGACCAGGGATGGCCTGGAGGACCAGCTGCTGGCTGCTGTGGTCAGCATGGAGAGACCAGACCTGGAACAGCTGAAGTCCGATCTCACAAAGCAGCAGAACGGGTTCAAAATCACCCTCAAAACCTTAGAGGACAACCTGCTATCTCGCCTCTCTTCAGCCTCGGGGAACTTCCTGGGAGAAACAGCCTTGGTGGAGAACCTGGAGGTCACCAAGCAGACTGCTGCAGATGTGGAGGAAAAGGTCCAAGAAGCCAAATTGACAGAAGTAAAAATTAATGAGGCCCGAGAGCACTATAGGCCAGCAGCTGCCCGGGCATCTCTGCTCTACTTCATCATGAATGACCTCAGCAAGATCCATCCAATGTATCAGTTCTCCCTCAAGGCCTTCAGCATTGTCTTCCAGAAAGCTGTGGAG------AAGGCAGCTCCCAGTGAAAGTGTCACAGAGCGAGTGACTAATCTAATAGACAGCATAACTTTCTCAGTGTACCAGTATACCACACGTGGCCTCTTTGAGTGTGATAAGCTGACCTACCTAGCCCAGCTCACCTTTCAGATTCTCCTCGTGAACCAGGAAGTTAATGCAGCAGAGTTGGATTTCTTGCTTAGGGCTCCAGTACAGACAGGGACTCCCAGCCCAATGGAGTTCCTGTCCCACCAGGCCTGGGGAGGCATCAAGGCACTCTCATCAATGGAGGAATTCTGCAATCTGGACCGAGACATTGAAGGCTCTGCCAAGAGCTGGAAAAAGTTTGTGGAGTCAGAGTGTCCCGAGAAGGAGAAGTTTCCCCAGGAGTGGAAGAACAAGACAGCCCTGCAGCGCCTCTGCATGATGAGAGCCATGAGGCCTGACCGGATGACCTATGCCATGCGAGATTTTGTTGAGGAGAAGTTGGGAAGCAAATACGTGATGGGAAGAGCACTCGATTTTGTAACCTCATTTGAAGAGTCAGGACCAGCCACTCCCATGTTTTTCATCCTGTCTCCAGGGGTGGATCCACTGAAGGATGTGGAAAATCAAGGAAAGAAACTTGGATATACATTCAACAATCGGAACTTCCACAACGTGTCCCTAGGGCAAGGACAAGAGGTAGTTGCTGAGGCTGCACTGGACTTGGCTGCTAAGAAGGGTCACTGGGTGATTCTGCAGAACATCCACCTGGTGGCCAAGTGGCTCAGTACCCTGGAGAAGAAACTGGAGGAGCTCAGCGAGGAAAGTCACCCAGACTTCAGGGTCTTCATCAGCGCAGAGCCTGCACCCTCCCCTGAGGGCCACATCATTCCCCAGGGCATTCTGGAAAACTCCATTAAGATCACCAATGAGCCTCCCACAGGCATGCACGCCAACTTACACAAAGCCCTGGACAACTTCACTCAGGACACTCTGGAGATGTGTTCCCGGGAGACAGAGTTCAAGACCATCCTCTTTGCTTTGTGCTACTTTCATGCGGTGGTTGCCGAGAGACGGAAGTTTGGGCCACAGGGCTGGAATCGGTCCTATCCATTTAACACTGGGGACCTCACCATCTCTGTGAATGTGCTATATAATTTCCTGGAGGCTAACACAAAGGTACCCTATGACGACTTGCGTTACCTGTTCGGTGAGATCATGTACGGTGGCCATATCACAGATGACTGGGACAGGAGACTCTGCAGAACCTATTTAGAGGAATTCATTCGGCCAGAGATGCTAGAAGGAGAGCTCTCCCTGGCCCCGGGGTTCCCACTCCCAGGAAACATGGACTACAGTGGCTATCACCAGTATATTGATGCTGAGCTGCCCCCTGAGTCTCCCTACCTATATGGCCTCCATCCAAACGCCGAGATTGGCTTCCTGACCCAGACATCGGAAAAACTCTTCCGAACCGTGCTGGAGATGCAGCCTCGGGACAGCCAGGCTGGAGATGGAGCTGGCATCACAAGGGAAGAAAAGGTCAAAACCTTTCTGGAAGAAATACTGGATCGGATGACAGATGAATTTAACATCGCGGAGCTAATGGCTAAGGTGGAGGAACGCACCCCCTACATTGTAGTTGCCCTCCAGGAGTGTGAACGCATGAACATCCTTACCAGAGAGATCCAGCGCTCACTAAGAGAGCTGCATCTTGGCTTACAGGGGGAGCTGACCATGACCAGTGAGATGGAAAACCTACAGAATGCCCTATATCTAGATGTGGTCCCAGAGTCCTGGGCCAGGCGAGCCTACCCTTCCACAGCAGGCCTGGCAGCCTGGTTTCTAGACCTGCTTAACAGAATCAAAGAGCTGGAGTCCTGGACAGGCGACTTCTTGATGCCCTCAACTGTGTGGCTGACGGGCTTCTTCAACCCCCAGTCCTTCCTGACTGCCATCATGCAGTCCATGGCCCGCAAGAATGAATGGCCACTAGACCAGATGGCCCTGCAGTGTGATGTGACAAAGAAGAACAGAGAGGAGTTCCGGAGCCCTCCTCGGGAAGGGGCCTACATCTATGGGCTCTTCATGGAAGGTGCCTGCTGGGACACACAGACTGGGATCATTGCAGAGGCGAAACTGAAGGACCTGACACCCCCCATGCCTGTGATGTTCCTCAAGGCCATTCCAGCAGATAAGCAAGATTGTCGAAGTGTCTATGCTTGTCCTGTGTACAAGACTTGTCAGCGGGGACCCACCTACGTGTGGACTTTCAATCTGAAGACTAAAGAAAACCCATCCAAGTGGGTTCTGGCTGGTGTTGCCTTGCTTCTCCAGATT",
+    "Opossum": "ATGCCAGTGAAAGTG------GCGGCGGTGTTCTCCGAGGCTGAGGATGGAGATAGCGAGAAAGGG---CCAGATCCTAGAATACGGCTCCTGGGCACCTACGTGATTCGGTGCTTGCGGCCAGCGGCTGGCGCCTGGAAGCGCTGTGTGAGCACGGCCGAATCTGAGCAGCTGCTCCAGGCCTTCCTGACAGGCTACTGCCCTCAAGAG---------CCG------ATGTTGGTGGTGCGTCCTGGGGGTGGGGGTCTGCAGCTGAGCTCCGGG------------CCATACCCCGATTTGTCTCGAGCCAAAGCGCTGTTCTTCCTGCGC---GGTTCCCCCAATCCCCCAGGACCCTTGGGTCCCAAAGGCGCAGTGCTCTGCGGGGAGCTGGCCCCGGCACCGTTGGAGCATCTGGCCACGCTGGTGGCTGAGGTAGTGATGCCTGTCCTGACCAATGAAAAGAATCACCAAGACTGGCCAAATGCTTTATATCAGGATGTAAGACGACACATCCACCACCTTCAAAGTGACCTCATTATCTTCCTGGGTCAAGTGACAGGAAAAACATTGCTGCCTCTTCCAGTAGGCTCAGAAAACATGGAATGTGCGGATTATGAAAATGAAAAAGACTTGGATTTCACAGATAAATCAATTGTCTATGCTATTGAGTCCACAGTGATCAAATGGAGCCACCAGATTCAAATAGTATTAAAGAGGGAATCTTCAGAACCACTCTTGCAGGGAGGAAATCCCACCCCAAAAGTGGAGCTGGAATTCTGGAAGAGAAGGTGTGAAGATCTGGAATATATTTATAATCAGCTGAGAGCCATAGAAGTGAGGGGCATAGCTGCACTTTTAAACAGACTTCAGAGCAGCTACTTCCCAGCCTTCAAAACCATGTTCAAAGATGTAATAGCAGCTTTAAGAGAGGCCCAGGATATCTATGTACATCTGAAGCCCCTGCAACGCCATGTGGAAAACATAGAGAATGTGGAATTTAGTGAAGTGAAGCCACTGGTGGGACCCCTGCTTCACGTAATCTTCTTGATTTGGTCCACATCTAAATACTACTGCTGTCCAGTACGGATCATTGTGCTACTGCAGGAAATATATAACCTTCTCATTCAGCAGGCCTGTACATATCTTAGCCCAGAAGATCTACTGAAAGGGGAGATAGAAGAGAGTCAGAGGAAAATACAGGTGGTGATGGACATTCTTAACTTTTCCAAAGAGATGTTTGAGAATAGGAGGAACAATCTCAAGACTTACTTCAAACAGGTCCGGGAGGTGAAGGAATGGGATTTCAATTCTTCTATGATCTTTGTGCGACTAAATAACTTCCTCAAAAGACTCAAGATGGTAGAGAATCTTCTGAAGACAACCTTGGATTTTCTCAAGCTAGAGAAACTTGAATTCAGTGGAATTAGAGGGAATGCTTTGAGTCAACAAGTCCAGGGCATGTATGAAGAATTCCAGAATGTGTACAAAGCCTTTTCAGAATGCTTCTATGACTGTCTGGACCCTAAACACACAGAATTCGAAAATTATGTTGCTGAATTCAACCACAAAGTAGAAGATTTGGACCGAAGATTAGGGATGGTCTTCCTCCAAGCTTTTAATGATGTATCTGGCTTAGAGCATGCTTTTAAGTTGCTTGAAATATTTGGGAGTCTGCTTGAAAGACCAGTAGTAGCCATGGATGTATTTGATAAATACCCAAGACTGATCACAATGTTCAGCAGTGACTTGGATGCTGTTAAGACAATCTATTGTCAGCATGTCCAGGAGGAGTCAGAACTTGGGTTTTCCCAATTACACAAGAACATGCCGGCAGTAGCTGGGGGTCTCCGCTGGGCTCAGGAACTCAGAGAACGAATTGAAATTTCATTCAATAACCTAAGACATATCAATCACCCCTATATGGAGTCCACTGAAAGTAAAGAGATGTTCCAAAAGTATGGTGAAATGTTTACATTGTTAGAAAAGTATGAGACAAAACTTTATGATAGCTGGTGCCAGACAGTGTCAGAGAAGTCACAATACAATCTCACTCAGCCACTCCTCTGTCATGATCCAGAAACTAAGCAGATCATTGTCAACTTTAACCCACAG---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------GAAAAC---ATGAAA------------------------------------------------------------------------------------------------------------------------------------------------------CTCTTCCTTGCAGATCCAGCATCCAGTATGTGGAAGGCATACATTGACTACCTTGATAACATGGTTCTTCATGGATTCTTCATTGCTATTGAATCTTCTCTAAATTATCTCCTGGAAAATACTGATTCTAAGAACAGACTTCCTCCCTTTTTTGAAACACAATTGGATCTAGTTATACCAGAACTGATCTTTTGTCCCTCTCTGGACCCTTTTGCTAATGGTGGTTTTCAAAGCATTGTGGAGGGACTTATCAATGACATTTTCAAAATATCTTCTATGGTCCCACGACTT---GCACAGAAAAGTTCCCCTCATTATCAGGCTGACATGGAGGACACAGAAGATCTGTCAAACCTGAGGAATATATTAATGGAGAGAGTCAAGAATATGATGACCATCTGCTGTGACTATCGGAACTCCTTTGACCATTACTCTTTCCTTTACATGGATGACCGAAAGGAATTTATGCGACAATTTCTTTTATATGGACATATGCTCACACCAGAGGATTTAGAAGCCCATGGAGAAGATGAGATCCCTGAAAATCCTCCCACTCTTCAGCAATTTAAATTGCAGATTGATTCCTATGAAAAGATTTATGAAGAGGTGAACCAGCTAGAGCCTTTCAGAATTTTTGACAACTGGATGAAAATTGATATTCGTCCATTTAAGATGACTCTACTAAATGTAATTAAGAGATGGAGCTTCATGCTTAAACAGTATCTCATTGATCATGTCACTCACAGTTTGGCAGACCTTGAAGTCTTTATAAGAAATGGTGAGAGTGGTTTAAACAAGAAGATAGAAAAAGGTGATTTTGCTGGTTTGGTTGAAATTATGAGAACTCTAATGGCTCTTAAAGAACGACAGAGCAGCACTGATGAGATGTTTGAGCCACTGAAACACACAATTGATTTACTGAAGATCTATGAACAAGAATTACCTGATTCAGTATTTAAACAGCTAGAGGAATTGCCAGAAAAATGGAGCAACATAAAGAAGATGGCAGTCATTGTAAAACAGCATGTAGCTCCCCTGCAGGAAGATGAAGTAACAGCTCTCCGTCAAAAATGTGCCATTTTTGCTATTGAACAGAATATATTCCAAGATCAGTTCCACAAAGAAGCTCCTTTCAGGTATGATAGCATTAAGCCTCACCAAGTCTTGGATGCCAAAAACATTCAAATCCAACAGATGGAATCTTTCATGTCCTCCATTTCAGACTCTACAAATTTGTTTGAAGTCAACATTCCTGATTACAAACATTTAAAACAATGCAGGAAGGAGATCTGCTTGTTGAAGGAGCTTTGGGATATGATTGACCTAGTGAATTTTAGCATCAATAACTGGAAGGTAACCAAATGGAGGAATATTAATGTAGAGAATATGGACTTAGAATGTAAAAGTTTTGCCAAACAAATATGGAAACTTGGCAAGGATGTGAGAGCCTGGGATGCTTTCATGGGGCTGGACAACACTCTGAAGAACATCCTAACATCCTTACGGGCTGTGTCTGAACTTCAAAATCCAGCCATAAGAGAGAGGCACTGGAACCAGCTGATGCAGGCTACAGGTATGAAATTCATCATGAATGATGATACCACCCTTGAAGACTTACTTAAGCTTGAGCTACACAACTTTGAAGAGGCGATCCAGGGCATTGTAGACAAAGCTGTGAAAGAGATGGACATGGAAAAAGTTCTAAAGGAACTAAAAGCCACTTGGGCAGGGATGGAATTTCATTATGAGCCCCACCCCCGGACAATGGTCCCACTGATGAGGTCTGATGAAGACCTTATTGAAACTCTTGAAGATAACCAAGTCCAGCTGCAGAATCTAATGACATCCAAGTACATAGCCTTCTTCCTGGAGGAAGTATCTGACTGGCAGAAGAAGCTCTCAATAGCTGATGCTGTCATTTCAATCTGGTTTGAAGTACAGCGTACATGGTCTCATCTTGAAAGCATTTTCATTGGATCTGAAGATATACGAGCACAGCTTCCTAAGGACTCTAAACATTTTGAAGACATTGATACTGATTTTAAAGAGTTAGTTTGTGATGCTCAGAAGACACCAAATGTGGTAGAAGCTACCAACAAACCAGGTGTTCATGAACAATTAGAAGATATTCAGAACAGGTTGAGCCAGTGTGAGAAAGCCTTGACTGAATATCTAGATACCAAGAGACTGGTCTTCCCCAGGTTTTATTTCCTCTCCTCTTCCGACCTTTTAGACATCCTTTCCAACGGCACAAATCCACAACAAGTTCAACGCCATCTTTCTAAGTTATTTGACAGTATGGCTAGGATGAAGTTCCAGGTGGACTCCAGTCAAAAACCAACCAAGAAAAGCCTCGGCATGTATAGCAAAGAGGAAGAGTATGTGAATTTCAGTGAGCCTTGTGACTGTAGTGGCCAGGTTGAACTCTGGTTGAATAATGTGCTCAATCACATGAGGGCCACCGTGAGACATGAAATGACAGAAGGTGTCACTGCCTATGAAGAGAAACCTAGGGAACAGTGGCTCTTTGACTACCCTGCTCAGGTGGCCTTGACATGTACCCAGATCTGGTGGACCACTGAGGTGGGAATTTCCTTTGCCAGGCTGGAAGAAGGATATGAAAGTGCCATGAAGGACTATTATAAGAAACAAGTTACCCAACTGAACACCCTGATCACCATGCTAATTGGCCAGCTCTCCATGGGTGACCGTCAAAAAATCATGACCATCTGTACTATTGATGTGCATGCTCGAGATGTAGTTGCCAAGATGATCTCTCAGAAGGTAGAGAACACTCAGGCTTTTCTCTGGATGTCCCAGATGCGCCATCGGTGGGATGATGAGAAAAAGCACTGCTTTGCCAATATTTGTGATGCTGAATTCCTGTATTCCTATGAGTACCTGGGTAACACACCTCGCCTGGTGATTACCCCTCTGACA------GACAGATGTTACATTACCCTCACCCAGTCTTTGCATTTGACCATGAGTGGGGCTCCAGCAGGACCTGCAGGCACTGGCAAAACTGAGACTACCAAAGACCTGGGTCGAGCATTGGGTATCATGGTATATGTGTTTAACTGTTCTGAGCAAATGGACTACAAGTCTTGTGGCAATATCTACAAAGGCCTTTCTCAGACTGGTGCCTGGGGCTGTTTTGATGAATTTAACAGAATTTCTGTGGAGGTTCTTTCCGTGGTGGCAGTACAGGTGAAAAGTATTCAAGATGCAATCAGAGATAAGAAGCTGAATTTCAATTTCCTTGGAGAGGAGATTAAATTGAATCCCTCAGTGGGAATCTTCATCACTATGAACCCAGGATATGCTGGCCGTACAGAGCTTCCAGAGAACCTTAAGGCTCTCTTCAGGCCTTGTGCTATGGTGGTACCAGATTTTGAATTGATCTGTGAAATCATGTTGGTTGCTGAAGGATTCATCGAGGCACAATCATTGGCCAAAAAATTCATTACCCTTTATCAGCTTTGCAAAGAGCTCCTCTCCAAACAGGACCATTATGACTGGGGTCTGCGAGCTATCAAGTCTGTGCTTGTGGTAGCAGGATCTCTGAAGAGAGGAGACCCTGACCGACCTGAAGACCAAGTCCTAATGCGTTCCCTCAGAGACTTCAATATTCCCAAAATCACAACTGATGACATGCCTGTGTTTATAGGCCTAATTGGAGACCTTTTCCCTGCCCTTGACATCCCCAGAAAGAGAGACCTTCACTTTGAAAGTTTTGTAAAACAGGCAATATTAGAGCTAAAACTTCAGGCTGAGGACAACTTTATGCTCAAAGTGGTACAGCTAGAAGAACTTTTGGCTGTGCGACACTCTGTG---TTTGTGGTGGGGAATGCTGGT------ACAGGAAAATCCCAGGTACTAAGATCTTTGCAAAAGACATACCAG------AACATGAAACGACGACCAGTCTGGACTGATCTCAACCCCAAAGCTGTCACTAATGATGAACTATTT---------------------------------------GGCATCATCAACCCAGCCACAAGAGAATGGAAAGATGGA---CTTTTCTCATCAATCTTGCGTGAACTTGCCAATATAATACATGATGGGCCCAAGTGGATTTTACTAGATGGCGATATTGATCCAATGTGGATTGAATCTTTGAACACTGTCATGGATGATAACAAGGTTCTGACTCTGGCAAGCAATGAAAGAATCTCTCTCAATCCAACTATGCGGCTCCTGTTTGAGATCAACCACTTGCACACAGCTACCCCTGCCACAGTCTCCAGAGCAGGAATACTATACATCAATCCTGCAGACCTGGGTTGGAATCCACCAGTGAGTAGTTGGATTGACAAGAGAGAGATACAGTCTGAACGAGCTAACCTGACCATCCTATTTGACAAATATTTGCCACCTTGCCTGGATGTAGTCAAAACAAGATTTAAAAAGATTGTTCCAATACCAGAGCAGAGCATGATTCAAATGCTGTGTTACATTCTTGAGTGTCTTCTAACAAAGGAAAACAGTCCCCCAGACTCTCCCAAGGAACTTCATGAACTTTATTTTGTGTTTGCTTCTATCTGGGCTTTTGGTGGAGTATTGATCCAAGACCAGCTTGTGGATTACAGAGCAGAGTTCAGTAAATGGTGGATAACTGAATTCAAGACAATCAAGTTTCCTTCCCAAGGAACAATCTTTGACTTTTACATAGAACCAGAAACAAAGAAATTTGAGCCGTGGTCCAAACTTATCCCCAAATTTGAATTTGACCCAGACCTACCTTTGCAGACTTGCCTGGTACACACTGTTGAGACCATTCGTGTGTGCTATTTCATGGAGCAGCTCCTGAAACACCGGAGACCTATCATGTTGGTGGGGAATGCAGGCACTGGGAAATCTGTTCTGGTAGGGGCAAAATTGGCTACCCTGGATGCAGATGAATACATGGTGAAGAATATCCCATTTAATTATTATACTACATCTGCAATGCTACAAGCTGTCCTAGAGAAACCTCTAGAAAAGAAAGCTGGAAGAAATTATGGCCCACCTGGCACCAAGAAACTCATCTATTTCATTGATGATATGAACATGCCTGAGGTAGATGCATATGGAACAGTGCAACCCCACACACTCATCAGACAGCATATGGACTATGGGCACTGGTATGACAGAAATAAACTGTTCCTCAAAGAAATCATGAATGTACAATATGTGGCCTGTATGAATCCGACAGCTGGTGGCTTCTCTATCAATCCTCGGCTACAGCGTCATTTTAGCATCTTTGTGCTCTCCTTCCCTGGAGTAGATGCATTGTATTCAATCTATAGCACCATCTTGACTCAGCATCTAAAACTTGGTAACTTTCCAGCATCACTGCTGAATTCCACCCCCCGACTCATTAACTTGGCCATTACCTTCCATCAGAAGATTGCTGCCACTTTTCTCCCCACAGCAATAAAATTCCATTATATCTTCAATCTCCGAGATTTCTCCAACATTTTCCAAGGCATTCTCTTTTCAACAGTGGAAAGTGTTAAAACCACATCAGACCTTGTGAAGCTCTATCTTCATGAGTCCAATCGGGTTTATCGCGATAAGATGGTTGAAGACAATGACTTTGACAACTTTGATAAAATCCAAATTGAAGTGGTAAATAATTTCTTTGATGATATGGACAAGACTCTAGAGGAAATCAAGAGATTGAATATGTACTGCCACTTTGCAAATGGTATTGGTGAGCCCAAATATATGCCAGTGAAGACATGGGAACTGCTTACCCAAATCCTGGTGGAAGCCTTAGAGAACCACAATGAAGTCAATCAAGTGATGAATCTGGTTCTTTTTGAGGATGCCATGTGCCATGTTTGTCGTATCAATCGCATCCTAGAATCCCCAAGGGGGAATGCTTTGCTGGTTGGAGTAGGTGGAAGTGGCAAACAGAGCCTGACAAGACTTGCAGCTTTTATCAGCTCCATGGATGTTTTCCAGATCACTCTAAGAAAAGGTTATTGTATTTCTGATCTTAAGATGGATTTAGCCAATCAGTGCCTAAAAGCTGGAGTAAAGAATGTAAGCACTGTATTTCTCATGACAGATGCCCAAGTTGCTGATGAAAAGTTCCTTGTCCTTATCAATGATCTGTTGGCATCTGGAGAGATTCCAGATCTATACTCTGATGATGAAGTTGAAAACATCATAAACAATATGAGAAATGAAGTCAAGAGCCTGGGTTTGTTTGACAGCAGGGAAACTTGCTGGAAAGTCTTCATAGAAAGGGTCCAAAAACAATTAAAGGTTATACTCTGCTTCTCCCCTGTGGGGAATAAGCTAAGAGTCCGCAGCAGGAAATTTCCAGCCATTGTGAACTGCACAGCCATTGATTGGTTTCATGAGTGGCCTCAGCAAGCACTAGAATCAGTGAGCCTGCGCTTCTTACAAAACATAGAGAATATTGATCCAGCAGTAAAAGAGTCAATTAGTAAATTCATGGCTTATGTACACACGAGTGTCAACCAAATGTCCCAGTCCTACCTGAGCAATGAACGGCGCTATAATTACACCACCCCAAAATCCTTCCTGGAACAGATCAGACTCTATCAGAACCTACTGGTCAAGAATGGCAATGAGCTGACATCTAAAATGAAGAGGCTTAAGAATGGACTGCAGAAGCTTCACAGTACATCTTCCCAGGTAGATGACCTGAAAGCTAAACTGGCACTCCAGGAAATAGAGCTCAGGCAGAAGAATGAAGATGCAGACAAACTAATTCAAGTGGTTGGAGTAGAGACAGAGAAAGTAAGCAAAGAGAAAGCCATTGCTGATGAAGAGGAACATAAAGTGGCTCTAATCATGTTGGAGGTCAAGCAGAAGCAAAAGGACTGTGAGGAAGATCTGGCCAAAGCAGAGCCATCTCTCACAGCAGCCCAAGAAGCTCTCAACACACTCAATAAGACCAACCTAACAGAGCTCAAGTCTTTTGGTTCACCACCTTTGGCTGTCAGCAATGTCACTGCTGCAGTAATGGTTCTCATGGCCCCTGGAGGGAAGGTACCCAGAGATCGAAGTTGGAAAGCTGCCAAGGCTACCATGGCCCGAGTTGATGGCTTTCTGGACGCCCTGGTCAACTTTAACAAAGAGAACATACCTGAGAGCTGCCTCAAAGCTATCCAACCATATATTCAAGATCCAGAATTTAAACCTGAGTTTGTGGCCTCTAAGTCTTTTGCAGCAGCTGGCCTCTGTTCCTGGGTCATAAATATTGTGAGGTTTTATGAGGTTTTCTGTGATGTGGAACCCAAGCGGCAAGCCCTGAGTAAAGCAAGTTTGGATCTTGCTATTGCCCAAGAAAAATTGGCAACCATTAAAATCAAGATTGCTCACCTTAATGAAAACTTGGCAAAACTTACAACCAAATTTGAGAAAGCAACTGCAGAAAAACTCAAATGTCAGCAAGAAGCTGAACTGACCACAGGTACCATCTCACTTGCAAATCGCCTGGTTGGAGGTCTTGCCTCTGAAAATGTAAGATGGGCAGAAGCTATTAAGGACTTCAGACAGCAAGAGAATACATTGTGTGGAGATATTTTACTGATTACAGCTTTCATTTCTTACCTGGGATACTTTACCAAGAAATATCGTCAAAATCTTATGGATTGTAGCTGGAGACCTTACCTGAATCAGTTAAAAGTGCCTTTTCCAGTCACCCCTACTTTAGATCCTCTAAAGATGCTTACTGATGATGTAGACATAGCCACCTGGCAAAATGAGGGTCTTCCTGCTGACCGTGTGTCCACAGAGAATGCTACAATCCTTATCAACAGTGAACGATGGCCACTTATAGTTGACCCTCAACTACAAGGAGTCAAATGGATTAAGAAGAAATATGGCAAAGACCTTCGAGTCATCCAGATTGGAGAGAAAGGATACCTTGATATCATAGAACATGCCCTAGCAGATGGTGATGTAGTGCTGATTGAAAACATAGGAGAGACAGTGGACCCTGTTCTAGGACCCTTGTTGGGAAGAGAAGTAATTAAAAAAGGAAGATTCATTAAAATTGGAGACAAGGAGTGTGAATATAATCCCAGGTTCCGCCTCATTCTTCACACCAAGCTTGCCAATCCTCACTATCAGCCTGAGCTCCAAGCCCAGGCAACGCTTATCAACTTCACTGTGACTAGAGATGGCTTAGAGGATCAGCTTCTGGCAGCAGTGGTCAACATGGAGAGGCCAGACTTAGAAGAACTGAGATCAGATCTGACAAAGCAACAGAATGCATTCAAGATCACACTGAAAACCTTGGAAGACAACTTGCTGTCTTGCCTCTCCTCCGCATCTGGGAACTTCCTGGGAGATATGGCTTTAGTAGAAAACTTAGAGATCACCAAAAAGACAGCTACAGAAATTCAGAAGAAGGTCCAGGAAGCCAAGATAACAGAAATAAAAATTAATGATGCCAGAGAGCATTACCGGCCAGTGGCAGCACGTGCGTCTTTGCTGTACTTCATCATGAATGACCTCAGTAAAATCCATCCCATGTATCAGTTTTCTCTCAAGGCTTTCAGCCTTGTCTTCCAAAAGGCTATACAGAAGACCAAGGCAGATGCTGATGAGAACCTCCAACAGAGAGTAGTCAGTTTGATTGACAACATCACCTTCTCTGTATATCAGTATACTACTAGGGGACTTTTTGAATGTGATAAGCTGACTTACATCACCCAGGTCACCTTTCAGACACTCATGATGAATCAGGAAATCAATGCTGCTGAATTGGACTTCCTTCTCCGGTACCCAGCACAGTCCAATGTTCTGAGCCCTGTGGATTTCCTTTCCAACCAATCCTGGGGAGGTATCAAGACTCTTTCATTAATGGAAGAATTTTGTAATCTGGATCGAGACATTGAAGGGTCTGCCAAACATTGGAAAAAATTTGTGGACTCAGAGTGTCCTGAAAAGGAGAAATTCCCCCAAGCATGGAAAAACAAGTCATCTCTACAAAAACTGTGTATGATGAGGGCAATGAGACCTGACCGAATGACATATGCTATGAGAGATTTTGTGGAAGAAAAGCTAGGAAGCAAATATGTGGTGGGCAGAGCATTAGATCTCTCTACCTCTTTAGAAGAATCAGGGTCTGCAACTCCCATGTTCTTCATACTGTCGCCAGGTGTTGACCCACTGAAAGATGTGGAGAAGGAAGGGAAGAAACTTGGTTATACCTTCAACAACCAGAACTTCCATAATGTGTCCTTGGGTCAAGGACAAGAGGTAGTAGCAGAGGCTGCATTGGATCTGGCTGCCAAGAATGGCCACTGGGTTATCTTACAGAACATCCATCTGGTAGCCAAATGGCTTGGATCCCTTGAGAAGAAGTTGGAACAACATAGCAAGAGCAGTCATCATGAGTTCAGAGTCTTCATGAGTGCTGAGCCTGCAGCTTCTCCTGATGGTCACATCATTCCCCAAGGCATCCTAGAAAATTCAATAAAGATCACTAATGAGCCTCCCATGGGCATGCATGCCAAACTGCACAAGGCCCTGGACAACTTCACTCAGAATACTCTGGAAATGTGTACCCGAGAAACAGAGTTTAAGAGTATTTTATTTGCACTTTGTTACTTCCACGCTGTTGTAGCAGAAAGGCACAAATTTGGACCTCAAGGCTGGAATCGCACTTACCCTTTTAACACTGGAGACCTTACCATCTCAGTGAATGTGCTCTATAACTTTCTTGAGGCCAATGCCAAGGTACCCTATGATGACTTGCGCTATCTCTTTGGTGAGATCATGTATGGAGGTCACATCACAGATAACTGGGACAGGAGGCTTTGTAGAACATACCTGGAGGAATTCATTAAGCCAGAAATGTTAGAAGGAGAAATGTTTCTGGCTCCAGGGTTCCCGATGCCAGGCAATATGGATTACAACAGTTATCACCAGTTCATTGATGACATGCTGCCAACAGAGTCACCATATTTGTATGGGCTTCACCCCAATGCTGAAATTGGCTTTCTGACCCACACCGCAGAAAAGCTCTTCCATACAGTGCTAGAAATGCAGCCTCGGGACAGCCAAGATGGAGATGGAGGAGGGATCACAAGAGAAGAAAAGGTAAAAGCCTTTCTGGATGAAATATTAGAGAAGATAACTGAAGAGTTTAACATTGCAGAGTTGATGGCTAGGGTAGAAGAACGTACCCCCTACGTTGTGGTTACCTTTCAAGAATGTGAAAGAATGAACCTCCTTATCAGAGAAATACAGCACTCGTTGAAAGAACTGGATCTGAGTCTGAAGGGTGAGCTAACAATGACCAGCAACATGGAGAGCTTACAGAGAGCTCTGTACCTAGATACTGTACCAGCGTCGTGGGCCAAGAAAGCATACCCATCAACAGCAGGCCTGGCAAGCTGGATTGTGGATCTGCTCACCCGAATTAAAGAGCTGGAAAGATGGATGGGAGACTTTGCATTACCCTCTGCTGTCTGGCTAGCAGGATTTTTTAACCCCCAATCATTCCTAACGGCCATCATGCAGTCCATGGCTCATAAGAATAAGTGGCCACTGGATAAGATAGCCCTGCAGTGTGAAGTGACTAAGAAGAATCGGGAAGATTTCAGTAGCCCACCTCGGGAAGGGGCCTATATCTATGGTCTGTTCATGGAAGGGGCCTGTTGGGATACACAGGCTGGAAATATCACAGAGGCTAGACTGAAGGATCTGACTCCACTCATGCCTGTGATATTCATCAAAGCTGTCCCTGTGGACAAGCAAGACAATCGAAATATCTATCCTTGTCCAGTATACAAGACTTGTCAGCGGGGACCCACTTATATTTGGACATTTGGTCTGAAAACTAAAGAAGCTCCATCCAAATGGGTATTAGCTGGTGTGGCCTTGCTTTTGCAGATT",
+}
 
 
 class MakeCachedObjects:
diff --git a/tests/test_evolve/test_parameter_controller.py b/tests/test_evolve/test_parameter_controller.py
index 551d39835..fcb284f12 100644
--- a/tests/test_evolve/test_parameter_controller.py
+++ b/tests/test_evolve/test_parameter_controller.py
@@ -1,7 +1,7 @@
 import os
 import warnings
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy.testing import assert_allclose, assert_almost_equal
 
@@ -38,7 +38,8 @@ def setUp(self):
         # length all edges 1 except c=2.  b&d transitions all other
         # transverions
         self.al = make_aligned_seqs(
-            data={"a": "tata", "b": "tgtc", "c": "gcga", "d": "gaac", "e": "gagc"}
+            data={"a": "tata", "b": "tgtc", "c": "gcga", "d": "gaac", "e": "gagc"},
+            moltype="dna",
         )
         self.tree = make_tree(treestring="((a,b),(c,d),e);")
         self.model = cogent3.evolve.substitution_model.TimeReversibleNucleotide(
@@ -165,7 +166,7 @@ def test_set_constant_lengths(self):
         self.assertEqual(lf.get_param_value("length", "d"), 5)
 
     def test_pairwise_clock(self):
-        al = make_aligned_seqs(data={"a": "agct", "b": "ggct"})
+        al = make_aligned_seqs(data={"a": "agct", "b": "ggct"}, moltype="dna")
         tree = make_tree(treestring="(a,b);")
         model = cogent3.evolve.substitution_model.TimeReversibleDinucleotide(
             equal_motif_probs=True, model_gaps=True, mprob_model="tuple"
diff --git a/tests/test_evolve/test_substitution_model.py b/tests/test_evolve/test_substitution_model.py
index eb5ac27a2..5fc9a98df 100644
--- a/tests/test_evolve/test_substitution_model.py
+++ b/tests/test_evolve/test_substitution_model.py
@@ -2,7 +2,7 @@
 
 import os
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.evolve import predicate, substitution_model
 from cogent3.evolve.models import F81, GN, HKY85
diff --git a/tests/test_format/test_bedgraph.py b/tests/test_format/test_bedgraph.py
index 81fe066e1..8c3d8100a 100644
--- a/tests/test_format/test_bedgraph.py
+++ b/tests/test_format/test_bedgraph.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.util.table import Table
 
diff --git a/tests/test_format/test_clustal.py b/tests/test_format/test_clustal.py
index 16a2a57be..e65649a0a 100644
--- a/tests/test_format/test_clustal.py
+++ b/tests/test_format/test_clustal.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 """Tests for Clustal sequence format writer.
 """
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.core.alignment import Alignment
 from cogent3.format.clustal import clustal_from_alignment
diff --git a/tests/test_format/test_fasta.py b/tests/test_format/test_fasta.py
index 362566034..04e611dc8 100644
--- a/tests/test_format/test_fasta.py
+++ b/tests/test_format/test_fasta.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 """Tests for FASTA sequence format writer.
 """
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.core.alignment import Alignment
 from cogent3.core.info import Info
diff --git a/tests/test_maths/test_distance_transform.py b/tests/test_maths/test_distance_transform.py
index e3afc4e52..40423af9f 100644
--- a/tests/test_maths/test_distance_transform.py
+++ b/tests/test_maths/test_distance_transform.py
@@ -3,7 +3,7 @@
 """
 
 from random import randint, sample, seed
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy import array, ones, shape, sqrt
 from numpy.testing import assert_allclose
diff --git a/tests/test_maths/test_geometry.py b/tests/test_maths/test_geometry.py
index 54d798f17..c5d2697f5 100644
--- a/tests/test_maths/test_geometry.py
+++ b/tests/test_maths/test_geometry.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 """Tests of the geometry package."""
 from math import sqrt
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy import allclose, arange, array, insert, isclose, sum, take
 from numpy.linalg import norm
diff --git a/tests/test_maths/test_matrix_exponential_integration.py b/tests/test_maths/test_matrix_exponential_integration.py
index cfab8262a..a50ebc5b7 100644
--- a/tests/test_maths/test_matrix_exponential_integration.py
+++ b/tests/test_maths/test_matrix_exponential_integration.py
@@ -1,4 +1,4 @@
-from unittest import TestCase, main
+from unittest import TestCase
 
 import numpy as np
 
diff --git a/tests/test_maths/test_matrix_logarithm.py b/tests/test_maths/test_matrix_logarithm.py
index 7ce43151e..93ff43526 100644
--- a/tests/test_maths/test_matrix_logarithm.py
+++ b/tests/test_maths/test_matrix_logarithm.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 """Unit tests for matrix logarithm."""
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy import array
 from numpy.testing import assert_allclose
diff --git a/tests/test_maths/test_measure.py b/tests/test_maths/test_measure.py
index 78b723144..943f2a43d 100644
--- a/tests/test_maths/test_measure.py
+++ b/tests/test_maths/test_measure.py
@@ -1,4 +1,4 @@
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy import arange, array, diag_indices, dot, finfo, float64
 from numpy.random import random
diff --git a/tests/test_maths/test_period.py b/tests/test_maths/test_period.py
index dc200964f..bcf8dff5b 100644
--- a/tests/test_maths/test_period.py
+++ b/tests/test_maths/test_period.py
@@ -1,4 +1,4 @@
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy import arange, array, convolve, exp, float64, pi, random, sin, zeros
 from numpy.testing import assert_allclose, assert_almost_equal, assert_equal
diff --git a/tests/test_maths/test_stats/test_contingency.py b/tests/test_maths/test_stats/test_contingency.py
index cbfebaf3e..78188f135 100644
--- a/tests/test_maths/test_stats/test_contingency.py
+++ b/tests/test_maths/test_stats/test_contingency.py
@@ -1,4 +1,4 @@
-from unittest import TestCase, main
+from unittest import TestCase
 
 import numpy
 
diff --git a/tests/test_maths/test_stats/test_distribution.py b/tests/test_maths/test_stats/test_distribution.py
index 353586707..9f39ac71e 100644
--- a/tests/test_maths/test_stats/test_distribution.py
+++ b/tests/test_maths/test_stats/test_distribution.py
@@ -4,7 +4,7 @@
 Currently using tests against calculations in R, spreadsheets being unreliable.
 """
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy.testing import assert_allclose, assert_almost_equal
 
diff --git a/tests/test_maths/test_stats/test_information_criteria.py b/tests/test_maths/test_stats/test_information_criteria.py
index a2a3e9bff..e4edf271e 100644
--- a/tests/test_maths/test_stats/test_information_criteria.py
+++ b/tests/test_maths/test_stats/test_information_criteria.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy.testing import assert_allclose
 
diff --git a/tests/test_maths/test_stats/test_jackknife.py b/tests/test_maths/test_stats/test_jackknife.py
index 61a06fbd6..64e152040 100644
--- a/tests/test_maths/test_stats/test_jackknife.py
+++ b/tests/test_maths/test_stats/test_jackknife.py
@@ -1,4 +1,4 @@
-from unittest import TestCase, main
+from unittest import TestCase
 
 import numpy as np
 
diff --git a/tests/test_maths/test_stats/test_ks.py b/tests/test_maths/test_stats/test_ks.py
index fdd79269f..c0be4559a 100644
--- a/tests/test_maths/test_stats/test_ks.py
+++ b/tests/test_maths/test_stats/test_ks.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy.testing import assert_allclose
 
diff --git a/tests/test_maths/test_stats/test_number.py b/tests/test_maths/test_stats/test_number.py
index a060970a2..265b534ec 100644
--- a/tests/test_maths/test_stats/test_number.py
+++ b/tests/test_maths/test_stats/test_number.py
@@ -1,4 +1,4 @@
-from unittest import TestCase, main
+from unittest import TestCase
 
 import numpy
 
diff --git a/tests/test_maths/test_stats/test_period.py b/tests/test_maths/test_stats/test_period.py
index f14d367c9..66dfa21a9 100644
--- a/tests/test_maths/test_stats/test_period.py
+++ b/tests/test_maths/test_stats/test_period.py
@@ -1,4 +1,4 @@
-from unittest import TestCase, main
+from unittest import TestCase
 
 import numpy
 
diff --git a/tests/test_maths/test_stats/test_special.py b/tests/test_maths/test_stats/test_special.py
index 0440ff562..5056c37fd 100644
--- a/tests/test_maths/test_stats/test_special.py
+++ b/tests/test_maths/test_stats/test_special.py
@@ -3,7 +3,7 @@
 """
 import math
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy.testing import assert_allclose
 
@@ -570,5 +570,3 @@ def test_incbi(self):
 
 
 # execute tests if called from command line
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_maths/test_stats/test_test.py b/tests/test_maths/test_stats/test_test.py
index f99d4aa84..3e302801c 100644
--- a/tests/test_maths/test_stats/test_test.py
+++ b/tests/test_maths/test_stats/test_test.py
@@ -1,7 +1,7 @@
 """Unit tests for statistical tests and utility functions.
 """
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy import (
     arange,
@@ -2094,5 +2094,3 @@ def test_ANOVA_one_way(self):
 
 
 # execute tests if called from command line
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_maths/test_util.py b/tests/test_maths/test_util.py
index 15bf833f1..68f0d06ac 100644
--- a/tests/test_maths/test_util.py
+++ b/tests/test_maths/test_util.py
@@ -4,7 +4,7 @@
 # SUPPORT2425
 # from __future__ import with_statement
 
-from unittest import TestCase, main
+from unittest import TestCase
 from warnings import filterwarnings
 
 from numpy import array, transpose
diff --git a/tests/test_parse/test_blast.py b/tests/test_parse/test_blast.py
index 591618e6d..0e56d85a5 100644
--- a/tests/test_parse/test_blast.py
+++ b/tests/test_parse/test_blast.py
@@ -1,4 +1,4 @@
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy.testing import assert_allclose, assert_equal
 
diff --git a/tests/test_parse/test_blast_xml.py b/tests/test_parse/test_blast_xml.py
index 75f67dc7b..77ce9adf0 100644
--- a/tests/test_parse/test_blast_xml.py
+++ b/tests/test_parse/test_blast_xml.py
@@ -6,7 +6,7 @@
 
 import xml.dom.minidom
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.parse.blast_xml import (
     HIT_XML_FIELDNAMES,
diff --git a/tests/test_parse/test_clustal.py b/tests/test_parse/test_clustal.py
index eb230e04a..af6ed2932 100644
--- a/tests/test_parse/test_clustal.py
+++ b/tests/test_parse/test_clustal.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 """Unit tests for the clustal parsers.
 """
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.parse.clustal import (
     MinimalClustalParser,
diff --git a/tests/test_parse/test_ebi.py b/tests/test_parse/test_ebi.py
index 42eff152f..3cd769c24 100644
--- a/tests/test_parse/test_ebi.py
+++ b/tests/test_parse/test_ebi.py
@@ -2,7 +2,7 @@
 """ Provides tests for EbiParser and related classes and functions.
 """
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.parse.ebi import (
     EbiFinder,
@@ -1049,5 +1049,3 @@ def test_rp_parser(self):
 
 
 # Run tests if called from the command line
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_parse/test_gbseq.py b/tests/test_parse/test_gbseq.py
index a27e04a30..aa939d247 100644
--- a/tests/test_parse/test_gbseq.py
+++ b/tests/test_parse/test_gbseq.py
@@ -1,6 +1,6 @@
 import xml.dom.minidom
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.parse.gbseq import GbSeqXmlParser
 
diff --git a/tests/test_parse/test_gff.py b/tests/test_parse/test_gff.py
index e4653e5a2..68940f9c3 100644
--- a/tests/test_parse/test_gff.py
+++ b/tests/test_parse/test_gff.py
@@ -5,7 +5,7 @@
 
 from io import StringIO
 from pathlib import Path
-from unittest import TestCase, main
+from unittest import TestCase
 
 import pytest
 
diff --git a/tests/test_parse/test_greengenes.py b/tests/test_parse/test_greengenes.py
index 6b7070fa2..4a649a7e0 100644
--- a/tests/test_parse/test_greengenes.py
+++ b/tests/test_parse/test_greengenes.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.parse.greengenes import (
     MinimalGreengenesParser,
diff --git a/tests/test_parse/test_locuslink.py b/tests/test_parse/test_locuslink.py
index dae725bcb..d5b96f439 100644
--- a/tests/test_parse/test_locuslink.py
+++ b/tests/test_parse/test_locuslink.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 """Unit tests for locuslink-specific classes
 """
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.parse.locuslink import (
     LinesToLocusLink,
diff --git a/tests/test_parse/test_ncbi_taxonomy.py b/tests/test_parse/test_ncbi_taxonomy.py
index 03dd641bf..36a18c92a 100644
--- a/tests/test_parse/test_ncbi_taxonomy.py
+++ b/tests/test_parse/test_ncbi_taxonomy.py
@@ -2,7 +2,7 @@
 """Tests of parsers for dealing with NCBI Taxonomy files.
 """
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.parse.ncbi_taxonomy import (
     MissingParentError,
diff --git a/tests/test_parse/test_nexus.py b/tests/test_parse/test_nexus.py
index 7e2541614..a3c766630 100644
--- a/tests/test_parse/test_nexus.py
+++ b/tests/test_parse/test_nexus.py
@@ -1,7 +1,7 @@
 """Unit tests for the Nexus Parser
 """
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3 import load_aligned_seqs
 from cogent3.parse.nexus import (
diff --git a/tests/test_parse/test_pamlmatrix.py b/tests/test_parse/test_pamlmatrix.py
index 8182017ab..42d594cef 100644
--- a/tests/test_parse/test_pamlmatrix.py
+++ b/tests/test_parse/test_pamlmatrix.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 from io import StringIO
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy.testing import assert_equal
 
diff --git a/tests/test_parse/test_phylip.py b/tests/test_parse/test_phylip.py
index f59ad49d2..2be410378 100644
--- a/tests/test_parse/test_phylip.py
+++ b/tests/test_parse/test_phylip.py
@@ -3,7 +3,7 @@
 """Unit tests for the phylip parser
 """
 from io import StringIO
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.parse.phylip import MinimalPhylipParser, get_align_for_phylip
 
diff --git a/tests/test_parse/test_psl.py b/tests/test_parse/test_psl.py
index 4936f634b..09511e7b5 100644
--- a/tests/test_parse/test_psl.py
+++ b/tests/test_parse/test_psl.py
@@ -3,7 +3,7 @@
    Compatible with blat v.34
 """
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.parse.psl import MinimalPslParser, PslToTable
 
@@ -54,7 +54,3 @@ def test_getting_seq_coords(self):
             query_name = row["Q name"]
             query_strand = row["strand"]
             q_start = row["Q start"]
-
-
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_parse/test_pwm_parsers.py b/tests/test_parse/test_pwm_parsers.py
index 61f1bc24a..45a43ed4e 100644
--- a/tests/test_parse/test_pwm_parsers.py
+++ b/tests/test_parse/test_pwm_parsers.py
@@ -1,4 +1,4 @@
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy import array
 from numpy.testing import assert_allclose, assert_array_equal
@@ -38,7 +38,3 @@ def test_cisbp(self):
         assert_allclose(pfm.array, array(expect).T, atol=1e-2)
         assert_allclose(pfm[0, "A"], 0.199862209150251)
         self.assertEqual(pfm[6, "C"], 0.0787969447816471)
-
-
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_parse/test_rdb.py b/tests/test_parse/test_rdb.py
index a4c6e761d..9565835c6 100644
--- a/tests/test_parse/test_rdb.py
+++ b/tests/test_parse/test_rdb.py
@@ -2,7 +2,7 @@
 # test_rdb.py
 """Unit test for RDB Parser
 """
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.core.info import Info
 from cogent3.core.sequence import DnaSequence, RnaSequence, Sequence
@@ -328,6 +328,3 @@ def test_full(self):
 ---CGAU[C(G){--UA}U]ACG-Nooo-
 //
 """
-
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_parse/test_record.py b/tests/test_parse/test_record.py
index e05c9e67b..f3583425d 100644
--- a/tests/test_parse/test_record.py
+++ b/tests/test_parse/test_record.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 """Unit tests for parser support libraries dealing with records.
 """
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.parse.record import (
     DelimitedSplitter,
@@ -591,7 +591,3 @@ def test_altered_default(self):
         self.assertEqual(fm({3: 3, 4: "4"}), {"3": "3", 4: 4})
         # check that unrecognized values get the appropriate conversion
         self.assertEqual(fm({3: 3, 5: "5"}), {"3": "3", "5": 4.5})
-
-
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_parse/test_record_finder.py b/tests/test_parse/test_record_finder.py
index f1f489ae4..3f300a574 100644
--- a/tests/test_parse/test_record_finder.py
+++ b/tests/test_parse/test_record_finder.py
@@ -2,7 +2,7 @@
 """Unit tests for recordfinders: parsers that group the lines for a record.
 """
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.parse.record import RecordError
 from cogent3.parse.record_finder import (
@@ -260,7 +260,3 @@ def ignore_labels(line):
             list(LineGrouper(2, ignore=ignore_labels)(lines)),
             [["abc", "1"], ["def", "2"]],
         )
-
-
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_parse/test_tinyseq.py b/tests/test_parse/test_tinyseq.py
index 89f80de10..2b3fee517 100644
--- a/tests/test_parse/test_tinyseq.py
+++ b/tests/test_parse/test_tinyseq.py
@@ -1,6 +1,6 @@
 import xml.dom.minidom
 
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.parse.tinyseq import TinyseqParser
 
@@ -39,7 +39,3 @@ def test_parse(self):
             self.assertEqual(seq.annotation_db.num_matches(), 2)
 
     pass
-
-
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_parse/test_unigene.py b/tests/test_parse/test_unigene.py
index 9e1cc920b..521d6dd77 100644
--- a/tests/test_parse/test_unigene.py
+++ b/tests/test_parse/test_unigene.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 """Unit tests for unigene-specific classes
 """
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.parse.record_finder import GbFinder
 from cogent3.parse.unigene import (
@@ -236,7 +236,3 @@ def test_LinesToUniGene(self):
 
         # test that the synonym mapping works OK
         self.assertEqual(second.SequenceIds[0].NucleotideId, "g8185938")
-
-
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_recalculation.py b/tests/test_recalculation.py
index 56ec8b7bc..fb304ab92 100644
--- a/tests/test_recalculation.py
+++ b/tests/test_recalculation.py
@@ -1,4 +1,4 @@
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.recalculation.definition import CalcDefn, ParamDefn
 from cogent3.recalculation.scope import (
@@ -150,7 +150,3 @@ def curve(x, y):
         # 'dropoff', dropoff = chi2.isf(1, p) / 2.0.  Also in general you won't need ultra precise answers,
         # so don't use 'xtol=0.0', that's just to make the doctest work.
         pc.graphviz()
-
-
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_tree_distance.py b/tests/test_tree_distance.py
index ec206b7cc..867c207a0 100644
--- a/tests/test_tree_distance.py
+++ b/tests/test_tree_distance.py
@@ -3,17 +3,102 @@
 import pytest
 
 from cogent3 import make_tree
-from cogent3.phylo.tree_distance import lin_rajan_moret
+from cogent3.phylo.tree_distance import (
+    lin_rajan_moret,
+    matching_cluster_distance,
+    rooted_robinson_foulds,
+    unrooted_robinson_foulds,
+)
+
+
+ROOTED_DISTANCE_MEASURES = matching_cluster_distance, rooted_robinson_foulds
+UNROOTED_DISTANCE_MEASURES = lin_rajan_moret, unrooted_robinson_foulds
+
+
+# unrooted rf distance
+def test_unrooted_rf_different_trees():
+    a = make_tree(treestring="(a, b, (c, (d, e)));")
+
+    # There are two splits not in common for the two trees
+    # abc-de in tree a, and ad-dce in tree b.
+    b = make_tree(treestring="(a, b, (d, (c, e)));")
+    distance = unrooted_robinson_foulds(a, b)
+    assert distance == 2
+    distance = unrooted_robinson_foulds(b, a)
+    assert distance == 2
+
+    # Identical unrooted tree as before, but a different orientation.
+    b = make_tree(treestring="((a, b), d, (c, e));")
+    distance = unrooted_robinson_foulds(a, b)
+    assert distance == 2
+    distance = unrooted_robinson_foulds(b, a)
+    assert distance == 2
+
+    # Identical unrooted tree as before, but a different orientation.
+    b = make_tree(treestring="(((a, b), d), c, e);")
+    distance = unrooted_robinson_foulds(a, b)
+    assert distance == 2
+    distance = unrooted_robinson_foulds(b, a)
+    assert distance == 2
 
+    a = make_tree(treestring="(a, (b, (c, d)), (e, (f, (g, h))));")
+    b = make_tree(treestring="(a, (c, (b, d)), (h, (f, (g, e))));")
+
+    # There are six splits not in common.
+    # For tree a they are cd-abefgh, abcdef-gh, and abcde-fgh
+    # For tree b they are bd-acefgh, abcdhf-ge, and abcdh-fge
+    distance = unrooted_robinson_foulds(a, b)
+    assert distance == 6
+    distance = unrooted_robinson_foulds(b, a)
+    assert distance == 6
+
+
+def test_unrooted_rf_same_tree():
+    # Topology of the unrooted trees are identical
+    # though they are stored in different orientations.
+    a = make_tree(treestring="(a, b, (c, d));")
+    b = make_tree(treestring="(c, d, (a, b));")
+
+    distance = unrooted_robinson_foulds(a, b)
+    assert distance == 0
 
-def test_different_trees():
+    distance = unrooted_robinson_foulds(b, a)
+    assert distance == 0
+
+
+def test_unrooted_rf_distance_multifurcation():
+    a = make_tree(treestring="(a, b, (c, (d, e)));")
+    b = make_tree(treestring="(a, b, (c, d, e));")
+
+    # There is one split not in common.
+    # Tree a: abc-de
+    distance = unrooted_robinson_foulds(a, b)
+    assert distance == 1
+
+    distance = unrooted_robinson_foulds(b, a)
+    assert distance == 1
+
+    a = make_tree(treestring="(a, b, (((c1, c2), (d1, (d2, d3))), (e1, (e2, e3))));")
+    b = make_tree(treestring="(a, b, ((c1, c2), (d2, (d1, d3)), (e1, e2, e3)));")
+
+    # There are four splits not in common
+    # Tree a: e1-others, d2,d3-others, c's and d's - others
+    # Tree b: d1,d3-others
+    distance = unrooted_robinson_foulds(a, b)
+    assert distance == 4
+    distance = unrooted_robinson_foulds(b, a)
+    assert distance == 4
+
+
+# lin_rajan_moret distance
+def test_lrm_different_trees():
     a = make_tree(treestring="(1,(((2,3),4),(5,((6,(7,(8,9))),(10,11)))),12);")
     b = make_tree(treestring="(1,((((2,3),4),5),((6,7),((8,9),(10,11)))),12);")
     distance = lin_rajan_moret(a, b)
     assert distance == 8
 
 
-def test_dist_matches_original_implementation_small_tree(DATA_DIR):
+def test_lrm_matches_original_implementation_small_tree(DATA_DIR):
     path, expect = DATA_DIR / "match_dist_100.tree", 1402
     data = path.read_text().splitlines()
     t1 = make_tree(data[0])
@@ -24,7 +109,7 @@ def test_dist_matches_original_implementation_small_tree(DATA_DIR):
 
 
 @pytest.mark.slow
-def test_dist_matches_original_implementation_bigtree(DATA_DIR):
+def test_lrm_matches_original_implementation_big_tree(DATA_DIR):
     path, expect = DATA_DIR / "match_dist_2000.tree", 17037
     data = pathlib.Path(path).read_text().splitlines()
     t1 = make_tree(data[0])
@@ -34,40 +119,255 @@ def test_dist_matches_original_implementation_bigtree(DATA_DIR):
     assert distance == expect
 
 
-def test_same_tree():
+def test_lrm_same_tree():
+    # Topology of the unrooted trees are identical
+    # though they are stored in different orientations.
     a = make_tree(treestring="(a, b, (c, d));")
     b = make_tree(treestring="(c, d, (a, b));")
+
     distance = lin_rajan_moret(a, b)
     assert distance == 0
 
+    distance = lin_rajan_moret(b, a)
+    assert distance == 0
+
+
+# rooted rf distance
+def test_rooted_rf_multifurcation():
+    a = make_tree(treestring="((a, b), (c, d));")
+    b = make_tree(treestring="((a, c, b), d);")
+
+    # There are three clades not in common
+    # Tree a: ab, cd
+    # Tree b: acb
+    distance = rooted_robinson_foulds(a, b)
+    assert distance == 3
+
+    distance = rooted_robinson_foulds(b, a)
+    assert distance == 3
+
+
+def test_rooted_rf_different_trees():
+    a = make_tree(
+        treestring="((((a1,a2),(b1,b2,b3),x),(c1,c2,c3),(d1,d2)),(e1,(e2,e3)));"
+    )
+    b = make_tree(
+        treestring="((((a1,a2),(b1,b2,b3)),x,(c1,c2,c3),(d1,d2)),(e1,(e2,e3)));"
+    )
+
+    # Moving the single x up one level creates two clades that are not in common.
+    # Tree a: ABx
+    # Tree b: AB
+    distance = rooted_robinson_foulds(a, b)
+    assert distance == 2
+
+    distance = rooted_robinson_foulds(b, a)
+    assert distance == 2
+
+    a = make_tree(treestring="((a,b),c);")
+    b = make_tree(treestring="((a,c),b);")
+
+    # There are two clades not in common
+    # Tree a: ab
+    # Tree b: ac
+    distance = rooted_robinson_foulds(a, b)
+    assert distance == 2
+
+    distance = rooted_robinson_foulds(b, a)
+    assert distance == 2
+
+    a = make_tree(treestring="((a,(b1,(b2,b3))),((c1,c2),(c3,c4)));")
+    b = make_tree(treestring="((a,((c1,c2),(c3,c4))),(b1,(b2,b3)));")
+
+    # There are two clades not in common
+    # Tree a: aB
+    # Tree b: aC
+    distance = rooted_robinson_foulds(a, b)
+    assert distance == 2
+
+    distance = rooted_robinson_foulds(b, a)
+    assert distance == 2
+
+    a = make_tree(treestring="(a,(b,(c,(d,(e,f)))));")
+    b = make_tree(treestring="(a,(f,(c,(d,(b,e)))));")
+
+    # There are six clades not in common
+    # Tree a: ef, def, cdef
+    # Tree b: be, bde, bcde
+    distance = rooted_robinson_foulds(a, b)
+    assert distance == 6
+
+    distance = rooted_robinson_foulds(b, a)
+    assert distance == 6
+
+
+def test_rooted_rf_same_tree():
+    a = make_tree(treestring="((a, b), (c, d));")
+    b = make_tree(treestring="((c, d), (a, b));")
+
+    # The distance between trees of the same topology is 0
+    distance = rooted_robinson_foulds(a, b)
+    assert distance == 0
+
+
+# matching cluster distance
+def test_matching_cluster_distance_multifurcation():
+    # The basic example from the matching cluster distance paper
+    # see reference in matching_cluster_distance
+    a = make_tree(treestring="((a, b), (c, d));")
+    b = make_tree(treestring="((a, c, b), d);")
+
+    distance = matching_cluster_distance(a, b)
+    assert distance == 3
+
+    distance = matching_cluster_distance(b, a)
+    assert distance == 3
+
+
+def test_matching_cluster_distance_properties():
+    # Test properties of the matching cluster distance paper shown as exampels
+    # see reference in matching_cluster_distance
+    a = make_tree(
+        treestring="((((a1,a2),(b1,b2,b3),x),(c1,c2,c3),(d1,d2)),(e1,(e2,e3)));"
+    )
+    b = make_tree(
+        treestring="((((a1,a2),(b1,b2,b3)),x,(c1,c2,c3),(d1,d2)),(e1,(e2,e3)));"
+    )
+
+    distance = matching_cluster_distance(a, b)
+    assert distance == 1
+
+    distance = matching_cluster_distance(b, a)
+    assert distance == 1
+
+    a = make_tree(treestring="((a,b),c);")
+    b = make_tree(treestring="((a,c),b);")
+
+    distance = matching_cluster_distance(a, b)
+    assert distance == len(a.tips()) - 1
+
+    distance = matching_cluster_distance(b, a)
+    assert distance == len(a.tips()) - 1
 
+    a = make_tree(treestring="((a,(b1,(b2,b3))),((c1,c2),(c3,c4)));")
+    b = make_tree(treestring="((a,((c1,c2),(c3,c4))),(b1,(b2,b3)));")
+
+    distance = matching_cluster_distance(a, b)
+    assert distance == len(a.tips()) - 1
+
+    distance = matching_cluster_distance(b, a)
+    assert distance == len(a.tips()) - 1
+
+
+def test_matching_cluster_distance_same_tree():
+    a = make_tree(treestring="((a, b), (c, d));")
+    b = make_tree(treestring="((c, d), (a, b));")
+
+    # The distance between trees of the same topology is 0
+    distance = matching_cluster_distance(a, b)
+    assert distance == 0
+
+
+# different tips
 @pytest.mark.parametrize(
     "b",
     (
         "(e, d, (a, b));",
-        "(a, d, (a, b))",
+        "(a, d, (a, b));",
+        "(a, b, (c, (d, e)));",
+    ),
+)
+def test_unrooted_rf_fails_different_tips(b):
+    t1 = make_tree(treestring="(a, b, (c, d));")
+    t2 = make_tree(treestring=b)
+    with pytest.raises(ValueError):
+        unrooted_robinson_foulds(t1, t2)
+    with pytest.raises(ValueError):
+        unrooted_robinson_foulds(t2, t1)
+
+
+@pytest.mark.parametrize(
+    "b",
+    (
         "(e, d, (a, b));",
-        "((e, d), (a, b));",
-        "((e, d), (a, b));",
+        "(a, d, (a, b));",
+        "(a, b, (c, (d, e)));",
     ),
 )
-def test_fails_different_tips(b):
+def test_lrm_fails_different_tips(b):
     t1 = make_tree(treestring="(a, b, (c, d));")
     t2 = make_tree(treestring=b)
     with pytest.raises(ValueError):
         lin_rajan_moret(t1, t2)
+    with pytest.raises(ValueError):
+        lin_rajan_moret(t2, t1)
 
 
+@pytest.mark.parametrize(
+    "b",
+    (
+        "((e, d), (a, b));",
+        "((a, d), (a, b));",
+        "(a, (b, (c, (d, e))));",
+    ),
+)
+def test_rooted_rf_fails_different_tips(b):
+    t1 = make_tree(treestring="((a, b), (c, d));")
+    t2 = make_tree(treestring=b)
+    with pytest.raises(ValueError):
+        rooted_robinson_foulds(t1, t2)
+    with pytest.raises(ValueError):
+        rooted_robinson_foulds(t2, t1)
+
+
+@pytest.mark.parametrize(
+    "b",
+    (
+        "((e, d), (a, b));",
+        "((a, d), (a, b));",
+        "(a, (b, (c, (d, e))));",
+    ),
+)
+def test_matching_cluster_fails_different_tips(b):
+    t1 = make_tree(treestring="((a, b), (c, d));")
+    t2 = make_tree(treestring=b)
+    with pytest.raises(ValueError):
+        matching_cluster_distance(t1, t2)
+    with pytest.raises(ValueError):
+        matching_cluster_distance(t2, t1)
+
+
+# unrooted distance measures applied to examples with rooted trees
 @pytest.mark.parametrize(
     "a,b",
     (
-        ("((a, b), (c, d));", "(e, d, (a, b));"),
-        ("(a, b, (c, d));", "((e, d), (a, b));"),
-        ("((a, b), (c, d));", "((e, d), (a, b));"),
+        ("((a, b), (c, d));", "(d, c, (a, b));"),
+        ("(a, b, (c, d));", "((d, c), (a, b));"),
+        ("((a, b), (c, d));", "((a, c), (b, d));"),
     ),
 )
-def test_fails_unrooted(a, b):
+def test_unrooted_dist_fails_rooted_trees(a, b):
     t1 = make_tree(treestring=a)
     t2 = make_tree(treestring=b)
-    with pytest.raises(ValueError):
-        lin_rajan_moret(t1, t2)
+
+    for unrooted_distance_measure in UNROOTED_DISTANCE_MEASURES:
+        with pytest.raises(ValueError):
+            unrooted_distance_measure(t1, t2)
+
+
+# rooted distance measures applied to examples with unrooted trees
+@pytest.mark.parametrize(
+    "a,b",
+    (
+        ("((a, b), (c, d));", "(d, c, (a, b));"),
+        ("(a, b, (c, d));", "((d, c), (a, b));"),
+        ("(a, b, (c, d));", "(d, c, (a, b));"),
+    ),
+)
+def test_rooted_dist_fails_unrooted_trees(a, b):
+    t1 = make_tree(treestring=a)
+    t2 = make_tree(treestring=b)
+
+    for rooted_distance_measure in ROOTED_DISTANCE_MEASURES:
+        with pytest.raises(ValueError):
+            rooted_distance_measure(t1, t2)
diff --git a/tests/test_util/test_deserialise.py b/tests/test_util/test_deserialise.py
index ef06c7564..d0f46d0e0 100644
--- a/tests/test_util/test_deserialise.py
+++ b/tests/test_util/test_deserialise.py
@@ -696,3 +696,11 @@ def test_roundtrip_seq(mtype):
     assert got.name == "label"
     assert got.moltype == seq.moltype
     assert str(got) == str(seq)
+
+
+@pytest.mark.parametrize("mn", ("BH", "F81", "JC69"))
+def test_dser_submodel(mn):
+    sm = get_model(mn)
+    rd = sm.to_rich_dict(for_pickle=False)
+    got = deserialise_object(rd)
+    assert got.moltype.alphabet.moltype is got.moltype
diff --git a/tests/test_util/test_dictarray.py b/tests/test_util/test_dictarray.py
index 586f12c20..a8cbb1d53 100644
--- a/tests/test_util/test_dictarray.py
+++ b/tests/test_util/test_dictarray.py
@@ -2,7 +2,7 @@
 import os
 
 from tempfile import TemporaryDirectory
-from unittest import TestCase, main
+from unittest import TestCase
 
 import numpy
 
@@ -496,7 +496,3 @@ def test_add(self):
         # must be equal dimensions
         with self.assertRaises(ValueError):
             darr1 + DictArrayTemplate(list("CD"), list("AB")).wrap(data)
-
-
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_util/test_misc.py b/tests/test_util/test_misc.py
index d56bf6256..bf51ada9f 100644
--- a/tests/test_util/test_misc.py
+++ b/tests/test_util/test_misc.py
@@ -463,6 +463,7 @@ def test_curry(self):
         for arg2, result in knowns:
             self.assertEqual(curry_test(arg2), result)
 
+    @pytest.mark.filterwarnings("ignore::UserWarning")
     def test_get_setting_from_environ(self):
         """correctly recovers environment variables"""
         import os
diff --git a/tests/test_util/test_parallel.py b/tests/test_util/test_parallel.py
index 4b43ce063..efda1ca27 100644
--- a/tests/test_util/test_parallel.py
+++ b/tests/test_util/test_parallel.py
@@ -2,7 +2,7 @@
 import sys
 import time
 
-from unittest import TestCase, main, skipIf
+from unittest import TestCase, skipIf
 
 import numpy
 
@@ -60,7 +60,3 @@ def test_is_master_process(self):
             if result:
                 master_processes += 1
         self.assertEqual(master_processes, 0)
-
-
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_util/test_recode_alignment.py b/tests/test_util/test_recode_alignment.py
index c655793fa..5e0c77cd7 100755
--- a/tests/test_util/test_recode_alignment.py
+++ b/tests/test_util/test_recode_alignment.py
@@ -1,4 +1,4 @@
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy.testing import assert_allclose
 
@@ -323,7 +323,3 @@ def test_recode_count_matrix_no_change(self):
         actual = recode_count_matrix(self.alphabet2, self.recoded_m2, self.aa_order2)
         expected = self.recoded_m2
         assert_allclose(actual, expected)
-
-
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_util/test_table.py b/tests/test_util/test_table.py
index eade243f8..d2e25c936 100644
--- a/tests/test_util/test_table.py
+++ b/tests/test_util/test_table.py
@@ -875,7 +875,7 @@ def test_normalized(self):
         """test the table normalized method"""
         t5 = Table(header=self.t5_header, data=self.t5_rows)
         self.assertEqual(
-            t5.normalized().tolist(t5.header),
+            t5.normalized().to_list(t5.header),
             [
                 [0.25, 0.25, 0.25, 0.25],
                 [0.5, 0.0, 0.25, 0.25],
@@ -883,7 +883,7 @@ def test_normalized(self):
             ],
         )
         self.assertEqual(
-            t5.normalized(by_row=False).tolist(t5.header),
+            t5.normalized(by_row=False).to_list(t5.header),
             [[0.25, 0.25, 0.25, 0.25], [0.5, 0.0, 0.25, 0.25], [0.25, 0.75, 0.5, 0.5]],
         )
 
@@ -892,14 +892,14 @@ def test_sorted(self):
         t1 = Table(header=self.t1_header, data=self.t1_rows)
         got = t1.sorted("length")
         self.assertEqual(
-            got.tolist("length"),
+            got.to_list("length"),
             [999, 1353, 1383, 1554, 1599, 1698, 1827, 1977, 2307, 4185],
         )
 
         t5 = Table(header=self.t5_header, data=self.t5_rows)
-        self.assertEqual(t5.sorted("b").tolist("b"), [0, 1, 3])
-        self.assertEqual(t5.sorted().tolist("a"), [1, 1, 2])
-        self.assertEqual(t5.sorted(reverse="a").tolist("a"), [2, 1, 1])
+        self.assertEqual(t5.sorted("b").to_list("b"), [0, 1, 3])
+        self.assertEqual(t5.sorted().to_list("a"), [1, 1, 2])
+        self.assertEqual(t5.sorted(reverse="a").to_list("a"), [2, 1, 1])
 
         table = Table(
             data={
@@ -955,18 +955,18 @@ def test_summed(self):
         with self.assertRaises(TypeError):
             _ = mix.summed(strict=True)
 
-    def test_tolist(self):
-        """test the table tolist method"""
+    def test_to_list(self):
+        """test the table to_list method"""
         t3 = Table(header=self.t3_header, data=self.t3_rows)
-        self.assertEqual(t3.tolist("id"), [6, 7])
-        self.assertEqual(t3.tolist("foo"), ["abc", "bca"])
+        self.assertEqual(t3.to_list("id"), [6, 7])
+        self.assertEqual(t3.to_list("foo"), ["abc", "bca"])
 
-    def test_tolist_column_order(self):
+    def test_to_list_column_order(self):
         """column order of input reflected in result"""
         t3 = Table(header=self.t3_header, data=self.t3_rows)
         rev_order = ["id", "foo", "bar"]
         rev_order.reverse()
-        result = t3.tolist(rev_order)
+        result = t3.to_list(rev_order)
         self.assertEqual(result[0], list(reversed(self.t3_rows[0][:])))
 
     def test_to_dict(self):
@@ -1038,7 +1038,7 @@ def test_with_new_column(self):
         """test the table with_new_column method"""
         t5 = Table(header=self.t5_header, data=self.t5_rows)
         t5_row_sum = t5.with_new_column("sum", sum, t5.header)
-        self.assertEqual(t5_row_sum.get_columns("sum").tolist(), [4, 4, 8])
+        self.assertEqual(t5_row_sum.get_columns("sum").to_list(), [4, 4, 8])
         # now using a string expression
         t8 = Table(header=self.t8_header, data=self.t8_rows, index_name="edge.name")
         n = t8.with_new_column("YZ", callback="y+z")
@@ -1709,13 +1709,13 @@ def test_head(self):
         t.head(nrows=3)
         self.assertEqual(head.data.shape[0], 3)
         self.assertEqual(len(head.output.splitlines()), 9)
-        self.assertEqual(head.data.tolist(), self.t1_rows[:3])
+        self.assertEqual(head.data.to_list(), self.t1_rows[:3])
         # tests when number of rows < default
         t = make_table(data=dict(a=["a"], b=["b"]))
         t.head()
         self.assertEqual(head.data.shape[0], 1)
         self.assertEqual(len(head.output.splitlines()), 7)
-        self.assertEqual(head.data.tolist(), [["a", "b"]])
+        self.assertEqual(head.data.to_list(), [["a", "b"]])
         table.display = display
 
     def test_tail(self):
@@ -1730,7 +1730,7 @@ def test_tail(self):
         self.assertEqual(tail.data.shape[0], 3)
         self.assertEqual(len(tail.output.splitlines()), 9)
         self.assertEqual(
-            [int(v) for v in tail.data[:, -1].tolist()],
+            [int(v) for v in tail.data[:, -1].to_list()],
             [r[-1] for r in self.t1_rows[-3:]],
         )
         # tests when number of rows < default
@@ -1738,14 +1738,14 @@ def test_tail(self):
         t.tail()
         self.assertEqual(tail.data.shape[0], 1)
         self.assertEqual(len(tail.output.splitlines()), 7)
-        self.assertEqual(tail.data.tolist(), [["a", "b"]])
+        self.assertEqual(tail.data.to_list(), [["a", "b"]])
         table.display = display
 
     @skipIf(DataFrame is None, "pandas not installed")
-    def test_to_dataframe(self):
+    def test_to_pandas(self):
         """produces a dataframe"""
         t = Table(header=self.t1_header, data=self.t1_rows)
-        df = t.to_dataframe()
+        df = t.to_pandas()
         self.assertIsInstance(df, DataFrame)
         data = df.to_numpy()
         self.assertEqual(data.tolist(), self.t1_rows)
diff --git a/tests/test_util/test_transform.py b/tests/test_util/test_transform.py
index f7f2731a5..4c69e8d9f 100644
--- a/tests/test_util/test_transform.py
+++ b/tests/test_util/test_transform.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 """Tests of transformation and composition functions .
 """
-from unittest import TestCase, main
+from unittest import TestCase
 
 from numpy.testing import assert_allclose
 
@@ -199,8 +199,3 @@ def test_first_index_in_set(self):
         self.assertEqual(first_index_in_set(s2, vowels), 3)
         self.assertEqual(first_index_in_set(s3, vowels), None)
         self.assertEqual(first_index_in_set(s4, vowels), None)
-
-
-# run tests if invoked from the commandline
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_util/test_union_dict.py b/tests/test_util/test_union_dict.py
index 966137269..32cb8dc20 100644
--- a/tests/test_util/test_union_dict.py
+++ b/tests/test_util/test_union_dict.py
@@ -2,7 +2,7 @@
 
 """Unit tests for union_dict.
 """
-from unittest import TestCase, main
+from unittest import TestCase
 
 from cogent3.util.union_dict import UnionDict
 
@@ -113,7 +113,3 @@ def test_setitem(self):
         self.assertEqual(d.b.c, 42)
         self.assertEqual(d["b"], dict(c=42))
         self.assertIsInstance(d.b, UnionDict)
-
-
-if __name__ == "__main__":
-    main()
diff --git a/tests/test_util/test_warning.py b/tests/test_util/test_warning.py
index 391d2663b..e4dbe916d 100644
--- a/tests/test_util/test_warning.py
+++ b/tests/test_util/test_warning.py
@@ -6,7 +6,6 @@
 from cogent3.util.warning import deprecated_args, deprecated_callable
 
 
-@pytest.mark.filterwarnings("ignore::DeprecationWarning")
 def test_function_deprecated_args():
     @deprecated_args(
         version="a future release",
@@ -16,12 +15,12 @@ def test_function_deprecated_args():
     def changed(a: int, b: int) -> int:
         return a + b
 
-    expected = changed(a=5, b=3)
-    got = changed(x=5, y=3)
-    assert got == expected
+    with pytest.deprecated_call():
+        expected = changed(a=5, b=3)
+        got = changed(x=5, y=3)
+        assert got == expected
 
 
-@pytest.mark.filterwarnings("ignore::DeprecationWarning")
 def test_function_deprecated_args_docstring():
     @deprecated_args(
         version="a future release",
@@ -65,7 +64,6 @@ def changed(a: int, b: int) -> int:
         changed(a=5, b=3)
 
 
-@pytest.mark.filterwarnings("ignore::DeprecationWarning")
 def test_function_deprecated_args_pickled():
     @deprecated_args(
         version="a future release",
@@ -75,11 +73,12 @@ def test_function_deprecated_args_pickled():
     def changed(a: int, b: int) -> int:
         return a + b
 
-    myfunc = changed(x=1, y=2)
-    pickled_func = pickle.dumps(myfunc)
-    assert isinstance(pickled_func, bytes)
-    unpickled_func = pickle.loads(pickled_func)
-    assert unpickled_func == changed(a=1, b=2)
+    with pytest.deprecated_call():
+        myfunc = changed(x=1, y=2)
+        pickled_func = pickle.dumps(myfunc)
+        assert isinstance(pickled_func, bytes)
+        unpickled_func = pickle.loads(pickled_func)
+        assert unpickled_func == changed(a=1, b=2)
 
 
 class foo:
@@ -98,7 +97,6 @@ def changed(self, a: int, b: int):
         self.b = b
 
 
-@pytest.mark.filterwarnings("ignore::DeprecationWarning")
 def test_method_deprecated_args():
     foo_instance = foo()
     foo_instance.changed(a=5, b=3)
@@ -106,7 +104,6 @@ def test_method_deprecated_args():
     assert foo_instance.b == 3
 
 
-@pytest.mark.filterwarnings("ignore::DeprecationWarning")
 def test_method_deprecated_args_docstring():
     assert foo.changed.__doc__ == "This is a test function"
     foo_instance = foo()
@@ -126,12 +123,12 @@ def test_method_correct_args_do_not_warn():
         foo().changed(a=5, b=3)
 
 
-@pytest.mark.filterwarnings("ignore::DeprecationWarning")
 def test_method_deprecated_args_pickled():
     foo_instance = foo()
     assert foo_instance.a == 0
     assert foo_instance.b == 0
-    foo_instance.changed(x=1, y=2)  # pylint: disable=no-value-for-parameter
+    with pytest.deprecated_call():
+        foo_instance.changed(x=1, y=2)  # pylint: disable=no-value-for-parameter
     assert foo_instance.a == 1
     assert foo_instance.b == 2
 
@@ -144,7 +141,8 @@ def test_method_deprecated_args_pickled():
     assert isinstance(unpickled_foo, foo)
     assert unpickled_foo.a == 1
     assert unpickled_foo.b == 2
-    unpickled_foo.changed(x=2, y=3)
+    with pytest.deprecated_call():
+        unpickled_foo.changed(x=2, y=3)
     assert unpickled_foo.a == 2
     assert unpickled_foo.b == 3
 
@@ -214,7 +212,6 @@ def test_deprecated_callable_resolves_type(recwarn, func, _type):
     assert _type in recwarn.list[0].message.args[0]
 
 
-@pytest.mark.filterwarnings("ignore::DeprecationWarning")
 def test_function_deprecated_args_deprecated_callable_chained_decorators(recwarn):
     @deprecated_args("2023.6", "x is not descriptive", old_new=[("x", "a")])
     @deprecated_args("2023.6", "b is no longer required", discontinued=["b"])