Project: A corrective constraint increases multi-target attainment under finite adaptive search Authors: Matthew E. Brashears, Eric Gladstone, Jose Ferrer Data: https://doi.org/10.17605/OSF.IO/K2PH7
A simulation study of error correction in evolving replicator populations. A population of textual replicators (English sentences) mutates and reproduces while evolving toward target sentences that define the peaks of a fitness landscape. A correction mechanism repairs variants toward a set of allowed forms (real dictionary words) without any information about the targets. The study asks how such correction affects adaptation.
The work has two parts:
- Single-peak (replication). Reproduces, with the current implementation, the single-peak result
from the authors' earlier unpublished Java model: correction speeds convergence to a single peak and
buffers a population against mutational degradation. Interactive HTML/JavaScript builds
are in
simulation/; the replication notebooks are inanalysis/. - Multi-peak (new). Replaces global selection with per-peak retention so a population can occupy
several peaks at once, then asks whether correction increases how many distinct targets a population
reaches exactly within a finite search horizon. It does: correction more than doubles the mean number
of targets reached within 500 generations. Extended runs show that much of this advantage reflects
faster attainment rather than an established increase in long-run coverage, and the advantage is
smaller when the targets are more widely spaced. It depends on the corrector's dictionary preserving the
words the targets require; when those words are removed, correction becomes harmful. The multi-peak
model is
analysis/bfg_stage2.pywith the harnesses and analysis scripts alongside it.
| Task | File |
|---|---|
| Interactive single-peak simulation | simulation/bfg_v6_50-50_ecm80.html (open in a browser) |
| Multi-peak model | analysis/bfg_stage2.py |
| Run multi-peak experiments | analysis/run_stage2.py (and the run_*.sh drivers) |
| Regenerate the figures | analysis/make_pub_figures.py, analysis/make_schematic.py |
| Audit the reported numbers | analysis/audit_recompute.py |
| Robustness analyses (extended horizon, replicated landscapes, colonization criterion, correction threshold) | analysis/analyze_robustness.py |
| Codebook (dictionary) ablation | analysis/analyze_dict_ablation.py |
| Fitness-gated correction | analysis/analyze_gate.py |
| Single-peak baseline figure | analysis/make_fig2_baseline.py |
| Mutation-pool sensitivity check | analysis/pool_sensitivity.py (data in data/pool_sensitivity/) |
| Session history and decisions | docs/BFG_Dev_Log.md |
| Audit report | docs/BFG_Audit_Jul9.md |
analysis/ model (bfg_stage2.py), harnesses (run_*.py, run_*.sh), analysis and figure scripts,
replication notebooks (BFG_Simulation_v6.ipynb)
simulation/ interactive HTML/JavaScript builds of the single-peak model
data/ per-experiment manifests + per-run summary CSVs (full per-generation data on OSF)
figures/ publication figures (figures/pub/*.png) and original figure panels (SVG)
docs/ development log, audit report, replication notes (Markdown)
Every run set carries a manifest.json recording the full configuration, the seed list, the peak
identities, and an MD5 hash of the model code, so any run reproduces exactly from the code and its
manifest. A seed-matched check confirmed the model is identical across code versions (see
docs/BFG_Audit_Jul9.md). Headline multi-peak estimates pool two independent draws produced under the
current code and are reported with standard errors.
To keep the repository lean, data/ here contains the manifests and per-run summary CSVs for each
experiment, which reproduce the run-level results (targets reached, colonization, final fitness) given the
code. The full per-generation output, needed for the time-course analyses (for example the extended
horizon and best-fitness trajectories), is deposited on the Open Science Framework at
https://doi.org/10.17605/OSF.IO/K2PH7 in two archives:
bfg_full_data.zip(July 2026): the core multi-peak runs, distance ladder, niche capacity, disruption, and the exploratory Stage 3/4 runs. ContainsDATA_DICTIONARY.md.bfg_additional_data_2026-10.zip(October 2026): the single-peak baseline (stage1_baseline), extended horizon (asymptotic), replicated landscapes (multiland), codebook ablation (dict_ablation,dict_ablation_relaxed), fitness-gated correction (gate_*,nogate_check2), relaxed colonization (colon_*), and correction threshold (simthresh_*). Contains a README mapping each folder to its figure and script, and MD5 checksums.
Unzip both into the repository root so that the per-generation files sit alongside the summaries in
data/<experiment>/.
The simulation follows a five-component loop each generation:
- Replicator — each survivor produces N offspring (exact copies before mutation).
- Mutator — applies stochastic character-level variation to each offspring.
- Corrector — optionally repairs non-words via dictionary lookup (correction condition only).
- Evaluator — scores each offspring against all fitness targets.
- Selector — retains offspring for the next generation (see Selection below).
| Parameter | Value |
|---|---|
| Fitness weighting | 50/50 word / character |
| Correction similarity threshold | 0.80 |
| Mutation rate multipliers | 0.25, 0.5, 1.0, 1.5 |
| Children per parent | 10 |
| Max generations | 500 (1,500 for the extended-horizon analysis; longer windows for the disruption experiment) |
| Exit (exact-match) fitness | 0.99999 |
| Seeds | 16 |
| Targets | 100 sentences |
| Dictionary | 110,879 words (all target words already present; none added) |
Five operators, each scaled by the mutation rate multiplier:
| Operator | Base probability |
|---|---|
| Character substitution | 0.02 |
| Character deletion | 0.004 |
| Character insertion | 0.004 |
| Space deletion | 0.02 |
| Space insertion | 0.004 |
Applied per character position in a fixed order. Valid characters: a-z, hyphen, apostrophe.
Applied word by word after mutation. A word found in the dictionary is retained. A word not found is replaced by a randomly selected candidate meeting a minimum combined trigram and Levenshtein similarity threshold of 0.80, or left unchanged if no candidate qualifies. Correction has no knowledge of the fitness targets. Implemented with a custom trigram index and Levenshtein distance.
Each replicator is scored against all targets and keeps its highest score. For a replicator S and target T:
- Character similarity:
(len_max - levenshtein(S, T)) / len_max - Word similarity:
words_shared / words_max(set-based, unique words only) - Combined:
(word + character) / 2(equal weighting)
In the multi-peak model, a replicator's fitness is its similarity to its single nearest peak.
- Single-peak (baseline): global top-N truncation. All offspring are ranked by fitness and the top N are retained. Because retention rewards only the fittest across all targets, the population collapses onto the easiest peak.
- Multi-peak (new): each offspring is assigned to its nearest peak. Each peak has a fixed number of genotype slots filled by the fittest replicators nearest it; the remainder wait in a capacity-capped valley remnant that is pruned by fitness or at random. A peak is colonized when one of its slot occupants reaches an exact match. This lets a population hold several peaks at once.
The original Java implementation is preserved here in original_java/, and is maintained at its
canonical repository, https://github.com/lardener/BFG, whose commit history documents how the model
developed. The table below lists where this implementation deviates from it.
| # | Item | Java | This implementation | Reason |
|---|---|---|---|---|
| 1 | Word scoring | Count-based (multiset) | Set-based | Paper methods text specifies a set of words |
| 2 | Convergence criterion | Fitness equality | String equality | Matches the Java BFG.java source and is more meaningful |
| 3 | Max generations | 1000 (dev config) | 500 | Manuscript specification |
| 4 | Exit fitness threshold | 0.99 | 0.99999 | Matches the Java source, more precise |
| 5 | Levenshtein library | Apache Lucene | rapidfuzz / pure Python | Verified identical results |
| 6 | Word correction | Apache Lucene FuzzyQuery | Custom trigram index | Equivalent algorithm, independently verified |
| 7 | Language | Java | JavaScript and Python | Portability |
| 8 | Mutation character pool | 54 characters (A–Z, a–z, -, '), so each letter is ~2× as likely as either punctuation mark |
28 characters (a–z, -, '), drawn uniformly |
Text is lowercased for scoring and correction, so case is immaterial; only the relative rate of punctuation substitution/insertion differs, not fitness or correction outcomes |
| 9 | Dictionary target words | Scrubbed target words added to the dictionary at load time | General 110,879-word list used as-is; no words added | All 633 target words are already in the general list (verified 0 missing), so the effective codebook is identical for the target set studied |
110,879 words: a general English dictionary. All 633 distinct words in the 100 target sentences are already present in it, so no words were added (the original Java implementation added the target words at load time; for this target set the resulting codebook is identical). In the interactive builds it is stored as a gzip-compressed, base64-encoded string with a trigram index built at load time.
Citation metadata is in CITATION.cff (GitHub's "Cite this repository"). Code and data are released
under CC0 1.0 (see LICENSE). The data deposit is https://doi.org/10.17605/OSF.IO/K2PH7.
Export refreshed October 5, 2026.