Evolving Digital Life: Designing a Modular Evolution Simulator

Evolutionary Simulation with LLM-Assisted Phenotype Analysis

Evolutionary Simulation with LLM-Assisted Phenotype Analysis

This project is a modular digital evolution framework that borrows structure and concepts from genomics, evolutionary dynamics, and modern AI systems engineering. It is implemented as a Python package and lives at:

https://github.com/vaddhiparthy/Evolutionary-simulation-using-LLM

At its core, the system models a population of synthetic organisms, each with an explicit genotype (a high-dimensional DNA matrix), a derived phenotype (continuous trait vector), and a dynamic interaction with a stochastic environment. On top of that, it integrates a language model layer that translates abstract trait vectors into human-readable descriptions, acting as an AI-assisted “phenotype interpreter” for emergent clusters of organisms.


1. Design Goals and Perspective

The project is designed like a production-quality AI data platform, not a one-off script. The primary goals are:

  • Explicit genotype–phenotype separation
    The framework distinguishes clearly between encoded genetic information (DNA matrix) and emergent traits (phenotypic vectors), mirroring how genomic variants map to organism-level properties.
  • Config-driven experimental control
    Genome dimensionality, mutation rates, population size, environmental volatility, clustering hyperparameters, and LLM settings are externalised in YAML configurations to support systematic experimentation.
  • Observability and reproducibility
    Each generation’s summary statistics and cluster-level phenotypes are written to a structured SQLite database for auditability and post-hoc analysis.
  • LLM-assisted interpretation layer
    High-dimensional trait vectors are translated into natural-language descriptions using a Hugging Face-compatible LLM (or a deterministic heuristic), producing an interpretable “phenotype report” per cluster.
  • Modular, maintainable architecture
    The codebase is organised as a proper Python package (evo_sim) with well-defined modules: configuration, DNA/traits, environment, organism dynamics, evolution engine, database, and UI.

The result is closer to a digital evolution lab than a tutorial snippet: you can run controlled evolutionary experiments, adjust evolutionary parameters, and analyse population-level outcomes both numerically and through LLM-generated summaries.


2. Architectural Overview

The high-level architecture is composed of the following layers:

  • Genotype layer: DNA represented as a 2-D matrix of continuous allelic intensities.
  • Phenotype layer: deterministic projection from DNA to a trait vector.
  • Environment layer: time-varying ecological state (UV, temperature, nutrients, toxins).
  • Organism model: energy balance, damage accumulation, and reproduction dynamics.
  • Evolution engine: multi-generation loop implementing mutation, selection, and replacement.
  • Analytics and storage: SQLite database capturing generation metrics and cluster means.
  • LLM integration: trait-vector-to-text translation using a Hugging Face-style interface.
  • UI layer: static HTML dashboard for inspecting metrics and cluster descriptions.

The package layout is:

evo_sim/
  __init__.py
  main.py                  # entry point: simulation + dashboard generation

  config/
    __init__.py
    simulation_config.yaml # genome, population, environment, traits, clustering
    llm_config.yaml        # LLM model + prompt template

  models/
    dna.py                 # DNA matrix representation & mutation operators
    traits.py              # genotype -> phenotype mapping
    environment.py         # stochastic environment ("bombardment") dynamics
    organism.py            # organismal state & survival functions
    engine.py              # evolutionary loop across generations
    database.py            # SQLite logging of metrics and cluster means
    llm.py                 # trait vectors -> natural language descriptions

  ui/
    dashboard.py           # static HTML dashboard from the SQLite log

Running:

python -m evo_sim.main --output dashboard.html

executes a full evolutionary run with the configured parameters and generates both the SQLite log and a static dashboard HTML file.


3. Genomic Representation: DNA as a Matrix

Each organism’s genome is encoded as a dense matrix:

  • Rows ≈ chromosomes
  • Columns ≈ gene loci or feature positions along each chromosome
  • Entries ≈ continuous “allelic intensities” in the range [0, 1]

Formally, for an organism:

DNA ∈ [0, 1]C × G

where C is the number of synthetic chromosomes (e.g., 48) and G is the number of loci per chromosome, both specified in the YAML configuration.

While biological genomes use discrete nucleotides and codons, this simulator adopts a continuous representation to support a smooth genotype–phenotype mapping. Small perturbations in the DNA matrix lead to continuous variations in trait space, which is well-suited for synthetic evolution.

3.1 Mutation Operators

Mutations operate directly on the allelic intensities:

  • Point mutations
    With probability p_mut per gene, Gaussian noise is added to that entry: values are then clamped back into [0, 1].
  • Structural perturbations (optional)
    Low-probability duplication or deletion events act along columns (loci), emulating copy-number-like changes.
  • Clamping
    All mutated values are clipped to [0, 1] to keep the representation numerically bounded.

Each offspring inherits the parent’s DNA matrix subject to these mutation operators. Conceptually, each matrix element stands in for the effective dosage or strength of a hypothetical gene product at that locus.


4. Genotype → Phenotype: Trait Projection

The genotype–phenotype map is implemented as a fixed, randomly initialised, nonlinear projection from DNA space to trait space:

  1. Flatten the C × G DNA matrix into a single vector.
  2. Apply a linear transformation z = Wg + b, where W and b are fixed.
  3. Apply a logistic activation t = σ(z) to obtain trait values in (0, 1).

The resulting trait vector t is interpreted along a set of named axes such as:

  • light_sensitivity
  • uv_resistance
  • temp_optimum
  • temp_tolerance
  • nutrient_affinity_a, nutrient_affinity_b
  • motility_speed, motility_directionality
  • dna_repair_rate
  • replication_rate

The trait count and names are defined in simulation_config.yaml, while the projection matrix W and bias b are initialised from a seeded RNG and remain fixed throughout the run. This captures a “frozen developmental program” that evolution must adapt to, in analogy with the biophysical constraints of real organisms.


5. Environment Model and Bombardment Dynamics

The environment is represented as a compact ecological state vector:

  • UV radiation level
  • Ambient temperature
  • Concentration of nutrient A
  • Concentration of nutrient B
  • Toxic load

All components are normalised to the interval [0, 1]. At each time step within a generation, the environment undergoes:

  • Minor fluctuations: small random perturbations representing background noise.
  • Major events: larger jumps modelling events such as UV spikes, nutrient crashes, or toxicity surges, occurring with a configurable low probability.

This produces a bounded stochastic process on a low-dimensional environmental manifold. By adjusting step sizes, probabilities, and the number of steps per generation, one can simulate different selective regimes (stable vs. volatile environments, frequent vs. rare catastrophes, etc.).


6. Organism Dynamics: Energy, Damage, and Reproduction

Each organism experiences the environment through its trait vector. The Organism model encodes three main state variables:

  • Energy: a scalar representing metabolic reserves.
  • Damage: a scalar summarising cumulative stress and injury.
  • Reproduction count: the number of successful replication events.

6.1 Energy Balance

Energy is updated each time step based on:

  • Intake from nutrient fields, modulated by nutrient_affinity_a and nutrient_affinity_b.
  • Expenditure comprising a baseline metabolic cost plus a motility-dependent cost (higher motility_speed increases energy usage).
  • Organisms whose energy declines below a configured threshold are considered non-viable.

6.2 Damage Accumulation and DNA Repair

Damage evolves according to:

  • UV-induced damage, attenuated by uv_resistance.
  • Thermal stress, driven by the difference between current environmental temperature and temp_optimum, with temp_tolerance acting as a buffer.
  • Toxin-related damage, scaled by environmental toxin load and any configured resistance traits.
  • A repair component proportional to dna_repair_rate.

If accumulated damage exceeds a threshold, the organism is marked as dead. This captures a stylised interaction between genotoxic stress, repair mechanisms, and survival.

6.3 Reproductive Potential

Reproduction is modelled as a stochastic event whose probability depends on:

  • Current energy level (reproduction is suppressed at low energy).
  • Current damage level (heavily damaged organisms rarely reproduce).
  • The trait replication_rate, analogous to an intrinsic r-strategy parameter.

When conditions are favourable, the organism increments its reproduction counter. At the end of the generation, this count feeds into the fitness calculation.


7. Evolution Engine: Generations, Fitness, and Clustering

The EvolutionEngine coordinates the dynamics across generations. For each generation, it:

  1. Simulates a fixed number of time steps, updating the environment via bombardment and stepping each organism.
  2. Computes a scalar fitness for each organism, combining final energy, reproduction count, and damage penalties.
  3. Logs summary statistics (mean energy, mean damage, mean reproduction, survivors) to SQLite.
  4. Performs k-means clustering in trait space to approximate “species” and logs cluster means.
  5. Samples parents proportional to fitness and generates the next generation via mutation.

