diff --git a/.gitignore b/.gitignore
index 4234921..29b9dee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ __pycache__
 build
 dist
 seqgen.egg-info
+.pytest_cache
diff --git a/README.md b/README.md
index 2ae492c..af241af 100644
--- a/README.md
+++ b/README.md
@@ -50,7 +50,7 @@ optional arguments:
                         100)
 ```
 
-## Sequence spefication
+## Sequence specification
 
 Your JSON specifies what sequences you want created.
 
@@ -148,6 +148,11 @@ specification keys you can put in a object in the JSON is as follows:
   an internal name.
 * `random aa`: The sequence should be made of random amino acids.
 * `random nt`: The sequence should be made of random nucleotides.
+* `ratchet`: If `true` and a count and mutation rate are given, sequences
+  after the first will be mutants of the previous sequence. This can be
+  used to build a series of sequences that are successive mutants of one
+  another. For the name derivation, see
+  [Muller's Ratchet](https://en.wikipedia.org/wiki/Muller's_ratchet).
 * `sections`: Gives a list of sequence sections used to build up another
   sequence (see example above). Each section is a JSON object that may
   contain the keys present in a regular sequence object, excluding `count`,
diff --git a/seqgen/__init__.py b/seqgen/__init__.py
index 58c2ae3..285814d 100644
--- a/seqgen/__init__.py
+++ b/seqgen/__init__.py
@@ -2,6 +2,6 @@ from seqgen.sequences import Sequences
 
 # Note that the version string must have the following format, otherwise it
 # will not be found by the version() function in ../setup.py
-__version__ = '1.0.10'
+__version__ = '1.0.11'
 
 __all__ = ['Sequences']
diff --git a/seqgen/sequences.py b/seqgen/sequences.py
index 7e31515..3af0d90 100644
--- a/seqgen/sequences.py
+++ b/seqgen/sequences.py
@@ -23,6 +23,7 @@ class Sequences(object):
     DEFAULT_LENGTH = 100
     DEFAULT_ID_PREFIX = 'seq-id-'
     LEGAL_SPEC_KEYS = {
+        'alphabet',
         'count',
         'description',
         'id',
@@ -33,12 +34,14 @@ class Sequences(object):
         'name',
         'random aa',
         'random nt',
+        'ratchet',
         'sections',
         'sequence',
         'sequence file',
         'skip',
     }
     LEGAL_SPEC_SECTION_KEYS = {
+        'alphabet',
         'from name',
         'length',
         'mutation rate',
@@ -82,8 +85,30 @@ class Sequences(object):
         self._vars = vars_
         self._sequenceSpecs = list(map(self._expandSpec, sequenceSpecs))
         self._checkKeys()
+        self._checkValid()
         self._names = {}
 
+    def _checkValid(self):
+        """
+        Check that all specification dicts contain sensible values.
+
+        @param sequenceSpec: A C{dict} with information about the sequences
+            to be produced.
+        @raise ValueError: If any problem is found.
+        """
+        for specCount, spec in enumerate(self._sequenceSpecs, start=1):
+            if spec.get('ratchet'):
+                nSequences = spec.get('count', 1)
+                if nSequences == 1:
+                    raise ValueError(
+                        'Sequence specification %d is specified as ratchet '
+                        'but its count is only 1.' % specCount)
+
+                if 'mutation rate' not in spec:
+                    raise ValueError(
+                        'Sequence specification %d is specified as ratchet '
+                        'but does not give a mutation rate.' % specCount)
+
     def _checkKeys(self):
         """
         Check that all specification dicts only contain legal keys.
@@ -147,11 +172,15 @@ class Sequences(object):
             new[k] = value
         return new
 
-    def _specToRead(self, spec):
+    def _specToRead(self, spec, previousRead=None):
         """
         Get a sequence from a specification.
 
         @param spec: A C{dict} with keys/values specifying a sequence.
+        @param previousRead: If not C{None}, a {dark.Read} instance containing
+            the last read this method returned. This is only used when
+            'ratchet' is given for a specification, in which case we generate
+            a mutant based on the previous read.
         @raise ValueError: If the section spec refers to a non-existent other
             sequence, or to part of another sequence but the requested part
             exceeds the bounds of the other sequence. Or if the C{spec} does
@@ -159,10 +188,14 @@ class Sequences(object):
             to.
         @return: A C{dark.Read} instance.
         """
-        nt = True
+        alphabet = self.NT
         length = spec.get('length', self._defaultLength)
 
-        if 'from name' in spec:
+        if spec.get('ratchet') and previousRead:
+            read = Read(None, previousRead.sequence)
+            alphabet = previousRead.alphabet
+
+        elif 'from name' in spec:
             name = spec['from name']
             try:
                 namedRead = self._names[name]
@@ -176,6 +209,7 @@ class Sequences(object):
                 # named read.
                 length = spec.get('length', len(namedRead))
                 sequence = namedRead.sequence[index:index + length]
+                alphabet = namedRead.alphabet
 
                 if len(sequence) != length:
                     raise ValueError(
@@ -201,37 +235,39 @@ class Sequences(object):
                 raise ValueError("Sequence file '%s' could not be read." %
                                  spec['sequence file'])
 
+        elif spec.get('alphabet'):
+            alphabet = spec['alphabet']
+            read = Read(None, ''.join(choice(alphabet) for _ in range(length)))
+
         elif spec.get('random aa'):
-            read = Read(None, ''.join(choice(self.AA) for _ in range(length)))
-            nt = False
+            alphabet = self.AA
+            read = Read(None, ''.join(choice(alphabet) for _ in range(length)))
 
         else:
-            # Assume random nucleotides are wanted.
-            read = Read(None, ''.join(choice(self.NT) for _ in range(length)))
+            read = Read(None, ''.join(choice(alphabet) for _ in range(length)))
 
         try:
             rate = spec['mutation rate']
         except KeyError:
             pass
         else:
-            read.sequence = self._mutate(read.sequence, rate, nt)
+            read.sequence = self._mutate(read.sequence, rate, alphabet)
+
+        read.alphabet = alphabet
 
         return read
 
-    def _mutate(self, sequence, rate, nt):
+    def _mutate(self, sequence, rate, alphabet):
         """
         Mutate a sequence at a certain rate.
 
         @param sequence: A C{str} nucleotide or amino acid sequence.
         @param rate: A C{float} mutation rate.
-        @param nt: If C{True} the sequence is nucleotides, else amino acids.
+        @param alphabet: A C{list} of alphabet letters.
         @return: The mutatated C{str} sequence.
         """
         result = []
-        if nt:
-            possibles = set(self.NT)
-        else:
-            possibles = set(self.AA)
+        possibles = set(alphabet)
         for current in sequence:
             if uniform(0.0, 1.0) < rate:
                 result.append(choice(list(possibles - {current})))
@@ -247,17 +283,24 @@ class Sequences(object):
         @param sequenceSpec: A C{dict} with information about the sequences
             to be produced.
         """
+        alphabet = None
+        previousRead = None
         nSequences = spec.get('count', 1)
         for count in range(nSequences):
             id_ = None
             if 'sections' in spec:
                 sequence = ''
                 for section in spec['sections']:
-                    sequence += self._specToRead(section).sequence
+                    read = self._specToRead(
+                        section, previousRead)
+                    sequence += read.sequence
+                    if alphabet is None:
+                        alphabet = read.alphabet
             else:
-                read = self._specToRead(spec)
+                read = self._specToRead(spec, previousRead)
                 sequence = read.sequence
                 id_ = read.id
+                alphabet = read.alphabet
 
             if id_ is None:
                 try:
@@ -284,6 +327,7 @@ class Sequences(object):
                 pass
 
             read = Read(id_, sequence)
+            read.alphabet = alphabet
 
             # Keep a reference to this result if it is named.
             if count == 0 and 'name' in spec:
@@ -296,6 +340,7 @@ class Sequences(object):
 
             if not spec.get('skip'):
                 yield read
+                previousRead = read
 
     def __iter__(self):
         for sequenceSpec in self._sequenceSpecs:

Add a 'linear' directive to allow a set of mutant sequences to be successively generated from the previous one
diff --git a/test/testSequences.py b/test/testSequences.py
index 5e9e4ef..b4b839d 100644
--- a/test/testSequences.py
+++ b/test/testSequences.py
@@ -33,7 +33,7 @@ class TestSequences(TestCase):
         If the specification is not valid JSON, a ValueError must be raised.
         """
         if PY3:
-            error = '^Expecting value: line 1 column 1 \(char 0\)$'
+            error = '^Expecting value: line 1 column 1 \\(char 0\\)$'
         else:
             error = '^No JSON object could be decoded$'
         assertRaisesRegex(self, ValueError, error, Sequences,
@@ -45,7 +45,7 @@ class TestSequences(TestCase):
         If the JSON specification has no 'sequences' key, a ValueError
         must be raised.
         """
-        error = "^The specification JSON must have a 'sequences' key\.$"
+        error = "^The specification JSON must have a 'sequences' key\\.$"
         assertRaisesRegex(self, ValueError, error, Sequences,
                           spec='filename')
 
@@ -57,7 +57,7 @@ class TestSequences(TestCase):
         must be raised.
         """
         s = Sequences(spec='filename')
-        error = "^Name 'a' is duplicated in the JSON specification\.$"
+        error = "^Name 'a' is duplicated in the JSON specification\\.$"
         assertRaisesRegex(self, ValueError, error, list, s)
 
     def testNoSequences(self):
@@ -137,18 +137,53 @@ class TestSequences(TestCase):
                 }
             ]
         }'''))
-        error = ("^Sequence with id 'the-id' has a count of 6\. If you want "
-                 "to specify one sequence with an id, the count must be 1\. "
+        error = ("^Sequence with id 'the-id' has a count of 6\\. If you want "
+                 "to specify one sequence with an id, the count must be 1\\. "
                  "To specify multiple sequences with an id prefix, use "
-                 "'id prefix'\.$")
+                 "'id prefix'\\.$")
         assertRaisesRegex(self, ValueError, error, list, s)
 
+    def testRatchetWithNoCount(self):
+        """
+        If ratchet is specified for seqeunce spec its count must be greater
+        than one.
+        """
+        spec = StringIO('''{
+            "sequences": [
+                {
+                    "id": "xxx",
+                    "ratchet": true
+                }
+            ]
+        }''')
+        error = ("^Sequence specification 1 is specified as ratchet but its "
+                 "count is only 1\\.$")
+        assertRaisesRegex(self, ValueError, error, Sequences, spec)
+
+    def testRatchetWithNoMutationRate(self):
+        """
+        If ratchet is specified for seqeunce spec it must have a mutation
+        rate.
+        """
+        spec = StringIO('''{
+            "sequences": [
+                {
+                    "count": 4,
+                    "id": "xxx",
+                    "ratchet": true
+                }
+            ]
+        }''')
+        error = ("^Sequence specification 1 is specified as ratchet but does "
+                 "not give a mutation rate\\.$")
+        assertRaisesRegex(self, ValueError, error, Sequences, spec)
+
     def testUnknownSpecificationKey(self):
         """
         If an unknown key is given in a sequence specification, a ValueError
         must be raised.
         """
-        error = "^Sequence specification 1 contains an unknown key: dog\.$"
+        error = "^Sequence specification 1 contains an unknown key: dog\\.$"
         assertRaisesRegex(self, ValueError, error, Sequences, StringIO('''{
             "sequences": [
                 {
@@ -164,7 +199,7 @@ class TestSequences(TestCase):
         """
         for key in 'id', 'id prefix', 'name':
             error = ("^Section 1 of sequence specification 1 contains an "
-                     "unknown key: %s\.$" % key)
+                     "unknown key: %s\\.$" % key)
             assertRaisesRegex(self, ValueError, error, Sequences, StringIO('''{
                 "sequences": [
                     {
@@ -177,6 +212,34 @@ class TestSequences(TestCase):
                 ]
             }''' % key))
 
+    def testOneLetterAlphabet(self):
+        """
+        It must be possible to specify an alphabet with just one symbol.
+        """
+        s = Sequences(StringIO('''{
+            "sequences": [
+                {
+                    "alphabet": "0"
+                }
+            ]
+        }'''), defaultLength=500)
+        (read,) = list(s)
+        self.assertEqual('0' * 500, read.sequence)
+
+    def testTwoLetterAlphabet(self):
+        """
+        It must be possible to specify an alphabet with two symbols.
+        """
+        s = Sequences(StringIO('''{
+            "sequences": [
+                {
+                    "alphabet": "01"
+                }
+            ]
+        }'''), defaultLength=500)
+        (read,) = list(s)
+        self.assertTrue(x in '01' for x in read.sequence)
+
     def testOneSequenceIdOnlyDefaultLength(self):
         """
         If only one sequence is specified, and only by id, one sequence
@@ -526,7 +589,7 @@ class TestSequences(TestCase):
             ]
         }'''))
         error = ("^Sequence section refers to name 'xxx' of "
-                 "non-existent other sequence\.$")
+                 "non-existent other sequence\\.$")
         assertRaisesRegex(self, ValueError, error, list, s)
 
     def testSectionWithNameReferenceTooShort(self):
@@ -552,7 +615,7 @@ class TestSequences(TestCase):
         }'''))
         error = ("^Sequence specification refers to sequence name 'xxx', "
                  "starting at index 1 with length 10, but 'xxx' is not long "
-                 "enough to support that\.$")
+                 "enough to support that\\.$")
         assertRaisesRegex(self, ValueError, error, list, s)
 
     def testNamedRecombinant(self):
@@ -636,3 +699,42 @@ class TestSequences(TestCase):
         diffs = sum((a != b) for (a, b) in zip(sequence, read.sequence))
         self.assertEqual(len(sequence), len(read.sequence))
         self.assertEqual(diffs, len(read.sequence))
+
+    def testRatchet(self):
+        """
+        The ratchet specification must result in the expected result.
+        """
+        # Note that this is a very simple test, using a 1.0 mutation rate
+        # and a fixed alphabet.
+        length = 50
+        s = Sequences(StringIO('''{
+            "sequences": [
+                {
+                    "name": "orig",
+                    "alphabet": "01",
+                    "length": %s
+                },
+                {
+                    "count": 2,
+                    "from name": "orig",
+                    "mutation rate": 1.0,
+                    "ratchet": true
+                }
+            ]
+        }''' % length))
+        (orig, mutant1, mutant2) = list(s)
+        # The distance from the original to the first mutant must be 100 (i.e.,
+        # all bases).
+        diffCount = sum(a != b for (a, b) in
+                        zip(orig.sequence, mutant1.sequence))
+        self.assertEqual(length, diffCount)
+
+        # The distance from the first mutant to the second must be 100 (i.e.,
+        # all bases).
+        diffCount = sum(a != b for (a, b) in
+                        zip(mutant1.sequence, mutant2.sequence))
+        self.assertEqual(length, diffCount)
+
+        # The sequences of the original and the second mutant must be
+        # identical.
+        self.assertEqual(orig.sequence, mutant2.sequence)

pip install -e .
parse_log_pytest
pytest
pytest dark-matter>=1.1.28
3.6
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///opt/conda/conda-bld/attrs_1642510447205/work
backcall==0.2.0
biopython==1.79
bz2file==0.98
cachetools==4.2.4
certifi==2021.5.30
charset-normalizer==2.0.12
cycler==0.11.0
Cython==3.0.12
dark-matter==4.0.13
decorator==5.1.1
idna==3.10
importlib-metadata @ file:///tmp/build/80754af9/importlib-metadata_1631916693255/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
ipython==7.16.3
ipython-genutils==0.2.0
jedi==0.17.2
kiwisolver==1.3.1
matplotlib==3.3.4
more-itertools @ file:///tmp/build/80754af9/more-itertools_1637733554872/work
mysql-connector-python==8.0.11
numpy==1.19.5
packaging @ file:///tmp/build/80754af9/packaging_1637314298585/work
parso==0.7.1
pexpect==4.9.0
pickleshare==0.7.5
Pillow==8.4.0
pluggy @ file:///tmp/build/80754af9/pluggy_1615976315926/work
prompt-toolkit==3.0.36
protobuf==3.19.6
ptyprocess==0.7.0
py @ file:///opt/conda/conda-bld/py_1644396412707/work
Pygments==2.14.0
pyparsing @ file:///tmp/build/80754af9/pyparsing_1635766073266/work
pysam==0.23.0
pytest==6.2.4
python-dateutil==2.9.0.post0
pyzmq==25.1.2
requests==2.27.1
-e git+https://github.com/acorg/seqgen.git@2d2ddd5ea539ac58c29facbb017815aae2ff0354#egg=seqgen
simplejson==3.20.1
six==1.17.0
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
traitlets==4.3.3
typing_extensions @ file:///opt/conda/conda-bld/typing_extensions_1647553014482/work
urllib3==1.26.20
wcwidth==0.2.13
zipp @ file:///tmp/build/80754af9/zipp_1633618647012/work

name: seqgen
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=21.4.0=pyhd3eb1b0_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - importlib-metadata=4.8.1=py36h06a4308_0
  - importlib_metadata=4.8.1=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - more-itertools=8.12.0=pyhd3eb1b0_0
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=21.3=pyhd3eb1b0_0
  - pip=21.2.2=py36h06a4308_0
  - pluggy=0.13.1=py36h06a4308_0
  - py=1.11.0=pyhd3eb1b0_0
  - pyparsing=3.0.4=pyhd3eb1b0_0
  - pytest=6.2.4=py36h06a4308_2
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - toml=0.10.2=pyhd3eb1b0_0
  - typing_extensions=4.1.1=pyh06a4308_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.6.0=pyhd3eb1b0_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - backcall==0.2.0
      - biopython==1.79
      - bz2file==0.98
      - cachetools==4.2.4
      - charset-normalizer==2.0.12
      - cycler==0.11.0
      - cython==3.0.12
      - dark-matter==4.0.13
      - decorator==5.1.1
      - idna==3.10
      - ipython==7.16.3
      - ipython-genutils==0.2.0
      - jedi==0.17.2
      - kiwisolver==1.3.1
      - matplotlib==3.3.4
      - mysql-connector-python==8.0.11
      - numpy==1.19.5
      - parso==0.7.1
      - pexpect==4.9.0
      - pickleshare==0.7.5
      - pillow==8.4.0
      - prompt-toolkit==3.0.36
      - protobuf==3.19.6
      - ptyprocess==0.7.0
      - pygments==2.14.0
      - pysam==0.23.0
      - python-dateutil==2.9.0.post0
      - pyzmq==25.1.2
      - requests==2.27.1
      - simplejson==3.20.1
      - six==1.17.0
      - traitlets==4.3.3
      - urllib3==1.26.20
      - wcwidth==0.2.13
prefix: /opt/conda/envs/seqgen

test/testSequences.py::TestSequences::testOneLetterAlphabet test/testSequences.py::TestSequences::testRatchet test/testSequences.py::TestSequences::testRatchetWithNoCount test/testSequences.py::TestSequences::testRatchetWithNoMutationRate test/testSequences.py::TestSequences::testTwoLetterAlphabet
test/testSequences.py::TestSequences::testDuplicatedName test/testSequences.py::TestSequences::testNamedRecombinant test/testSequences.py::TestSequences::testNoSequences test/testSequences.py::TestSequences::testNoSequencesKey test/testSequences.py::TestSequences::testNonExistentSpecificationFile test/testSequences.py::TestSequences::testNotJSON test/testSequences.py::TestSequences::testOneSectionRandomAAs test/testSequences.py::TestSequences::testOneSectionRandomNTs test/testSequences.py::TestSequences::testOneSectionWithLength test/testSequences.py::TestSequences::testOneSectionWithSequence test/testSequences.py::TestSequences::testOneSequenceAAOnly test/testSequences.py::TestSequences::testOneSequenceByLength test/testSequences.py::TestSequences::testOneSequenceIdAndCountGreaterThanOne test/testSequences.py::TestSequences::testOneSequenceIdOnly test/testSequences.py::TestSequences::testOneSequenceIdOnlyDefaultIdPrefix test/testSequences.py::TestSequences::testOneSequenceIdOnlyDefaultLength test/testSequences.py::TestSequences::testOneSequenceIdPrefix test/testSequences.py::TestSequences::testOneSequenceLengthIsAVariable test/testSequences.py::TestSequences::testOneSequenceNameOnly test/testSequences.py::TestSequences::testOneSequenceRandomNTs test/testSequences.py::TestSequences::testOneSequenceSequenceMutated test/testSequences.py::TestSequences::testOneSequenceSequenceOnly test/testSequences.py::TestSequences::testOneSequenceWithIdAndDescription test/testSequences.py::TestSequences::testRecombinantFromFullOtherSequences test/testSequences.py::TestSequences::testSectionWithNameReference test/testSequences.py::TestSequences::testSectionWithNameReferenceTooShort test/testSequences.py::TestSequences::testSectionWithUnknownNameReference test/testSequences.py::TestSequences::testTwoSectionsWithLengths test/testSequences.py::TestSequences::testTwoSequencesButSecondOneSkipped test/testSequences.py::TestSequences::testTwoSequencesByCount test/testSequences.py::TestSequences::testTwoSequencesSecondFromName test/testSequences.py::TestSequences::testTwoSequencesSecondOneNotSkipped test/testSequences.py::TestSequences::testTwoSequencesWithDifferentIdPrefixesAndCounts test/testSequences.py::TestSequences::testUnknownSectionKey test/testSequences.py::TestSequences::testUnknownSpecificationKey
seqgen/sequences.py .gitignore seqgen/__init__.py README.md
seqgen/sequences.py .gitignore seqgen/__init__.py README.md