Files
DynamisLab/src/drl_pinball/reproduce/configs/scene_params.py
T
Frank14fandCursor f2f88c2442 feat(reproduce): legacy-test framework + fixed-inlet reproduce pipeline
- Track A (legacy_test): systematic validation scripts for all trained PPO
  models using LegacyCelerisLab. Each test script rebuilds the exact legacy
  CFD environment, runs deterministic inference, and compares against
  SR_analysis reference data using DTW-based comparison. Verified: Karman
  re100/re50/re200, Vortex lamb/taylor all pass (DTW > 0.95).

- Track B (reproduce): Phase 2 open-loop CFD validation + Phase 3 DRL
  inference using the legacy-compatible config (regularized inlet with
  neq_damp=1.0, matching the legacy NBB formula). The inlet scheme fix
  improves new-CFD Karman DTW from 0.916 to 0.943.

- Fixes: action_wrapper sign convention docstring, model inventory
  duplicate entries and missing models, stale config paths in legacy
  run_all_cases.py/run_illusion_vortex.py, illusion label formatting

- Add READMEs and run-all shell scripts for both tracks
- Add .gitignore entries for runtime output directories

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-12 23:09:53 +08:00

244 lines
8.2 KiB
Python

"""All scene parameters in one place.
Single source of truth for geometry, action scaling, norm formulas, and DRL settings.
Every scene family needed for reproduction is defined here.
Re convention:
- "re_code" uses reference length 2*D (=40 lattice units), matching model file naming.
- nu = U0 * (2*D) / re_code
- Physical Re_D = re_code / 2 (uses single cylinder diameter D=20)
"""
from __future__ import annotations
from typing import Any, Dict
import numpy as np
# ---------------------------------------------------------------------------
# Physics constants (must match config_lbm_pinball.json)
# ---------------------------------------------------------------------------
U0 = 0.01 # inlet centre velocity (lattice units)
L0 = 20.0 # base length unit = 1 cylinder diameter in lattice
D_CYL = 20.0 # single cylinder diameter
D_REF = 40.0 # reference length for code Re = 2*D
NX = 1280
NY = 512
CENTER_Y = (NY - 1) / 2.0 # 255.5
CFG_PATH = "configs/config_lbm_pinball.json"
def nu_from_re(re_code: float) -> float:
"""Kinematic viscosity from code Reynolds number."""
return U0 * D_REF / re_code
# ---------------------------------------------------------------------------
# Scene definitions
# ---------------------------------------------------------------------------
SCENES: Dict[str, Dict[str, Any]] = {}
# -- Steady Cloak (clean inflow, no disturbance cylinder) --------------------
# Also serves as the base pinball-only env for Illusion inference and Vortex.
SCENES["steady_cloak_re100"] = {
"scene_id": "steady_cloak",
"model": "d1a3o12_re100",
"model_subdir": "old",
"re_code": 100,
"nu": nu_from_re(100),
"s_dim": 12,
"a_dim": 3,
"has_disturbance": False,
"pinball_front_x": 30.0 * L0, # 600
"pinball_rear_x": 31.3 * L0, # 626
"pinball_y_span": 0.75 * L0, # 15
"sensor_x": 40.0 * L0, # 800
"sensor_y_span": 2.0 * L0, # 40
"sensor_radius": L0 / 4, # 5
"pinball_radius": L0 / 2, # 10
"sample_interval": 800,
"action_scale": 8.0,
"action_bias": np.array([0.0, -4.0, 4.0], dtype=np.float32),
"u0": U0,
"fifo_len": 150,
"conv_len": 30,
"max_steps": 500,
"n_objects": 6,
"obs_slice": (0, 12),
"force_norm_formula": "6*max",
"sens_norm_factor": 5,
"target_type": "steady", # mean of clean channel
"warmup_steps_init": int(4 * NX / U0),
"warmup_steps_pinball": int(4 * NX / U0),
}
# -- Karman Cloak (upstream disturbance cylinder) ----------------------------
SCENES["karman_cloak_re100"] = {
"scene_id": "karman_cloak",
"model": "d1a3o12_re100",
"model_subdir": "old",
"re_code": 100,
"nu": nu_from_re(100),
"s_dim": 12,
"a_dim": 3,
"has_disturbance": True,
"dist_center_x": 10.0 * L0, # 200
"dist_radius": 1.0 * L0, # 20
"pinball_front_x": 30.0 * L0, # 600
"pinball_rear_x": 31.3 * L0, # 626
"pinball_y_span": 0.75 * L0, # 15
"sensor_x": 40.0 * L0, # 800
"sensor_y_span": 2.0 * L0, # 40
"sensor_radius": L0 / 4, # 5
"pinball_radius": L0 / 2, # 10
"sample_interval": 800,
"action_scale": 8.0,
"action_bias": np.array([0.0, -4.0, 4.0], dtype=np.float32),
"u0": U0,
"fifo_len": 150,
"conv_len": 30,
"max_steps": 500,
"n_objects": 7,
"obs_slice": (2, 14), # skip dist_cyl forces
"force_norm_formula": "6*max",
"sens_norm_factor": 5,
"target_type": "periodic",
"warmup_steps_dist": int(4 * NX / U0),
"warmup_steps_pinball": int(4 * NX / U0),
}
# Also define the karman scenes at other Re for completeness
for re_code, name in [(50, "re50"), (200, "re200"), (400, "re400")]:
key = f"karman_cloak_{name}"
SCENES[key] = dict(SCENES["karman_cloak_re100"])
SCENES[key].update({
"model": f"d1a3o12_{name}",
"model_subdir": "old",
"re_code": re_code,
"nu": nu_from_re(re_code),
"scene_id": f"karman_cloak_{name}",
})
# -- Illusion (three target diameters) ---------------------------------------
# NOTE: The positions below (sensors at 40*L0, pinball at 30/31.3*L0) are the
# "unified" inference geometry used by CCD_analysis. The actual training
# geometry (legacy_env_imit.py) used sensors at 30*L0 and pinball at 19/20.3*L0.
# phase3_reproduce.py and legacy_test scripts use the TRAINING positions.
def _illusion_base() -> Dict[str, Any]:
return {
"scene_id": "illusion",
"re_code": 100,
"nu": nu_from_re(100),
"s_dim": 14,
"a_dim": 3,
"has_disturbance": False,
"target_center_x": 31.0 * L0, # 620 (inference position)
"pinball_front_x": 30.0 * L0, # 600 (standard inference pinball)
"pinball_rear_x": 31.3 * L0, # 626
"pinball_y_span": 0.75 * L0,
"sensor_x": 40.0 * L0, # 800 (inference, not training!)
"sensor_y_span": 2.0 * L0,
"sensor_radius": L0 / 4,
"pinball_radius": L0 / 2,
"action_scale": 8.0,
"action_bias": np.array([0.0, -2.0, 2.0], dtype=np.float32),
"u0": U0,
"fifo_len": 150,
"conv_len": 36, # illusion uses CONV_LEN=36
"max_steps": 500,
"n_objects": 6,
"obs_slice": (0, 12),
"force_norm_formula": "6*max",
"sens_norm_factor": 5,
"target_type": "harmonics",
"n_harmonics": 5,
"warmup_steps_target": int(4 * NX / U0),
"warmup_steps_pinball": int(4 * NX / U0),
}
illusion_entries = [
("illusion_075L", {
"model": "d1a3o14_250525_imit_075L_2U_400S",
"model_subdir": "250525",
"target_diameter": 0.75 * L0, # 15
"sample_interval": 400,
}),
("illusion_1L", {
"model": "d1a3o14_250525_imit_1L_2U_600S",
"model_subdir": "250525",
"target_diameter": 1.0 * L0, # 20
"sample_interval": 600,
}),
("illusion_15L", {
"model": "d1a3o14_250525_imit_15L_2U",
"model_subdir": "250525",
"target_diameter": 1.5 * L0, # 30
"sample_interval": 800,
}),
]
for key, overrides in illusion_entries:
base = _illusion_base()
base.update(overrides)
SCENES[key] = base
# -- Vortex (Lamb dipole and Taylor monopole) --------------------------------
def _vortex_base() -> Dict[str, Any]:
return {
"scene_id": "vortex",
"re_code": 100,
"nu": nu_from_re(100),
"s_dim": 12,
"a_dim": 3,
"has_disturbance": False,
"pinball_front_x": 30.0 * L0,
"pinball_rear_x": 31.3 * L0,
"pinball_y_span": 0.75 * L0,
"sensor_x": 40.0 * L0,
"sensor_y_span": 2.0 * L0,
"sensor_radius": L0 / 4,
"pinball_radius": L0 / 2,
"sample_interval": 800,
"action_scale": 4.0, # NOTE: scale=4 not 8
"action_bias": np.array([0.0, -4.0, 4.0], dtype=np.float32),
"u0": U0,
"fifo_len": 150,
"conv_len": 30,
"max_steps": 150, # transient!
"n_objects": 6,
"obs_slice": (0, 12),
"force_norm_formula": "6*max",
"sens_norm_factor": 5,
"target_type": "transient",
"vortex_center_x": 10.0 * L0, # target phase
"vortex_pinball_center_x": 15.0 * L0, # pinball phase
"vortex_radius": 2.0 * L0,
}
vortex_entries = [
("vortex_lamb", {
"model": "vortex_lamb",
"model_subdir": "old",
"vortex_type": "lamb",
"vortex_strength": 0.5 * U0,
}),
("vortex_taylor", {
"model": "vortex_taylor",
"model_subdir": "old",
"vortex_type": "taylor",
"vortex_strength": 0.03 * U0,
}),
]
for key, overrides in vortex_entries:
base = _vortex_base()
base.update(overrides)
SCENES[key] = base
def get_scene(name: str) -> Dict[str, Any]:
"""Look up a scene by name. Raises KeyError if not found."""
if name not in SCENES:
available = ", ".join(sorted(SCENES.keys()))
raise KeyError(f"Unknown scene '{name}'. Available: {available}")
return dict(SCENES[name]) # return a copy