Skip to content

Modernize: src/die layout, Simulation runner, typer CLI, MLflow observability - #4

Merged
gkirgizov merged 10 commits into
mainfrom
renovate
Jul 26, 2026
Merged

Modernize: src/die layout, Simulation runner, typer CLI, MLflow observability#4
gkirgizov merged 10 commits into
mainfrom
renovate

Conversation

@gkirgizov

Copy link
Copy Markdown
Owner

Summary

Full modernization of the project to a current Python + AI stack, in 7 commits (one per phase):

  • Packaging & tooling: src/die/ layout (renamed from core, history preserved via renames), pyproject.toml + uv.lock (Python >= 3.12, numpy 2.x, torch 2.x, gymnasium 1.x, mlflow 3.x), ruff, and a CI workflow that actually installs dependencies and discovers tests.
  • Machine level: new die.sim.Simulation owns the agent-env loop with pluggable SimObserver hooks; presentation (live matplotlib view, headless GIF/MP4 recording via imageio, metrics) became observers. Env gained public observe() and seeded reset() with an init-snapshot cache.
  • Learning stack (researched evotorch / EvoX / evosax / nevergrad / pycma; verdict in docs/NOTES.md): evotorch 0.6.1 kept, pinned, behind a generation-granular SearcherBackend seam; the intended follow-up is a JAX env port + evosax slotting into the same seam.
  • Observability: MlflowTrainLogger logs full run config, per-generation fitness metrics, checkpoints and rollout animations as artifacts (SQLite backend; mlflow >= 3.14 rejects the legacy file store).
  • CLI: die run / record / train / replay (typer), replacing edit-the-source example scripts.
  • Tests: 76 fast + 2 slow, incl. new behavioral suites: env physics invariants, learning smoke test (fitness improves, checkpoint round-trips, MLflow artifacts land), and calibrated agent comparisons (Physarum out-forages Brownian >= 1.15x; gradient agent climbs chem gradients).

Real bugs fixed along the way

  • np.float, cm.get_cmap, xavier_uniform — repo crashed on any modern install
  • In-place multiply after Tanh silently broke backprop
  • agents_die lifecycle rebinding desynced AgentIndexer
  • Dead agent slots phantom-fed from cell (0, 0), polluting rewards
  • init_params method-vs-property broke JSON save for scripted agents
  • Test file never discovered by pytest; CI never installed deps

Test plan

  • uv run pytest — 76 passed (unit + behavioral)
  • uv run pytest -m slow — 2 passed (full training pipeline incl. MLflow)
  • uv run ruff check . — clean
  • die run --headless, die record, die train (tiny run), die replay <ckpt> exercised end-to-end
  • Live interactive view verified locally (macosx backend)

Note: commits are grouped by phase with final file contents; only the branch tip is guaranteed green, not every intermediate commit.

🤖 Generated with Claude Code

gkirgizov and others added 10 commits July 19, 2026 22:04
…fix CI

- src/ layout, package renamed core -> die (agent -> agents, evo -> neural,
  base_types -> types, render/plotting -> view/)
- pyproject.toml + uv.lock replace unpinned requirements.txt; Python >= 3.12,
  numpy 2.x, torch 2.x, gymnasium 1.x; ruff config; train/video extras
- Fix breakage on modern deps: np.float, cm.get_cmap, xavier_uniform,
  in-place multiply after Tanh that broke autograd (test_grad now backprops)
- Move tests to tests/ with test_* names so pytest discovers them;
  new CI workflow actually installs dependencies (old one installed only pytest)
- Drop dead code (data_utilities, agent/learning, scratch examples);
  extract research notes from core/__init__.py into docs/NOTES.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Simulation owns the agent-env loop; SimObserver hooks (Tqdm, Metrics)
  make presentation and metrics pluggable
- Env: public observe(), gymnasium-style seeded reset() with an
  init-snapshot cache (skips costly perlin regeneration on repeat seeds)
- Fix lifecycle desync (agents_die rebinding broke AgentIndexer) and
  phantom feeding by dead agent slots parked at (0, 0)
- registry: AgentId/DynamicsId enums with tuned defaults shared by
  CLI, examples and tests; seedable gradient/physarum RNGs;
  init_params made a proper property so scripted-agent JSON save works
