-
Notifications
You must be signed in to change notification settings - Fork 0
Strict Clock Simulation Engine
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:
- Load and validate the simulation configuration
- Construct a rooted ultrametric phylogenetic tree
- Simulate DNA sequence evolution along every branch
- 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.
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 |
{
"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"
}
}- 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
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.
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).
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.
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:
- Calculate the probability of substitution from the branch length and mutation rate
- Decide whether a mutation occurs
- If a mutation occurs, replace the nucleotide with one of the remaining three bases
- Record the mutation event
This process is repeated recursively until every terminal taxon possesses its own simulated DNA sequence.
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.
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.
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.
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.
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.
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.
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.
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"