-
Notifications
You must be signed in to change notification settings - Fork 0
Relaxed Molecular Clock Engine
The Relaxed Molecular Clock Engine generates synthetic molecular clock datasets in which evolutionary rates are allowed to vary between lineages.
Like the Strict Clock Engine, it produces complete simulated datasets from a JSON configuration file, including DNA sequences, a rooted phylogenetic tree and detailed simulation metadata. Unlike the strict clock model, however, each lineage evolves at its own mutation rate.
The engine is intended as an educational implementation of a simple autocorrelated relaxed molecular clock. It deliberately favours clarity over biological complexity, providing datasets whose complete evolutionary history is known while illustrating the consequences of lineage-specific rate variation.
The Relaxed Clock Engine is implemented independently of the Strict Clock Engine. Although both produce equivalent outputs, they use separate configuration formats and independent implementations to preserve the conceptual distinction between strict and relaxed molecular clock models.
The simulation is controlled by a JSON configuration file organised into logical sections.
| Section | Purpose |
|---|---|
| simulation | General simulation settings, including the simulation name and random seed |
| sequence | Defines the ancestral sequence and sequence alphabet |
| tree | Controls the structure of the phylogenetic tree and branch durations |
| clock | Defines the relaxed molecular clock model and rate variation |
| mutation | Configures the nucleotide substitution model |
| outputs | Controls the files written by the simulator |
Example Configuration
{
"simulation": {
"name": "relaxed-clock-example",
"random_seed": 42
},
"sequence": {
"length": 500,
"alphabet": ["A", "C", "G", "T"],
"root_sequence": null
},
"tree": {
"max_depth": 4,
"branching_mode": "binary",
"branch_duration": 1.0,
"duration_jitter": 0.0
},
"clock": {
"model": "autocorrelated_relaxed",
"root_rate": 0.01,
"rate_distribution": "lognormal",
"rate_sigma": 0.25,
"minimum_rate": 0.001,
"maximum_rate": 0.05
},
"mutation": {
"model": "simple_substitution",
"allow_back_mutation": true
},
"outputs": {
"write_fasta": true,
"write_newick": true,
"write_metadata": true,
"newick_branch_lengths": "genetic_change"
}
}Unlike the Strict Clock Engine, the configuration separates the simulation into logical sections and explicitly models lineage-specific evolutionary rates.
As with the Strict Clock Engine, every simulation begins with a randomly generated ancestral DNA sequence unless one is supplied explicitly in the configuration.
The root sequence is inherited by every descendant lineage, with mutations accumulating independently along each branch.
The first release generates a full binary phylogenetic tree of configurable depth.
Each branch is assigned a duration representing evolutionary time.
Optional branch-duration jitter may be introduced, although the default configuration maintains an ultrametric time tree in which every terminal taxon lies the same distance from the root.
This is the key feature of the Relaxed Clock Engine. Rather than assigning every lineage the same mutation rate, each branch receives its own evolutionary rate.
The first implementation uses an auto-correlated relaxed clock.
The root lineage begins with the configured root rate.
At every branching event, each daughter lineage inherits its parent’s rate and modifies it using a random multiplier drawn from a lognormal distribution.
The resulting rate is constrained to remain between configurable minimum and maximum values. This produces realistic lineage-specific rate variation while maintaining continuity between parent and descendant branches.
DNA sequence evolution proceeds from the root towards the terminal taxa.
For every branch:
- Copy the parent sequence
- Calculate the branch’s expected genetic change using its duration and lineage-specific mutation rate
- Simulate nucleotide substitutions
- Record every mutation event
- Pass the resulting sequence to the child node
Unlike the strict clock model, the expected number of substitutions differs between branches because each lineage evolves at its own rate.
The current implementation supports a single clock model:
| Model | Description |
|---|---|
| autocorrelated_relaxed | Daughter lineages inherit their parent’s mutation rate with stochastic variation drawn from a lognormal distribution |
The engine deliberately focuses on a single educational model rather than supporting multiple relaxed clock formulations.
Future releases may introduce local clocks or uncorrelated relaxed clocks.
The Relaxed Clock Engine currently implements the same simple nucleotide substitution model as the Strict Clock Engine. For each simulated substitution:
- One nucleotide position is selected.
- The ancestral nucleotide is replaced by another nucleotide from the configured alphabet.
- The mutation is recorded.
The simulator currently models substitutions only. Insertions, deletions and more complex substitution models are intentionally excluded to keep the simulation easy to understand.
Every substitution event is recorded individually. Each mutation stores:
- Sequence position
- Ancestral nucleotide
- Derived nucleotide
These records provide the complete mutational history of every branch.
The tree is represented internally using RelaxedTreeNode objects. Each node stores:
- Unique identifier
- Optional taxon name
- Parent identifier
- Child nodes
- Branch duration
- Lineage mutation rate
- Expected substitutions
- Observed substitutions
- Accumulated genetic change
- DNA sequence
- Mutations inherited from its parent
Unlike the Strict Clock Engine, every branch therefore retains both its elapsed evolutionary time and its lineage-specific mutation rate.
One of the central concepts illustrated by the Relaxed Clock Engine is the distinction between evolutionary time and accumulated genetic change.
Every branch has both:
- Branch duration — how much evolutionary time has elapsed.
- Lineage rate — how rapidly mutations accumulate.
- Genetic change — the resulting amount of molecular evolution.
Two branches of identical duration may therefore accumulate different numbers of substitutions because they evolve at different rates.
This distinction forms the basis of relaxed molecular clock models.
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 metadata includes:
- Original configuration
- Random seed
- Ancestral sequence
- Rooted tree
- Lineage mutation rates
- Branch durations
- Expected substitutions
- Observed substitutions
- Mutation history
- Newick representation
- Summary statistics
The simulator uses a local pseudo-random number generator.
Providing the same configuration file and random seed will reproduce the same phylogenetic tree, lineage rates and DNA sequences.
The Relaxed Molecular Clock Engine extends the concepts introduced by the Strict Clock Engine without replacing it.
Both engines produce comparable outputs while modelling different biological assumptions:
- The Strict Clock Engine demonstrates evolution under a constant mutation rate
- The Relaxed Molecular Clock Engine demonstrates how lineage-specific rates influence accumulated genetic change while preserving a complete and reproducible evolutionary history
By implementing the two models independently, the project provides a clear comparison between strict and relaxed molecular clock assumptions without complicating either implementation.
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 relaxedclock --config "/path/to/config/file.json"