Publish the unified scratch-training contract, compact best-policy bundles, calibrated targets, tests, and presentation-ready retained evidence while excluding archived and intermediate run payloads. Co-authored-by: Cursor <cursoragent@cursor.com>
10 KiB
Current DRL training pipeline
This document describes the executable modern training path in this directory. It is a workflow specification, not evidence that the current source has been fully retrained or validated after later fixes. The release includes compact best-policy bundles for retained active runs; retained result summaries remain historical writing inputs rather than post-fix retraining evidence.
1. Scope and cases
The active path uses CelerisLab on a 2000×600 D2Q9 grid, uniform regularized inlet, free-slip y walls, MRT collision, double buffering, zero action bias, Gymnasium environments, Stable-Baselines3 PPO, and observation-only VecNormalize.
Canonical active IDs omit _sc:
| Family | Cases | Config | SI |
|---|---|---|---|
| Karman baseline | kar_re100 |
configs/config_lbm_karman_2000x600.json |
800 |
| Karman cross-Re | kar_re60, kar_re200, kar_re400 |
matching _re60, _re200, _re400 JSON |
800, 500, 400 |
| Karman variable disturbance radius | kar_d075, kar_d15, kar_d2 |
base JSON | 800 |
| Illusion | ill_075L, ill_1L, ill_15L, ill_2L |
base JSON | 1100, 1200, 1200, 1200 |
Code-level Re uses reference length 2L0=40, so code re100 corresponds to physical cylinder-diameter Re_D=50. _tr is reserved for archived transfer experiments. _sc survives only in historical paths/artifacts and must not be introduced into active prose or new case IDs.
2. End-to-end flow
calibrate.py
├─ record calibrated target signal
├─ warm training geometry and snapshot it
├─ measure Stage0 (zero rotation)
├─ measure Stage1 (reference open loop)
└─ write calibration.json + target artifacts
│
▼
train_karman.py / train_illusion.py
├─ load the matched calibration and target artifacts
├─ build physical env → symmetry wrapper → DummyVecEnv → VecNormalize
├─ repeat: PPO learn chunk → same-env deterministic evaluation → checkpoint
└─ write policy, paired normalizer, log, TensorBoard, metadata
│
▼
../eval/ (separate inference/evaluation workflow)
Training and evaluation intentionally share one wrapped environment. This preserves CFD data continuity and avoids constructing a second GPU simulation, but evaluation is not side-effect-free: resetting/stepping changes environment state and, while VecNormalize.training remains true, updates observation running statistics. The policy weights do not update during evaluation.
3. Phase 0: calibration
calibrate.py performs the expensive target and baseline measurements. For Karman it records the wake of one upstream disturbance cylinder at three sensors. For Illusion it records a standalone target cylinder at three sensors plus target drag/lift, then stores a five-harmonic reconstruction of all eight channels.
Artifacts are a contract:
calibration.json: geometry metadata, SI, scales, DTW mapping, reward constants, action mapping.target.npy: calibrated six-channel sensor target, shape(150, 6).target_harmonics.json: Illusion only; calibrated sensor/force harmonic model.calibrate.log: provenance log when calibration is run locally.
Target artifacts must come from the same calibrated case, config, geometry, SI, and force convention as calibration.json. Training fallback target recording is convenience behavior, not a substitute for producing and preserving a calibrated artifact set.
Calibration warms the target simulation for 4*NX/U0 = 800000 lattice steps. It then warms the training geometry, snapshots it, and measures:
- Stage0 — zero rotation.
- Stage1 — reference open-loop rotation (
[0, 0.004, -0.004]Karman;[0, 0.005, -0.005]Illusion).
Stage numbers describe acquisition order, not quality. The code sorts their mean DTW similarities into worst_sim and better_sim before constructing SIM_BP; Stage1 is not assumed better. Karman falls back to the generic [0, .30, .65, .79, .89, 1] mapping when the measured spread is below 0.10 or the better baseline is below 0.5. Illusion uses its scene-specific sorted mapping and SIM_VAL=[0, .1, .35, .7, .85, 1].
Reward force sensitivities are:
- Karman:
K_CD=50,K_CL=100. - Illusion:
K_CD=12,K_CL=25.
4. Geometry, observations, and actions
Karman contains disturbance cylinder + three sensors + three controlled cylinders. Its 12-vector is six controlled-cylinder forces followed by six sensor velocities. Drag and lift rewards use the average across the three controlled cylinders and target zero force.
Illusion contains three sensors + three controlled cylinders; the target cylinder exists only during target acquisition. Its 14-vector appends reconstructed target drag and lift to the same 12 channels. Its force reward compares the sum of all three controlled-cylinder forces with the single target-cylinder force. This wider mismatch motivates the lower 12/25 Gaussian constants.
The historical field name target_diam is misleading. The implementation passes target_diam * L0 to CelerisLab as the circle radius. Therefore values 0.75, 1.0, 1.5, 2.0 are radius ratios relative to L0=20, giving physical lattice radii 15, 20, 30, 40 and diameters 30, 40, 60, 80. Preserve this interpretation when reading or regenerating artifacts.
Actions are three normalized rotations:
omega = -(action * ACTION_SCALE) * U0 / RADIUS
ACTION_SCALE=12, U0=0.01, RADIUS=10
A 0.1 EMA smooths commanded omega. There is no legacy action bias.
5. Three normalization layers
The observation path has three distinct layers; they must not be collapsed conceptually:
- Solver observation averaging — CelerisLab
read_sensor(..., normalize=True)returns a sensor-area and sampling-time average; force reads are sampling-time averages. - Calibration pre-scaling — force channels divide by
FORCE_SCALE; sensor channels divide bySENS_SCALE. Illusion target-force channels divide byFORCE_SCALEbefore entering the 14-vector. Currentdrl-pinball-calibration-v2artifacts store native CelerisLab sensor units. - Online
VecNormalize— running mean/variance whiten the complete observation (norm_obs=True,norm_reward=False,clip_obs=10,gamma=0.99). This is learned online during training; it is not the Phase 0 calibration.
Legacy schema-less calibrations instead used SENSOR_CC=78 to convert native sensor values into the old policy/DTW unit convention before pre-scaling. normalization.py keeps that compatibility path for an existing legacy policy plus its matching VecNormalize; the active v2 schema does not apply 78.
Inference must load the model's matching VecNormalize and freeze it (training=False). A “best model” without the normalizer state saved at the same best-selection point is incomplete.
6. PPO loop and shared evaluation
Defaults: sinusoidal MLP [64,64], n_steps=2048, learn_timesteps=2048, batch 64, 10 epochs, learning rate 3e-4, PPO gamma 0.995.
One outer total_episodes iteration is:
model.learn(learn_timesteps, reset_num_timesteps=False).- Disable symmetry probability on the same wrapped env.
- Reset and run up to 360 policy steps with
model.predict(..., deterministic=True). - Score mean reward over the last 180 steps.
- If improved, save
best_model.zipand rootbest_vecnormalize.pklat the same point. - Save per-iteration model and normalizer checkpoints; finally save
final_model.zip.
The environments never terminate a Gym episode themselves. “Episode” in logs and CLI means this outer PPO chunk, not a Gym episode.
Because evaluation uses the training VecNormalize, its observations contribute to running statistics. This is intentional and must be reproduced when continuing this workflow.
7. Symmetry status
SymmetryAugmentWrapper implements the up/down G transform. The supported training default is off (--symmetry-prob 0); enable it only as an explicit experiment.
History matters: legacy runs were configured with probability 0.5, but repeated model.learn(...) and evaluation resets reset the wrapper counter at each chunk. The mirror coin flip occurred only at the chunk boundary, after the last transition, so those models were effectively trained without augmented transitions. They must not be described as symmetry-augmented evidence.
8. Checkpoints, resume, and seeds
Each iteration pairs epNNNN_model.zip with epNNNN_vecnormalize.pkl. Resume loads both, reconstructs a new CFD environment, parses historical best reward from train.log, and continues at N+1.
Resume is policy-and-normalizer continuation. It is not exact state restoration: optimizer/RNG details, CFD process/context, environment trajectory, wrapper state, TensorBoard state, and wall-clock scheduling are not guaranteed to replay identically. Likewise, --seed differentiates stochastic runs; it is not a deterministic replay guarantee for GPU CFD + PyTorch + PPO.
Transfer loading remains in the Python entry points for historical compatibility, but active launchers train from scratch. Historical transfer scripts, logs, calibrations, and metadata live below archive/; see the warnings in CROSSRE_ANALYSIS.md and VARDIST_ANALYSIS.md.
9. Output contract and release status
A run directory contains calibration.json, train.log, tb/, meta.json, models/, and normalizer files. best_model.zip must travel with best_vecnormalize.pkl; final_model.zip uses final_vecnormalize.pkl, and per-iteration policies use the matching epNNNN_vecnormalize.pkl. The legacy root vec_normalize.pkl remains a final-state compatibility alias.
The repository publishes the source and canonical targets needed to reproduce the workflow, plus compact retained best-policy bundles (best_model.zip with the same-selection best_vecnormalize.pkl and provenance files). It does not publish intermediate/final checkpoints, full training logs, or TensorBoard streams. Any retained results/ summaries and figures are historical writing basis only. Do not infer current post-fix validation, complete retraining, reproducibility across hardware, or release qualification from them.