Skip to content

sapristi/alife

Repository files navigation

YAACS - Yet Another Artificial Chemistry Simulator

An artificial chemistry simulator designed to trigger an evolutionary process. Molecules are graph objects (proteins) that interact with each other through chemical reactions, producing new molecules. The seed for evolution is a self-replicating system, and random mutations introduced by certain reactions provide the variation necessary for natural selection.

Technically, molecules encode proteins which fold into Petri nets that drive chemical reactions in a simulated environment.

Architecture

The project has three components:

  • Engine (engine/) — OCaml CLI that handles simulation: parsing molecules, building Petri nets, and running reactions. Outputs JSON to stdout.
  • Django app (django/) — Web application for managing experiments, running simulations (calls the OCaml binary as a subprocess), and exposing a REST API. Also provides a Typer-based CLI (cli.py) for running experiments from the command line.
  • Cytoscape visualization (cytoscape/) — JavaScript graph visualization using webcola for layout, bundled with webpack.

How they connect

Django calls the yaac OCaml binary via subprocess.Popen (see django/experiment/engine.py). The engine reads JSON input from CLI arguments and writes JSON results to stdout. Django parses the output and stores snapshots/logs in SQLite.

Grafana connects to the Django SQLite database for experiment monitoring dashboards.

Project structure

engine/                    # OCaml simulation engine
  bin/yaac.ml              # CLI entry point (cmdliner via ppx_subliner)
  bacterie/                # Simulation runtime (reactions, environments, molecule sets)
  base_chemistry/          # Core types: molecules, proteins, Petri nets, places, transitions
  libs/                    # Utility libraries (config, numerics, random, sets)
  jlog/                    # Custom JSON-capable logging library
  tests/                   # Alcotest tests and fixture states
    bact_states/           # JSON fixture files for initial states
  dune-project             # OCaml package definition

django/                    # Django web application
  manage.py                # Django management
  cli.py                   # Typer CLI entry point (experiment management)
  alife/                   # Django project settings
    settings.py
    urls.py
  experiment/              # Main Django app
    models.py              # Experiment, BactSnapshot, Log, InitialState
    engine.py              # YaacWrapper — subprocess interface to OCaml engine
    views.py               # REST API (DRF) and template views
    urls.py
    admin.py
    fixtures/
  subcommansds/            # Typer subcommands
    experiment.py          # list, run, clear experiments
  templates/               # HTML templates
  static/                  # Static assets
  Dockerfile               # Archlinux-based container
  pyproject.toml           # Python dependencies (uv)

cytoscape/                 # Graph visualization
  src/                     # JS source (index.mjs, cytoscape-cola.mjs, defaults.mjs)
  webpack.config.mjs
  package.json             # webcola dependency, webpack build

docs/                      # Documentation
  experiments.md           # Experiment log (summaries and links to reports)
  experiments/             # Detailed experiment reports
  plans/                   # Implementation plans

docker-compose.yaml        # Django + Grafana services
grafana/                   # Grafana data (SQLite datasource on Django DB)

Building and running

OCaml engine

Requires opam. Compatible with OCaml 5.x (tested with 5.4.0).

cd engine
opam switch create . ocaml-base-compiler.5.4.0 --yes
eval $(opam env)
opam pin add pringo git+https://github.com/sapristi/pringo.git --yes
opam install . --deps-only --with-test --yes
dune build          # Build the yaac binary
dune runtest        # Run alcotest tests

The binary is built at engine/_build/default/bin/yaac.exe. Key CLI subcommands:

  • from-mol — Parse molecule string, return protein + Petri net JSON
  • from-prot — Build Petri net from protein JSON
  • eval — Run N reaction steps from an initial state
  • load-signature — Expand a compact bacterie signature to full state
  • reactions — List available reactions from a state
  • acid-examples — List example acid types

Use --log-level (-l) to control logging. Set JSON_LOG=1 env var for JSON log output.

Django app

Requires Python >= 3.10. Uses uv for dependency management.

cd django
uv sync                                            # Install dependencies
uv run ./manage.py migrate                         # Create/update database
ln -sf ../engine/_build/default/bin/yaac.exe yaac  # Symlink engine binary
uv run ./cli.py load-initial-states                # Load fixtures from engine test data
uv run ./manage.py runserver                       # Start dev server (default: localhost:8000)

The Typer CLI for experiment management:

cd django
uv run ./cli.py experiment list                           # List experiments
uv run ./cli.py experiment run <id> <nb_reacs>            # Run experiment
uv run ./cli.py experiment run <id> <nb_reacs> --reset    # Run from initial state
uv run ./cli.py experiment clear <id>                     # Remove snapshots
uv run ./cli.py load-initial-states                       # Load fixtures from engine test data

Important: The yaac binary must be accessible at ./yaac from the Django working directory (the symlink step above handles this).

Docker (full stack)

docker-compose up       # Starts Django (port 8000) and Grafana (port 3000)

Cytoscape visualization

cd cytoscape
pnpm install
pnpm build              # Webpack bundle

Molecule encoding