- Tests: env physics invariants, simulation/observer contract, registry

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- FrameSource + compose_grid/to_uint8_rgb produce numpy RGB frames;
  Simulation.render_frames() memoizes per step across consumers
- InteractivePlotter rewritten: lazy figure, interactive-backend
  fallback chain instead of hardcoded qtagg; InteractiveViewObserver
- AnimationRecorder writes GIF/MP4 via imageio, fully headless
  (replaces matplotlib FuncAnimation path); Env no longer owns a renderer
- minimal_run example rewritten on the new API

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Console script entry point replacing hardcoded example __main__ blocks;
enum-backed agent/dynamics selection, --headless, --record, --seed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- SearcherBackend seam (generation-granular ask/tell) with evotorch
  0.6.1 (pinned; semi-dormant upstream) as the first backend: PGPE/CMA-ES
- EvolutionTrainer evaluates candidates through the same Simulation
  machinery; seeded eval modes (fixed / per-generation)
- MlflowTrainLogger: full run config as params, per-generation fitness
  metrics, checkpoints and rollout GIFs as artifacts; sqlite backend
  (mlflow >= 3.14 rejects the legacy file store)
- Learning smoke tests (slow): fitness improves over a tiny run,
  checkpoint round-trips, MLflow artifacts land

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Physarum must out-forage Brownian by a calibrated margin (measured
  1.27x food consumed; asserted floor 1.15x), stable across seeds;
  gradient agent provably climbs chemical gradients
- README: install (uv), CLI quickstart, training & MLflow usage,
  development workflow; milestone 3 (evolutionary Neural CA) done
- gitignore experiment outputs (runs/, samples/, mlruns, *.db)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rendered channels are unbounded while imshow accepts only [0, 1] floats
and logs a warning per out-of-range frame, flooding the terminal during
live view. Clipping in FrameSource keeps the live view and recorded
animations identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`die run --view` flooded the terminal with one
"Clipping input data to the valid range for imshow" warning per frame
per pane, scrolling the tqdm progress bar off the screen.

Root cause: the rendered channels are unbounded -- medium concentrations
reach ~3.2 and agent food ~1.25 -- while imshow accepts only [0, 1]
floats. It clipped them itself and logged a warning each time.

Clip at the FrameSource boundary, which covers the env panes and the
agent self-render alike. This is what imshow already did internally and
what to_uint8_rgb already did on the recording path, so neither the live
view nor the recorded animation changes: a 20-step recording is
byte-identical before and after.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AGENTS.md: architecture in one breath, the invariants and gotchas that
cost real debugging time (aliasing, array identity, dead-slot masking,
three-headed seeding, the coordinate seam), and workflow conventions —
kept at a level that won't go stale.

README: merged install into quick start, condensed learning section,
development section points to AGENTS.md and docs/NOTES.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clipping stopped imshow from warning, but the panes were still losing
detail: chem1 climbs past 4.0 and pinned ~5% of the medium pane to
white, the trace field settles at a steady state with 27% of the pane
permanently at the top of the colormap, and env_food never exceeds ~0.5
so it sat nearly invisible next to them.

Add ChannelNormalizer: a per-channel scale tracking an EMA of the
channel's 99th percentile. Percentile rather than max because the fields
are heavy-tailed -- a few hot pixels sit 3-4x above p99 and scaling by
the max would squash everything else back into the dark. The EMA keeps
brightness from pulsing as a channel drifts across a run, and a floor on
the divisor stops a near-empty field from being amplified into noise.

Applied where each frame is built, since only the renderer knows what a
channel means. Rescaled: the three medium concentrations, the trace
field (before colormapping, as the colormap itself clips), and
agent_food. Left alone: the alive/alpha mask (binary, not a quantity),
the medium pane's zero padding channels, and agent-owned frames such as
the gradient render, which is signed data centred on 0.5 -- rescaling
would move its neutral point.

Measured on physarum: medium chem1 saturation 5.0% -> 1.1%, env_food
distinct levels 179 -> 309, trace saturation 27% -> 1.4%.

On by default, with --normalize/--no-normalize on run/replay/record.
This changes recorded animations by default, and gives up cross-channel
comparison: equal brightness no longer means equal concentration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gkirgizov
gkirgizov merged commit 784b65c into main Jul 26, 2026
2 checks passed
@gkirgizov
gkirgizov deleted the renovate branch July 26, 2026 18:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant