Skip to content

Strict Clock Simulation Engine

Dave Walker edited this page Jul 6, 2026 · 4 revisions

Overview

The Simulation Engine is responsible for generating complete synthetic molecular clock datasets from a JSON configuration file.

Rather than attempting to model every aspect of biological evolution, the strict clock engine provides a deliberately simple implementation of sequence evolution under a strict molecular clock. The emphasis is on producing datasets with a completely known evolutionary history that can be used to validate molecular clock algorithms.

A simulation consists of four major stages:

  1. Load and validate the simulation configuration
  2. Construct a rooted ultrametric phylogenetic tree
  3. Simulate DNA sequence evolution along every branch
  4. Export the generated dataset

The simulator retains the complete evolutionary history, allowing both the final sequences and the “ground truth” tree and mutation history to be examined.

Simulation Workflow

Configuration

The simulation is controlled by a JSON configuration file. This file defines the size of the simulated dataset, the structure of the phylogenetic tree, and the evolutionary model used during sequence generation.

Parameter Type Description
sequence_length Integer The length of the ancestral DNA sequence, in nucleotides. Every simulated sequence generated during the simulation has this length
number_of_taxa Integer The number of terminal taxa (leaf nodes) to generate. These represent the simulated extant species and determine the size of the resulting phylogenetic tree
random_seed Integer Seed used to initialise the random number generator. Using the same seed and configuration will always reproduce the same simulated dataset
tree.topology String Specifies how the tree topology is generated. Current supported values are balanced and random
tree.root_age Float The age assigned to the root of the tree. During calibration this determines the total evolutionary time from the common ancestor to each terminal taxon
clock.model String Specifies the molecular clock model used during simulation. The strict clock engine supports only strict, meaning every lineage evolves at the same mutation rate
clock.mutation_rate Float The expected mutation rate per unit of evolutionary time. Higher values produce more substitutions along each branch
substitution.model String Defines the nucleotide substitution model used during sequence evolution. The strict clock engine supports the simple model, in which all nucleotide substitutions are equally likely

Example Configuration

{
  "sequence_length": 1000,
  "number_of_taxa": 8,
  "random_seed": 12345,

  "tree": {
    "topology": "balanced",
    "root_age": 1.0
  },

  "clock": {
    "model": "strict",
    "mutation_rate": 0.01
  },

  "substitution": {
    "model": "simple"
  }
}

Notes

  • Larger values of sequence_length produce more informative datasets but increase simulation time
  • Increasing number_of_taxa results in larger phylogenetic trees and more terminal sequences
  • Changing the random_seed produces a different evolutionary history while preserving the same simulation parameters

Generating the Ancestral Sequence

Every simulation begins with a randomly generated DNA sequence with each nucleotide is selected independently from:

  • A
  • C
  • G
  • T

The sequence length is determined by the configuration file. ßThis sequence represents the common ancestor of every simulated taxon.

Constructing the Phylogenetic Tree

The simulator next constructs a rooted binary tree. Two topology strategies are currently supported:

Strategy Description
Balanced The taxa are recursively divided into equally sized groups, producing a balanced binary tree
Random The taxa are recursively divided at random split points

A balanced tree provides predictable tree structure and is useful when validating algorithms while a random tree has varying branch arrangements while remaining fully bifurcating (each node having exactly two branches connecting it to its descendants).

Building an Ultrametric Tree

After the topology has been created, internal node ages are assigned so that every terminal taxon lies the same evolutionary distance from the root.

This produces an ultrametric tree, in which every root-to-tip path has the same total length.

Branch lengths are calculated directly from the ages of parent and child nodes.

Simulating Sequence Evolution

Once the tree has been constructed, DNA sequences are evolved from the root towards the terminal taxa.

Each child node receives a copy of its parent’s DNA sequence. The simulator then examines every nucleotide independently.

For each nucleotide:

  1. Calculate the probability of substitution from the branch length and mutation rate
  2. Decide whether a mutation occurs
  3. If a mutation occurs, replace the nucleotide with one of the remaining three bases
  4. Record the mutation event

This process is repeated recursively until every terminal taxon possesses its own simulated DNA sequence.

Mutation Model

The strick clock engine implements a simple nucleotide substitution model.

The probability that a nucleotide changes along a branch is calculated from:

  • Mutation rate
  • Branch length

using an exponential waiting-time model.

Only substitutions are currently supported.

Insertions, deletions and more sophisticated substitution models are intentionally excluded from the strict clock engine in order to keep the simulation straightforward and easy to understand.

Mutation Logging

Every substitution is recorded as an individual mutation event.

Each event stores:

  • Sequence position
  • Ancestral nucleotide
  • Derived nucleotide

These mutation records provide a complete history of sequence evolution along every branch.

Because the simulator records the true mutation history, later molecular clock algorithms can be validated against the known answer rather than inferred behaviour.

Tree Representation

The tree is represented internally using TreeNode objects.

Each node stores:

  • Unique identifier
  • Optional taxon name
  • Parent identifier
  • Child nodes
  • Node age
  • Branch length
  • DNA sequence
  • Mutations inherited from its parent

Internal nodes therefore retain their reconstructed ancestral sequences rather than only the terminal taxa.

Simulation Results

A completed simulation produces a SimulationResult object containing:

  • Validated configuration
  • Ancestral sequence
  • Rooted phylogenetic tree
  • Terminal DNA sequences
  • Newick tree representation

Convenience methods provide access to terminal sequences and metadata for export.

Output Files

Each simulation writes three output files:

Format Description
FASTA Contains the simulated DNA sequences for every terminal taxon
Newick Contains the true phylogenetic tree
JSON Metadata A JSON document containing the complete simulation record

The sequences in the FASTA file represent the observable data available to downstream phylogenetic algorithms.

The Newick files can be imported directly into phylogenetic software for comparison with inferred trees.

The exported metadata includes:

  • Original configuration
  • Random seed
  • Ancestral sequence
  • Newick tree
  • Every internal node
  • Every terminal node
  • Every DNA sequence
  • Every mutation event

The metadata represents the complete “ground truth” of the simulation.

Reproducibility

Every simulation uses its own local random number generator.

Providing the same configuration file and random seed will therefore generate exactly the same dataset.

This reproducibility is essential for testing, debugging and comparing molecular clock algorithms.

Design Philosophy

The simulator deliberately favours clarity over biological complexity.

Its purpose is to produce datasets whose evolutionary history is completely known and reproducible.

Future versions will extend the biological realism by introducing relaxed molecular clocks, alternative substitution models and multiple genes, while preserving the overall architecture established by the strick clock engine.

Running the Engine

To run the engine, first create a virtual environment. From the root of the project:

python -m venv venv
. venv/bin/activate
pip install --upgrade pip
pip install -e .

These commands are correct for MacOS/Linux but may need modification for Windows.

Once the environment has been created and activated, run the engine as follows:

python -m strictclock --config "/path/to/config/file.json"