The system currently uses asexual reproduction with mutation. The architecture is modular enough to support later extension to recombination, multi-parent reproduction, or explicit phylogenetic tracking.


8. Data, Logging, and Experiment Traceability

All data from a run is written to data/logs/evolution.db using SQLite. Two core tables are maintained:

8.1 Generation-Level Metrics

The generations table holds:

  • Generation index
  • Mean energy
  • Mean damage
  • Mean reproduction count
  • Number of surviving organisms
  • Timestamp and optional metadata

8.2 Cluster-Level Phenotypes

The cluster_means table holds:

  • Generation index
  • Cluster identifier
  • Mean trait vector (JSON-encoded float array)
  • Textual description (generated by the LLM or heuristic translator)

This transforms each simulation run into a structured artifact that can be interrogated with notebooks, dashboards, or external tools. The project also includes a static dashboard generator (ui/dashboard.py) that renders an HTML summary of metrics and cluster descriptions over time.


9. LLM Integration: Phenotype Interpretation

High-dimensional trait vectors are difficult to interpret manually. To address this, the project uses a trait-vector-to-text translation layer implemented in llm.py.

For each cluster mean trait vector, the system:

  1. Reads an LLM configuration from llm_config.yaml, including model name and prompt template.
  2. Interpolates the trait values into the prompt template.
  3. If a Hugging Face-compatible model is available, calls the LLM to generate a concise description of the cluster’s ecological niche and survival strategy.
  4. If no LLM is configured, falls back to a deterministic heuristic that buckets trait values into low/medium/high and constructs a textual summary.
  5. Stores the generated description in the cluster_means table.

This effectively produces an “AI-generated phenotype report” per cluster, offering a human-readable description of emergent strategies in trait space. It also opens the door to future LLM-based extensions, such as scenario generation or narrative reporting across generations.


10. Why This Architecture Matters

This framework is deliberately engineered as a platform for serious experimentation in digital evolution, rather than a minimal demonstration. It combines:

  • A well-structured genomic-style representation.
  • A nontrivial genotype–phenotype mapping.
  • Explicit, configurable environmental dynamics.
  • A clear organismal life-cycle model.
  • A persistent analytics layer for traceability.
  • LLM-based interpretation of emergent phenotypes.

From a data/AI engineering standpoint, it illustrates how to:

  • Design simulations as maintainable packages, not monoliths.
  • Treat simulation output as first-class data, suitable for long-term analysis.
  • Integrate LLMs as interpreters on top of structured numeric dynamics.

From a computational-evolution standpoint, it provides a sandbox for exploring:

  • How mutation rates and bombardment regimes influence stability and diversity.
  • Which trait combinations consistently survive given environmental statistics.
  • How phenotypic clusters (species-like entities) emerge and shift over time.

The current implementation also serves as a foundation for further extensions: multi-cellular organisation, explicit spatial structure, recombination, vector-database-driven search over phenotypes, and more.


11. Running the Simulation

From the project root, a full simulation and dashboard generation can be invoked with:

python -m evo_sim.main --output my_dashboard.html

This will:

  1. Load simulation_config.yaml and llm_config.yaml.
  2. Run the configured number of generations.
  3. Write metrics and cluster means to data/logs/evolution.db.
  4. Generate my_dashboard.html as a self-contained static dashboard.
Next directions: introduce multi-cellular models, add spatial structure, implement recombination, or integrate a vector database for large-scale phenotype search and cross-run analysis, all while keeping the existing architecture intact.

License and Rights Notice

All code, documentation, simulation designs, and descriptive text associated with this project are the exclusive property of the author.

If you wish to collaborate on this work, integrate it into your own systems, or use any part of this codebase or its outputs (including derivative simulations, dashboards, or analytical results), you must absolutely reach out to me in advance and obtain my explicit written permission.

No part of this project, including but not limited to source code, configuration files, database schemas, generated artifacts, or written explanations, may be reproduced, distributed, modified, or reused in any form without my prior consent.

I hold all rights in a legal manner.

For collaboration requests or licensing discussions, please contact me directly via the channels listed on my GitHub profile.

Comments

Popular posts from this blog

Vector Databases

Best Python Learning Resources for Data Engineering Job

Best SQL Learning Resources for Data Engineering Jobs and Interviews