Reference: See for current context understanding: https://www.sciencedirect.com/science/article/abs/pii/S2590238525005569
Goal: Explore the simulation/code space to discover parameter and code configurations that produce biologically interesting patterns in particle-field simulations. Understanding morphogenesis requires more than generating patterns; it requires understanding how interacting processes converge toward stable, functional forms. This work presents a closed-loop experimental framework in which experiments, reasoning, and long-term memory are tightly coupled, with a large language model (LLM) operating as an active scientific agent. Note, the LLM does not only explore parameter space; it is allowed, at controlled points, to modify and replace the governing partial differential equations (PDEs) that define the system dynamics. In this framework, PDEs are treated as hypotheses, not fixed truths. Rather than treating particles or fields as primary objects, the framework treats interactions as fundamental, with structure arising from their mutual constraint. The LLM evaluates simulation outcomes, formulates mechanistic hypotheses, and directs subsequent interventions through structured exploration and persistent memory, enabling cumulative understanding across regimes rather than isolated optimization.
Four Coupled Interactions, four PDEs:
Field–Field Reaction-diffusion PDE Turing patterns via activator-inhibitor dynamics
Field–Particle Diffusiophoresis Field gradients drive particle motion
Particle–Field Consumption/production Particles locally modify concentrations
Particle–Particle Attraction-repulsion Short-range forces between particles
Each block = n_iter_block iterations (default: 8) exploring one configuration space.
The prompt provides: Block info: block {block_number}, iteration {iter_in_block}/{n_iter_block} within block
You maintain TWO files:
File: diffusiophoresis_Claude_analysis.md
- Append every iteration's full log entry
- Append block summaries
- Never read this file - it's for human record only
File: diffusiophoresis_Claude_memory.md
- READ at start of each iteration
- UPDATE at end of each iteration
- Contains: established principles + previous blocks summary + current block iterations
- Fixed size (~500 lines max)
Read diffusiophoresis_Claude_memory.md to recall:
- Established principles about what produces interesting patterns
- Previous block findings
- Current block progress
Examine the Individual frames: graphs_data/{dataset_name}/Fig/Fig_0_XXXXXX.png
2x2 Figure Layout (each frame):
- Top row: Field concentrations C1 (left) and C2 (right)
- Bottom left: Particle spatial organization
What to look for (in priority order):
- Particle patterns (bottom left) - PRIMARY
- Field patterns C1/C2 (top row) - SECONDARY
Assess the pattern using these dimensions:
| Dimension | Categories |
|---|---|
| Symmetry | none | radial | hexagonal | stripes | other |
| Particle organization | collapsed | uniform | clustered | network | segregated |
| Stability | unstable (NaN/escape) | transient | stable |
| Novelty | repeat | variant | novel |
Primary metrics (use these for scoring):
spatial_entropy: COMPLEXITY - normalized entropy of particle spatial distribution (0-1). Values ~0.3-0.7 indicate structured patterns. Near 1 = uniform/boring, near 0 = collapsed to single point.plateau: STABILITY - convergence score (0-1). Compares mean particle velocity in final 20% vs early 20% of simulation. Near 1 = velocity dropped (steady state), near 0 = still moving fast.particles_in_box_pct: CRITICAL - percentage of particles remaining in [0,1] box. Low values indicate particle escape/instability.
Secondary metrics:
clustering: particle clustering metric (0.289 - pos_std) / 0.289. Values near 0 = uniform, higher = clustered.pattern_growth: change in field std over time. Positive = patterns developing.C1_std,C2_std: field concentration variation. Higher = stronger Turing patterns.
Metric interpretation for assessment:
| Metric | Stability=stable | Particles=clustered | Particles=network |
|---|---|---|---|
| plateau | > 0.5 | any | any |
| clustering | any | > 0.4 | -0.2 to 0.3 |
| particles_in_box | > 90% | > 90% | > 90% |
| spatial_entropy | 0.3-0.8 | 0.5-0.8 | 0.7-0.9 |
Append to Full Log ({config}_analysis.md) and Current Block sections of {config}_memory.md:
- In memory.md: Insert iteration log in "Iterations This Block" section (BEFORE "Emerging Observations")
- Update "Emerging Observations" at the END of the file with running notes
Log Format {config}_analysis.md: This format is compulsory
## Iter N
Node: id=N, parent=P
Mode/Strategy: [exploit/explore/boundary/code-modification/multi-type]
Config: params_mesh=[...], n_frames=X, delta_t=Y, ...
n_particle_types: [1/2/3]
Metrics: entropy=[X.XX], plateau=[X.XX], in_box=[XX.X]%, clustering=[X.XX]
Assessment:
- Symmetry: [none/radial/hexagonal/stripes/other]
- Particles: [collapsed/uniform/clustered/network/segregated]
- Stability: [unstable/transient/stable]
- Novelty: [repeat/variant/novel]
Visual: [description of patterns observed]
Mutation: [param or code]: [old] -> [new]
Observation: [what did this change reveal?]
Next: parent=P
Read ucb_scores.txt:
- If empty →
parent=root - Otherwise → select node with highest UCB as parent
Strategies:
| Condition | Strategy | Action |
|---|---|---|
| Default | exploit | Highest UCB node, try mutation |
3+ consecutive stable + hexagonal |
failure-probe | Extreme parameter to find boundary |
| n_iter_block/4 consecutive successes | explore | Select outside recent chain, branch to different parameter dimension |
| 2+ distant nodes with good metrics | recombine | Merge params from both nodes |
| 4+ consecutive partial with improving trend | scale-up | Increase n_frames or data_augmentation_loop to break plateau |
| 4+ consecutive converged with same param dimension | forced-branch | Select 2nd highest UCB node (not recent chain), switch param dimension |
novel pattern found |
robustness-test | Re-run same config to verify |
3+ consecutive unstable (iter 5+) |
code-change | Consider modifying PDE equations |
shuffle_particle_types rule: Toggle every iteration (false→true→false→...). Only applies when n_particle_types: 1 — for multi-type runs, keep it false. Use n_particle_types: 1 for the first iteration of each block only.
IMPORTANT - Particle Type Diversity:
Maintain roughly equal exploration of different particle type counts:
- ~33% of iterations should use
n_particle_types: 1 - ~33% of iterations should use
n_particle_types: 2 - ~33% of iterations should use
n_particle_types: 3
Multi-type configurations enable richer dynamics:
- Different types can have opposing mobilities (one attracted, one repelled by gradients)
- Cross-type attraction/repulsion creates phase separation or mixing
- Multiple types can create predator-prey or symbiotic dynamics
.py files.
Simulation Parameters (can change within block):
simulation:
params_mesh:
- [D1, Da_c, A, B, mu, ...] # Brusselator: diffusion, Damköhler, A, B
- [D2, M2, ...] # C2 field parameters
- [Pe, consumption, production, influence_radius, ...] # Particle-field coupling
n_frames: 4000 # simulation length (1000-10000)
delta_t: 5.0E-4 # time step (1E-5 to 1E-3)
n_particles: 9600 # particle count
n_nodes: 10000 # mesh resolution - MUST BE PERFECT SQUARE
n_particle_types: 1 # int 1, 2, or 3
shuffle_particle_types: false # toggle every iteration (only when n_particle_types=1, first iter of block)shuffle_particle_types: Toggle every iteration when n_particle_types: 1. Use n_particle_types: 1 for the first iteration of each block only.
IMPORTANT: n_nodes must be a perfect square (the mesh is n×n grid).
Use only these values: 10000 (100×100), 22500 (150×150), 40000 (200×200), 62500 (250×250).
Do NOT use values like 25000, 30000, etc. - simulation will crash.
To enable multi-type:
- Set
n_particle_types: Nin config simulation.paramsMUST have exactly N rows (one per particle type). Mismatched row count causes a runtime crash in PDE_D.- Keep total particle count constant: When changing
n_particle_types, particles are distributed equally among types. The totaln_particlesshould remain ~9600 to maintain simulation density. Example: 1 type = 9600 particles, 2 types = 9600 total (4800 each), 3 types = 9600 total (3200 each).
Quick-start templates for multi-type configs:
2 particle types (opposing responses):
simulation:
params:
- [-16, 16, 180, -180, 1.6, 1.0, 1.6, 1.5] # Type 0: attracted to C1 peaks
- [16, -16, -180, 180, 1.8, 1.0, 1.1, 1.9] # Type 1: repelled from C1 peaks
n_particle_types: 2
n_particles: 9600 # 4800 each type
sigma: 0.0053 particle types (complex ecosystem):
simulation:
params:
- [-16, 16, 180, -180, 1.6, 1.0, 1.6, 1.5] # Type 0: consumer
- [8, -8, -90, 90, 1.8, 1.8, 1.1, 1.9] # Type 1: producer
- [0, 0, 0, 0, 2.0, 1.0, 2.0, 1.0] # Type 2: neutral/interactor
n_particle_types: 3
n_particles: 9600 # 3200 each type
sigma: 0.005- Make ONE change at a time to get causal understanding right
This code implement the mesh_model that governs the field-field interaction
IMPORTANT: PDE variant creation are ONLY allowed at the end of a block when you see >>> BLOCK END <<< in the prompt. During regular iterations within a block, you can only modify config parameters.
Before creating a variant: Check the PDE Variants table in Working Memory and existing files in src/ParticleGraph/generators/ to avoid duplicating work. Only create a new variant if the desired physics isn't already implemented.
- ONLY at block boundaries - Never during regular iterations
- MUST cite scientific literature - Every variant must reference source model
- MUST include
PARAMS_DOC- Self-documenting parameter structure - MUST add compatibility attributes - Add
self.Aandself.Bin__init__(required by base class)
Naming convention:
| File Name | Config mesh_model_name |
|---|---|
PDE_Diffusiophoresis_GrayScott.py |
Diffusiophoresis_Mesh_GrayScott |
PDE_Diffusiophoresis_FHN.py |
Diffusiophoresis_Mesh_FHN |
Creating a variant (5 steps):
- Copy base file and rename class to match filename
- Add docstring with literature citation (author, year, journal)
- Add PARAMS_DOC with model equations and parameter descriptions
- Add compatibility attributes in
__init__:# Required for compatibility with base class expectations self.A = torch.tensor(1.0, device=p.device) # Initial U value self.B = torch.tensor(0.0, device=p.device) # Initial V value
- Implement reaction equations in
forward(), update config
utils.py init_mesh function
When creating a new mesh model variant, you MUST also update src/ParticleGraph/generators/utils.py to add a case for your new mesh_model_name in the init_mesh() function's match statement (~line 980).
Example - if creating PDE_Diffusiophoresis_GrayScott.py with mesh_model_name: PDE_Diffusiophoresis_GrayScott:
# In utils.py init_mesh() function, add a new case:
case 'PDE_Diffusiophoresis_GrayScott':
# Gray-Scott initialization
node_value = torch.zeros((n_nodes, 2), device=device)
node_value[:, 0] = 1.0 # U = 1
node_value[:, 1] = 0.0 # V = 0
n_seeds = max(1, n_nodes // 100)
seed_indices = torch.randperm(n_nodes)[:n_seeds]
node_value[seed_indices, 0] = 0.5
node_value[seed_indices, 1] = 0.25Common errors and fixes:
| Error | Cause | Fix |
|---|---|---|
UnboundLocalError: 'node_value' |
Missing case in init_mesh() |
Add case for new mesh_model_name in utils.py |
NameError: mesh_model_name |
Using bare variable | Use config.graph_model.mesh_model_name |
KeyError in PyG |
Class not registered | Ensure class name matches file suffix |
AttributeError: no attribute 'A' |
Missing compatibility | Add self.A, self.B in __init__ |
Established models:
| Model | Key Params | Literature |
|---|---|---|
| Gray-Scott | F, k | Pearson (1993) Science 261 |
| FitzHugh-Nagumo | a, b, ε | FitzHugh (1961) Biophys J |
| Schnakenberg | a, b, γ | Schnakenberg (1979) JTB |
Log format:
### Variant: PDE_Diffusiophoresis_GrayScott
Literature: Pearson (1993) Science 261:189-192
Rationale: [why this model]
Config: mesh_model_name: Diffusiophoresis_Mesh_GrayScott
Note: New variants are auto-committed by GNN_LLM after creation.
This code implement the particle_model that governs altogether the field-particle, particle-particle and the particle-field interactions
IMPORTANT: PDE variant creation are ONLY allowed at the end of a block when you see >>> BLOCK END <<< in the prompt. During regular iterations within a block, you can only modify config parameters.
Before creating a variant: Check the PDE Variants table in Working Memory and existing files in src/ParticleGraph/generators/ to avoid duplicating work. Only create a new variant if the desired physics isn't already implemented.
- ONLY at block boundaries - Never during regular iterations
- MUST cite scientific literature - Every variant must reference source model
- MUST include
PARAMS_DOC- Self-documenting parameter structure forparamsslots - MUST maintain same interface - Same
__init__signature andforward(data, direction)modes
Naming convention:
| File Name | Config particle_model_name |
|---|---|
PDE_D_Boids.py |
PDE_ParticleField_D_Boids |
PDE_D_Chemotaxis.py |
PDE_ParticleField_D_Chemotaxis |
Creating a variant (5 steps):
- Copy base PDE_D.py and rename class to match filename (e.g.,
PDE_D_Boids) - Add docstring with literature citation (author, year, journal)
- Add PARAMS_DOC documenting how
paramsslots are interpreted:PARAMS_DOC = { "model_name": "Boids", "literature": "Reynolds (1987) SIGGRAPH 'Flocks, Herds, and Schools'", "params": [ {"index": 0, "name": "alignment", "description": "Alignment strength", "typical_range": [0.1, 2.0]}, {"index": 1, "name": "cohesion", "description": "Cohesion strength", "typical_range": [0.1, 2.0]}, # ... etc ] }
- Implement particle dynamics in
message()for modes:'fp','pf','pp' - Update config with new
particle_model_nameand appropriateparamsvalues
Parameter reinterpretation:
Each PDE_D variant reinterprets the same params tensor slots according to its own physics:
| Variant | params[type][0:4] interpretation |
|---|---|
| PDE_D (base) | M1, M2, consumption, production |
| PDE_D_Boids | alignment, cohesion, separation, vision_radius |
| PDE_D_Chemotaxis | sensitivity, adaptation_rate, threshold, ... |
Established models for particle dynamics:
| Model | Key Behavior | Literature |
|---|---|---|
| Boids | Flocking (alignment, cohesion, separation) | Reynolds (1987) SIGGRAPH |
| Chemotaxis | Gradient sensing with adaptation | Keller-Segel (1971) JTB |
| Active Matter | Self-propelled particles | Vicsek (1995) PRL |
Log format:
### Variant: PDE_D_Boids
Literature: Reynolds (1987) SIGGRAPH 'Flocks, Herds, and Schools'
Rationale: [why this model for particle dynamics]
Config: particle_model_name: PDE_ParticleField_D_Boids
Note: New PDE_D variants are auto-committed by GNN_LLM after creation.
Add/modify rules based on block experience:
- If branching rate < 20% → add exploration rule
- If stuck at low scores → add code-change trigger
- Summarize block findings (2-3 lines)
- Update Knowledge Base with confirmed principles
- Clear "Iterations This Block" section
## Per-block Regime Comparison
| Regime | mesh_model | particle_model | n_types | Symmetry | Particles | Key Insight |
| ------ | --------------------- | ------------------- | ------- | -------- | --------- | ----------- |
| Base | Diffusiophoresis_Mesh | PDE_ParticleField_D | 3 | radial | clustered | baseline |
## Insights
| Category | Finding |
| ----------- | -------------------------- |
| Patterns | [key pattern observations] |
| Performance | [what configs work well] |
| Failures | [what to avoid] |
---
## Knowledge Base
### Established Principles
[Confirmed findings across 3+ iterations - see Knowledge Base Guidelines]
### Open Questions
[Tentative patterns needing more testing, contradictions to resolve]
### Failed Configurations
[What to avoid]
### Code Insights
[What code changes helped/hurt]
### PDE Variants
| Variant | Model | Literature | Status | Best Symmetry | Best Particles |
| ------------------------------- | ----------- | ---------------- | ------- | ------------- | -------------- |
| Diffusiophoresis_Mesh | Brusselator | Prigogine (1968) | active | hexagonal | clustered |
| Diffusiophoresis_Mesh_GrayScott | Gray-Scott | Pearson (1993) | tested | radial | network |
| PDE_Diffusiophoresis_FHN | FHN | FitzHugh (1961) | active | hexagonal | network |
**Action needed if imbalanced:** If one type is under-represented, use it in next iteration!
---
## Previous Block Summary
[Short summary of last block]
---
## Current Block
### Block Info
Parameters: ...
mesh_model_name: [current variant]
Iterations: M to M+8
### Hypothesis
[What are we exploring this block?]
### Iterations This Block
[Current block iterations only]
### Emerging Observations
[What's working/failing]
**CRITICAL: This section must ALWAYS be at the END of memory file. When adding new iterations, insert them BEFORE this section.**Examples:
- ✓ "High diffusion_u with low production_v creates traveling waves" (causal, generalizable)
- ✓ "n_particle_types=3 produces richer clustering than n_types=1" (experimental finding)
- ✓ "consumption > 0.5 destabilizes patterns" (boundary condition)
- ✓ "Brusselator achieves hexagonal symmetry, Gray-Scott stays radial" (model comparison)
- ✗ "params_mesh=[1.0, 0.5, 0.1] worked in Block 4" (too specific)
- ✗ "Iter 12 was good" (not a principle)
| Level | Criterion | Action |
|---|---|---|
| Established | Consistent across 3+ iterations/blocks | Add to Principles |
| Tentative | Observed 1-2 times | Add to Open Questions |
| Contradicted | Conflicting evidence | Note in Open Questions |
- Patterns needing more testing
- Contradictions between blocks
- Theoretical predictions not yet verified
dC1/dt = D1 * ∇²C1 + Da_c * (A - (B+1)*C1 + C1²*C2)
dC2/dt = D2 * ∇²C2 + Da_c * (B*C1 - C1²*C2)
- Turing instability: B > 1 + A² produces patterns
- Pattern wavelength: depends on D1/D2 ratio
- Higher Da_c → faster dynamics
Particles move in response to concentration gradients:
v_particle = M1 * ∇C1 + M2 * ∇C2
- Positive mobility → move up gradient (toward higher concentration)
- Negative mobility → move down gradient (away from concentration)
- Particles can consume/produce chemicals
- This creates feedback: particles affect fields, fields move particles
- Feedback can stabilize or destabilize patterns