161 lines
4.6 KiB
Python
161 lines
4.6 KiB
Python
"""Unified scene configuration for CCD_analysis.
|
|
|
|
All scene metadata in one place. Each scene dict contains all parameters
|
|
needed for data collection, resampling, POD, and CCD.
|
|
|
|
Re convention:
|
|
- "re_code" uses reference length 2*D (matching model file naming).
|
|
- Re_D = re_code / 2 is the true physical Reynolds number.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
# -- Root paths ---------------------------------------------------------------
|
|
_PROJ = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
MODEL_DIR = os.path.join(_PROJ, "..", "..", "models")
|
|
LEGACY_CFG_DIR = os.path.join(os.path.dirname(__file__), "configs")
|
|
DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
|
|
|
|
# -- Physics constants -------------------------------------------------------
|
|
U0 = 0.01
|
|
D_CYL = 20.0
|
|
D_REF = 40.0
|
|
L0 = 20.0
|
|
NX = 1280
|
|
NY = 512
|
|
CENTER_Y = (NY - 1) / 2.0
|
|
FIFO_LEN = 150
|
|
CONV_LEN = 30
|
|
|
|
|
|
def nu_from_re(re_code: float, u0: float = U0) -> float:
|
|
"""Viscosity from code Reynolds number (reference length = 2*D)."""
|
|
return u0 * D_REF / re_code
|
|
|
|
|
|
# -- Scene definitions -------------------------------------------------------
|
|
SCENES: Dict[str, Any] = {}
|
|
|
|
# -- Pure Pinball (uncontrolled baseline, 6 objects, no disturbance) ---------
|
|
SCENES["pinball"] = {
|
|
"scene_id": "pinball",
|
|
"re_code": 100,
|
|
"has_disturbance": False,
|
|
"sample_interval": 800,
|
|
"source": "open_loop",
|
|
"model_name": None,
|
|
"n_objects_env": 6,
|
|
"obs_slice": (0, 12),
|
|
"sensor_x": 40.0,
|
|
"pinball_front_x": 30.0,
|
|
"pinball_rear_x": 31.3,
|
|
"target_type": "periodic",
|
|
"s_dim": 12,
|
|
"u0": U0,
|
|
"nu": nu_from_re(100),
|
|
}
|
|
|
|
# -- Steady Cloak (open-loop constant rotation, 6 objects) --------------------
|
|
SCENES["steady_cloak"] = {
|
|
"scene_id": "steady_cloak",
|
|
"re_code": 100,
|
|
"has_disturbance": False,
|
|
"sample_interval": 800,
|
|
"source": "open_loop",
|
|
"model_name": None,
|
|
"n_objects_env": 6,
|
|
"obs_slice": (0, 12),
|
|
"sensor_x": 40.0,
|
|
"pinball_front_x": 30.0,
|
|
"pinball_rear_x": 31.3,
|
|
"target_type": "steady",
|
|
"s_dim": 12,
|
|
"u0": U0,
|
|
"nu": nu_from_re(100),
|
|
"omega_front": 0.0,
|
|
"omega_rear_scale": 5.1,
|
|
}
|
|
|
|
# -- Karman Cloak (PPO, 7 objects, disturbance cylinder) ---------------------
|
|
SCENES["karman_re100"] = {
|
|
"scene_id": "karman",
|
|
"re_code": 100,
|
|
"has_disturbance": True,
|
|
"sample_interval": 800,
|
|
"action_scale": 8.0,
|
|
"action_bias": (0.0, -4.0, 4.0),
|
|
"source": "PPO_inference",
|
|
"model_name": "d1a3o12_re100",
|
|
"model_subdir": "old",
|
|
"n_objects_env": 7,
|
|
"obs_slice": (2, 14),
|
|
"sensor_x": 40.0,
|
|
"pinball_front_x": 30.0,
|
|
"pinball_rear_x": 31.3,
|
|
"target_type": "periodic",
|
|
"s_dim": 12,
|
|
"u0": U0,
|
|
"nu": nu_from_re(100),
|
|
}
|
|
|
|
# -- 1L Illusion (PPO, 6 objects, 2U=0.02) ----------------------------------
|
|
SCENES["illusion_1L"] = {
|
|
"scene_id": "illusion",
|
|
"re_code": 100,
|
|
"target_diameter": 1.0,
|
|
"has_disturbance": False,
|
|
"sample_interval": 600,
|
|
"action_scale": 8.0,
|
|
"action_bias": (0.0, -2.0, 2.0),
|
|
"source": "PPO_inference",
|
|
"model_name": "d1a3o14_250525_imit_1L_2U_600S",
|
|
"model_subdir": "250525",
|
|
"n_objects_env": 6,
|
|
"obs_slice": (0, 12),
|
|
"sensor_x": 30.0,
|
|
"pinball_front_x": 19.0,
|
|
"pinball_rear_x": 20.3,
|
|
"target_type": "periodic",
|
|
"s_dim": 14,
|
|
"u0": 0.02,
|
|
"nu": nu_from_re(100, u0=0.02),
|
|
}
|
|
|
|
|
|
# -- Utility helpers ---------------------------------------------------------
|
|
|
|
def get_scene(name: str) -> dict:
|
|
"""Return scene config dict by name. Raises KeyError if not found."""
|
|
if name not in SCENES:
|
|
raise KeyError(f"Unknown scene: {name}. Available: {list(SCENES.keys())}")
|
|
return dict(SCENES[name])
|
|
|
|
|
|
def get_scene_list(scene_id: Optional[str] = None) -> List[str]:
|
|
"""Return list of scene names, optionally filtered by scene_id."""
|
|
if scene_id is None:
|
|
return list(SCENES.keys())
|
|
return [k for k, v in SCENES.items() if v["scene_id"] == scene_id]
|
|
|
|
|
|
def model_path_for_scene(scene_name: str) -> Optional[str]:
|
|
"""Return absolute path to PPO model .zip file, or None."""
|
|
cfg = get_scene(scene_name)
|
|
mn = cfg.get("model_name")
|
|
if mn is None:
|
|
return None
|
|
subdir = cfg.get("model_subdir", "old")
|
|
p = os.path.join(MODEL_DIR, subdir, f"{mn}.zip")
|
|
return p if os.path.isfile(p) else None
|
|
|
|
|
|
def data_dir_for_scene(scene_name: str) -> str:
|
|
"""Return the data directory for a scene, creating it if needed."""
|
|
cfg = get_scene(scene_name)
|
|
scene_id = cfg["scene_id"]
|
|
d = os.path.join(DATA_DIR, scene_id, scene_name)
|
|
os.makedirs(d, exist_ok=True)
|
|
return d
|