hub:
  repo_type: generator
  builtin: stringmol
  tags:
  - banzhaf-yamamoto
chemistries:
- id: stringmol
  name: Stringmol
  intuition: 'An automata chemistry built to be run for a long time. Strings over a small alphabet bind
    by complementary template matching, and the bound pair then executes: one acts as enzyme, copying,
    cleaving or modifying the other, before dissociating. A seed replicase copies itself, and single-point
    mutations in a search template produce genuinely new behaviours rather than broken molecules - which
    is why it has been used to study open-ended evolution and parasite dynamics.

    '
  aliases:
  - molecular microprograms
  - Stringmol automata chemistry
  origin: Hickinbotham, Clark, Stepney et al. (University of York), 2009-2011
  book: 11.1.2
  refs:
  - '[379]'
  - '[380]'
  - '[381]'
  - '[783]'
  - doi:10.1007/978-3-642-21283-3_37
  family: bio-inspired
  kind: generator
  constructive: true
  S:
    repr: 'Strings over 33 symbols: 26 template codes A..Z and 7 function codes $ > ^ ? = % }. Species
      id = the sequence with the function codes written as lowercase letters ($ s, > m, ^ t, ? i, = c,
      % x, } e); structure = the sequence. While bound, the active molecule also holds four pointers (instruction,
      flow, read, write), each with a toggle saying whether it points into its own string or the partner''s.

      '
    definition: implicit
  R:
    definition: implicit
    arity: 2
    scheme: enzyme + substrate -> enzyme' + substrate' + cleaved products (one bind-execute-dissociate
      event)
    notes: 'Binding: Smith-Waterman alignment of the complement of the initiating molecule (template codes
      shifted by 13, function codes self-complementary) against the partner, with a substitution matrix
      over a cyclic symbol loop. P(bind) = min(score, l - 1.124)/(l - 1.124) for aligned length l > 2.
      The molecule whose aligned segment starts further from the start of its string becomes active; all
      its pointers start at the bind sites. Opcodes: $ search (move F after the best complementary match
      of the following template, with probability from the match), > move (I, or R/W with modifier B/C,
      to F), ^ toggle (move F, or I/R/W with modifier A/B/C, to the other string), ? if (skip one instruction
      if R is past the end, or with probability (score/len)^len if the template matches at R), = copy
      (write the symbol at R to W, with mutation), % cleave (cut the string at F; the tail becomes a new
      molecule), } end (dissociate). Every bind and instruction costs one energy unit.

      '
  A:
    reactor:
    - well-stirred-multiset
    dilution: constant per-step decay of every molecule; population held by the balance of energy influx
      and decay (about 350 molecules in ALife XII)
    notes: 'Container (method container): at every time step all molecules are visited in random order;
      each may decay, or (if energy is left) try to bind a random unbound molecule with propensity 1-(1-a/c)^n,
      or execute one instruction of its complex. energy_per_step units are added after each step. Methods
      soup and closure are Chemart additions.

      '
  params:
  - name: method
    type: enum
    default: container
    choices:
    - container
    - soup
    - closure
    role: structural
    meaning: 'container: the authors'' time-stepped container with energy and decay, observed bind-execute-dissociate
      events with counts; soup: chemart.soup.soup with instantaneous pair reactions (bind test, program
      run to the end) and constant population; closure: all reactions reachable from the distinct seed
      molecules, with exact copying (Chemart additions)'
  - name: molecules
    type: dict
    default:
      OOGEOLHHHRLUEUOBBBRBXUUUDYGRHBLROORE$BLUBO^B>C$=?>$$BLUBO%}OYHOB: 100
    range: seed replicase of spec v0.2 app. B.1 (default); upstream configs use WWGEWLHHHRLUEUWJJJRJXUUUDYGRHJLRWWRE$BLUBO^B>C$=?>$$BLUBO%}OYHOB
      x 150; ALife XII species 9 is OBEQBXUUUDYGRHBBOSEOLHHHRLUEUOBLROORE$BLUBO^B>C$=?>$$BLUBO%}OYHOB
    role: population
    meaning: 'initial multiset {sequence: count} (closure: the distinct sequences form the seed set)'
  - name: steps
    type: int
    default: 3000
    min: 0
    max: 100000000
    range: 'ALife XII: until extinction, modal 750000 and up to about 15e6 time steps'
    role: population
    meaning: 'container: time steps; soup: collisions (elastic ones included); ignored by closure'
  - name: energy_per_step
    type: int
    default: 25
    min: 0
    range: 'ALife XII paper: 25; upstream default ESTEP 20; later spatial configs 2500'
    role: kinetic
    meaning: energy units added to the container after every time step (container only); binding and each
      instruction cost one unit
  - name: cell_radius
    type: float
    default: 2500.0
    min: 0.001
    range: upstream CELLRAD 2500; spatial configs 1
    role: spatial
    meaning: container radius; bind propensity 1-(1-(agent_radius/cell_radius)^2)^n with n unbound molecules
      not yet visited in the step (container only)
  - name: agent_radius
    type: float
    default: 10.0
    min: 0.0
    range: upstream AGRAD 10
    role: spatial
    meaning: molecule radius in the bind propensity (container only); must not exceed cell_radius
  - name: decay
    type: float
    default: 0.00023668639
    min: 0.0
    max: 1.0
    range: 'spec v0.2 and ALife XII: 1/65^2; upstream configs 0.0005-0.0015'
    role: kinetic
    meaning: probability per time step that a visited molecule is deleted; a decaying bound molecule takes
      its partner with it (container only)
  - name: substitution_rate
    type: float
    default: 1.0e-05
    min: 0.0
    max: 1.0
    range: 'ALife XII: 1e-5; upstream configs (MUTATE) 1e-4-2e-3, with indel_rate equal to it'
    role: stochastic
    meaning: per-copy probability that '=' writes a neighbour of the read symbol in the symbol loop instead
      of the symbol
  - name: indel_rate
    type: float
    default: 3.06125e-08
    min: 0.0
    max: 1.0
    range: 'upstream ALife XII default 3.06125e-8 (paper: p_s/(10 n) = 3.03e-8)'
    role: stochastic
    meaning: per-copy probability of an insertion (the copy plus a random symbol) or a deletion (the symbol
      is skipped), 50:50
  - name: max_length
    type: int
    default: 2000
    min: 2
    max: 100000
    range: 'ALife XII and upstream MAXLEN: 2000; ECAL 2009: 512'
    role: structural
    meaning: 'maximum string length: a copy with R or W at this index ends the reaction'
  - name: traceback
    type: enum
    default: matrix
    choices:
    - matrix
    - highest-neighbour
    role: structural
    meaning: 'Smith-Waterman traceback: matrix is the proper trace matrix (spec v0.2, SmithWatermanV2);
      highest-neighbour walks back through the largest neighbouring cell (the v0.1 implementation, spec
      app. B.1)'
  - name: max_exec_steps
    type: int
    default: 20000
    min: 1
    role: structural
    meaning: 'soup and closure only: instructions after which an isolated reaction that has not ended
      counts as non-terminating (no reaction); the seed replicase needs a few hundred'
  - name: max_species
    type: int
    default: 100
    min: 1
    role: structural
    meaning: 'closure only: species budget (status truncated when exceeded)'
  provides:
  - topology
  - stoichiometry
  - catalysts
  - flow
  - initial-state
  - sequence-structure-function
  fidelity: reconstructed
  sources:
  - 'Hickinbotham, S., Clark, E., Stepney, S., Clarke, T., Nellis, A., Pay, M., Young, P. (2010). Specification
    of the Stringmol chemical programming language version 0.2. Technical Report YCS-2010-458, University
    of York: alphabet and complements (table 2), substitution matrix (eqs. 7-9), Smith-Waterman (eq. 10,
    app. B), bind probability (eq. 13), bind handshake (eq. 18), pointers and opcodes (secs. 7-10), mutation
    (eq. 29), decay (sec. 4, app. C), seed replicase (app. B.1). Shipped as smtr0.2.pdf in https://github.com/franticspider/stringmol'
  - 'Hickinbotham, S. et al. (2010). Diversity from a monoculture: effects of mutation-on-copy in a string-based
    artificial chemistry. ALife XII, MIT Press, 24-31: 25 energy units per step, about 350 molecules,
    p_s = 1e-5, p_i = p_s/(10n), seed replicase 65 symbols and 240 time steps per copy, species 9/29/30/31
    and fig. 7 (origin of species 31). https://www-users.york.ac.uk/~ss44/bib/ss/nonstd/alife12_plazzmid.pdf'
  - 'Hickinbotham, S. et al. (2011). Molecular microprograms. ECAL 2009, LNCS 5777, 291-298: replicase,
    bind probability (s/l)^l of v0.1, length-dependent decay 1/L^2, invasion when rare. https://www-users.york.ac.uk/~ss44/bib/ss/nonstd/ecal09c.pdf'
  - 'Hickinbotham, S. stringmol, C++ source (GPL-3), commit 52b5625 (2021): stringPM.cpp (make_next, testbind,
    get_bprob, set_exec, exec_step, hcopy, cleave, rewind_bad_ptrs, testdecay), instructions.cpp (HSearch,
    IfLabel), alignment.cpp (SmithWatermanV2, SmithWaterman, default_table, sym_from_adj, align_event),
    stringmanip.cpp (AlphaComp), agents_base.cpp (eqn_prop), stringmol.cpp (SmPm_AlifeXII main loop).
    https://github.com/franticspider/stringmol'
  - Smith, T. F. & Waterman, M. S. (1981). Identification of common molecular subsequences. J. Mol. Biol.
    147:195-197 (book [783]).
  decisions:
  - 'The machine is a line-by-line port of the upstream C++ source, including its single-precision float
    arithmetic and the order in which random numbers are drawn. Compiled upstream with rand0to1 replaced
    by a shared LCG, it and the port agree on the substitution table, on 1503 alignments (score, start
    and end of both tracebacks, bind probability) and step by step (every molecule, pointer, toggle and
    the energy after every time step) on 7 container runs: ALife XII parameters (150 seed replicases,
    3000 steps), dense binding with high mutation rates, random molecules with many function codes (3
    seeds), max_length 130, and species 9 with mutant 29. Upstream crashes for max_length < 128 (HSearch
    clears 128 bytes of a max_length buffer); the port has no such limit.'
  - 'Where the spec (and the book figure copied from it) and the source disagree, the source wins, since
    it produced the published runs: (1) symbol order of the substitution matrix and mutation loop is ABC$DEF%GH^IJK?LMN}OPQ>RST=UVWXYZ
    (default_table, ALXII.mtx), not the spec''s ABC$DEF>GHIJ^KLM=NOP?QRS}TUV%WXYZ; complements are the
    same in both; (2) mismatch scores are -d*33/272 printed to 3 decimals (eq. 8 as written); spec table
    3 (-0.12, -0.25, ...) comes from an integer division in table_from_string, which the source does not
    use; (3) P(bind) is 0 for aligned length l <= 2 (spec: l <= 3) and uses m = 1.124 in eq. 13; (4) the
    bind propensity uses radii, (agent_radius/cell_radius)^2 = 1.6e-5 with CELLRAD 2500 and AGRAD 10,
    where spec eq. 1 uses the areas 10/2500, and n counts the unbound molecules not yet visited in the
    current step.'
  - 'Spec erratum: eq. 18 makes M_A active when its bind site starts nearer the string start, contradicting
    the prose (the executing string is the one whose bind site is furthest from its start); the source
    (set_exec) and the ALife XII paper follow the prose, and on ties the molecule that initiated the bind
    is active.'
  - 'Opcode details from the source that the spec states differently or not at all: ''$'' succeeds with
    probability min(score, l-1.124)/(l-1.124) with no cutoff for short alignments (the cutoff line is
    overwritten), puts F just after the match on the searched string, and on failure puts F on the last
    template symbol of the label on I''s string (spec: the next instruction); a ''$'' with no template
    sets F to I; ''>'' moves I to F + 1 (spec: to F, or +1 if already there); ''^'' increments I after
    toggling, so ''^A'' increments the pointer on the other string; ''?'' with no or a one-symbol template
    skips one instruction if R is past the end, otherwise it skips with probability (score/len)^len of
    the template against the symbols at R (align_event), not eq. 13.'
  - 'Copy: ''='' overwrites the symbol at W (the ECAL paper says it inserts; W is normally at the end
    of the string, so the string grows). A read past the end writes nothing but I still advances. R or
    W at max_length ends the reaction. Mutation (hcopy): with probability indel_rate an indel, then 50:50
    an insertion that writes the copy and then a random symbol (two symbols) or a deletion that skips
    the read symbol and also advances I once more; otherwise with probability substitution_rate a random
    neighbour of the read symbol in the symbol loop. indel_rate defaults to the source''s ALife XII value
    3.06125e-8, the paper''s formula p_s/(10*33) gives 3.03e-8.'
  - 'Cleave splits the string that F is on (spec: ''the'' string); if F is inside it, the tail becomes
    a new molecule, pointers past the new end move to the end, and if either string is left empty that
    molecule is destroyed and the reaction ends (cleave-zero). Decay: every visited molecule decays with
    constant probability per step whether bound or not (spec v0.2 DC+ALL); only the visited member of
    a pair is tested, and its partner is deleted with it.'
  - 'Energy: nothing but decay happens while energy <= 0; each successful bind and each instruction (including
    ''}'') costs one unit; the container starts with 0 energy and energy_per_step is added after each
    step (SmPm_AlifeXII). The ALife XII paper says 25 units per step; the source''s default ESTEP is 20;
    25 is the default.'
  - 'Seed replicase: the ALife XII figure''s 65-symbol seed is only an image. The default is the 64-symbol
    seed that spec app. B.1 gives for the matrix traceback (OOGEOL...). ALife XII species 9, printed in
    the paper''s fig. 7, is kept as a constant for the tests. The v1 catalog parameters complement_offset
    (fixed at 13 in AlphaComp), bind_probability (it follows from the alignment) and capacity (the population
    follows from energy and decay) are dropped; seed_replicase becomes molecules.'
  - 'Traceback option: spec app. B.1 says v0.1 walked back through the highest neighbouring cell and v0.2
    uses the trace matrix; both are in the source and both are offered. The ALife XII fig. 7 reactions
    (29 + 9 -> 30 + 9 and 30 + 9 -> 30 + 9 + 31) come out the same with both.'
  - 'Observed network (container): one reaction per complete event, reactants = the two sequences at binding,
    products = the two sequences at dissociation plus every molecule cleaved off during the event; reactions
    are deduplicated as multisets, with firing counts; extras.active_counts gives, per reaction, how often
    each reactant species was the active one. Events cut short by decay are not reactions: extras.aborted
    lists them (reactants, molecules already released, count); extras.decayed counts deletions of unbound
    molecules; extras.in_progress lists complexes still bound at the end; extras.final_state counts the
    unbound molecules. Then initial_state + sum(count * (products - reactants)) + sum over aborted and
    in_progress of (released - reactants) - decayed = final_state exactly. outflow is the per-step decay
    probability (a first-order removal of every species). extras.analysis samples population, distinct
    species and energy (about 500 samples); extras.epochs lists when the most abundant species changes
    (sweeps).'
  - 'Rates: none. Binding probability depends on the alignment and reaction duration on the program and
    energy, so no mass-action constant is published or implied.'
  - 'Soup and closure are Chemart additions for sampling the reaction space without the container: an
    isolated reaction runs the pair''s program to the end with unlimited energy and no decay; the first
    molecule initiates the bind (soup draws pairs in random order). Soup tests the bind with its probability
    and mutates on copy; closure takes every bind with P > 0 and copies exactly, but still draws ''$''
    and ''?'' outcomes at random once per ordered pair. A reaction still running after max_exec_steps
    instructions counts as non-terminating (no reaction; counted in extras.non_terminating).'
  - 'Undefined behaviour in the source, fixed choices: an alignment against an empty string (the C result
    is uninitialised) scores 0; reads beyond the string buffer return the terminator.'
  - 'The v1 reactor lattice-2d is removed: the book calls the topology dispensable, [379]-[381] use the
    aspatial container, and the later spatial Stringmol (smspatial.cpp, GRIDX/GRIDY) is not implemented.'
  reference_impl: 'stringmol (Hickinbotham, C++, GPL-3): https://github.com/franticspider/stringmol; refactor
    at https://github.com/uoy-research/stringmol'
  phenomena:
  - 'the seed replicase copies itself: R + R -> 3 R, with the active molecule copying the passive one
    (spec app. B.1; ALife XII: 240 time steps per copy)'
  - a single-point mutation in a search template ($BLUBO -> $BLUBP) makes a molecule that pastes a copy
    of its partner over its own end, giving a nearly double-length molecule (ALife XII fig. 7, species
    29 + 9 -> 30; spec app. B.1)
  - 'the double-length molecule makes truncated copies because the bind site shifts: 30 + 9 -> 30 + 9
    + 31, where 31 lacks the first 14 symbols (ALife XII fig. 7)'
  - 'binding is complementary, so a replicase binds copies of itself only through two different sites;
    mutants with better-matching sites bind more strongly (ECAL 2009 invasion when rare: 88 of 100 trials)'
  - 'extinction: every one of 1000 ALife XII trials ends when a parasite that is copied but cannot copy
    spreads and the replicases decay (modal extinction time 750000 steps)'
  - characteristic sweeps (under 50000 steps), slow sweeps, neutral drift, sub-populations of non-lethal
    parasites, rapid sweep sequences
  - 'emergent, spontaneous and multispecies hypercycles when a dominant species loses self-replication
    and depends on a partner (ALife XII: hypercycles in 30 of 1000 trials)'
  notes: 'Paper-scale runs (about 350 molecules for 10^5-10^7 time steps) are far beyond pure Python;
    the defaults run a few thousand steps with 100 seed replicases, where mutations are rare. Raise substitution_rate
    (the upstream configs use 1e-4 to 2e-3) to see new species within seconds.

    '