Molecules are strings of atoms (A-F). They are parsed left-to-right into a list of acids, which form a protein that folds into a Petri net.

Acid types

Prefix Suffix Acid Notes
AAA Place Petri net place
BAA <id>DDF Regular_iarc Standard input arc
BC <atom><id>DDF Filter_iarc Guards on atom at cursor
BAB <id>DDF Filter_empty_iarc Guards on cursor past end
BAC <id>DDF No_token_iarc Fires only when place is EMPTY
BAD <id>DDF Copy_iarc Reads acid at cursor → [original, acid_token]
BBA <id>DDF Split_iarc Cuts token at cursor → [left, right]
CAA <id>DDF Regular_oarc Standard output arc
CBA <id>DDF Merge_oarc Inserts source into dest
CCA <id>DDF Move_fw_oarc Advances cursor
ABA <pattern>DDF Grab_ext Place grabs matching molecules
ABB Release_ext Place releases tokens externally
ABC Init_token_ext Place starts with empty token
ABD Copy_grabbed_ext Place with internal copy buffer for CopyGrabbed reaction
BAE <id>DDF Copy_done_iarc Fires when copy buffer complete; produces [template, copy]
EEE Stop_interpretation Rest of molecule is inert
  • Transition IDs: max_group_length = 6, lazy match (.{1,6}?)
  • Grab patterns: F(X)F = single char capture, FF = wildcard .*?, anchored ^...$

Engine dump format

  • areactants: active molecules (with Petri nets) as [mol, pnets_list] pairs
  • ireactants: inert molecules as {mol, qtt, ambient} dicts

OCaml conventions

  • PPX: ppx_deriving_yojson for JSON serialization, ppx_subliner for CLI argument parsing from type definitions
  • Key dependencies: containers (stdlib extension), zarith (arbitrary precision), pringo (PRNG), alcotest (testing)
  • Module layout: base_chemistry defines core domain types (molecules, proteins, Petri nets); bacterie implements runtime simulation; libs has general utilities; jlog is a custom structured logging library
  • CLI pattern: Subcommands are defined as modules with a params type (derived with subliner), a doc string, and a handle function

Django conventions

  • Models (experiment/models.py): Experiment holds initial state as JSON; BactSnapshot stores simulation state at checkpoints; Log stores per-step statistics; InitialState stores reusable starting configurations
  • Engine integration (experiment/engine.py): YaacWrapper class invokes the OCaml binary, StatLogCollector captures structured log output during runs
  • API: Django REST Framework viewsets for experiments and snapshots
  • CLI: Typer app in cli.py with subcommands in subcommansds/

Maintaining this file

Always keep README.md up to date when making changes that affect project structure, build instructions, conventions, or workflow. Ask the user before adding new sections. This file is a symlink — that's expected, edit it normally.

Running experiments

All commands run from django/.

CLI commands

uv run ./cli.py experiment list                              # List experiments
uv run ./cli.py experiment info <id>                         # Detailed experiment info
uv run ./cli.py experiment create <initial_state_id>         # Create from an InitialState
uv run ./cli.py experiment create <id> --name "my exp"       # With custom name
uv run ./cli.py experiment run <id> <nb_reacs>               # Run reactions (continues from last snapshot)
uv run ./cli.py experiment run <id> <nb_reacs> --reset       # Run from initial state
uv run ./cli.py experiment run <id> 1000 --stats-period 50 --snapshot-period 500
uv run ./cli.py experiment stats <id>                        # Show log stats as table
uv run ./cli.py experiment stats <id> --last 10 --csv        # Last 10 entries as CSV
uv run ./cli.py experiment compare <id1> <id2>               # Side-by-side comparison
uv run ./cli.py experiment clear <id>                        # Remove all snapshots

Never run two concurrent continuations on the same experiment. Each run resumes from the last snapshot and saves new snapshots at fixed intervals. Concurrent runs will start from the same snapshot and crash with a UNIQUE constraint error when both try to save at the same reaction count.

Key stats columns

  • ireactants.nb_species / ireactants.total_nb — inactive molecule diversity and count
  • areactants.nb_species / areactants.total_nb — active (functional Petri net) molecule diversity and count
  • reactions.breaks/collisions/grabs/transitions.nb_reactions — cumulative reaction counts by type

Experiment reports

Save reports to docs/experiments/YYYYMMDDTHHmm-<topic>.md (compact datetime to the minute). Include:

  • Header: full datetime (e.g. 2026-03-08 14:30), experiment ID, initial state name, parameters
  • Summary table of key stats over time
  • Analysis of observed phases/trends
  • Footer: initial state JSON (in <details> block), date, and git commit hash

After writing a report, add a summary entry to docs/experiments.md with the experiment IDs, names, key findings, and a link to the full report.

Development workflow

  1. Engine changes: Edit OCaml code in engine/, build with dune build, test with dune runtest
  2. Django changes: Edit Python code in django/, restart dev server. Use ./cli.py for experiment operations
  3. Visualization: Edit JS in cytoscape/src/, rebuild with pnpm build
  4. Full integration: Ensure the yaac binary is built and accessible from the Django working directory, then run the Django server

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors