CCD analysis: correction-field framework complete (Round 6)
- Shift analysis from raw-field q_ctl to correction-field dq_ctl = q_ctl - q_blk - Force/action/signature CCD for illusion 0.75L, 1.0L, 1.5L - Zone-restricted CCD (near_body/body_wake/sensor_zone) with spatial separation evidence - 1.5L identified as special mechanism (low action coupling, phase drift) - Karman reference data collected (q_in, q_blk) - Snapshot POD speedup (96x96 instead of 1310720x96) - Comprehensive report: docs/ccd_correction_field_report.md (412 lines) - Handover document: docs/ccd_handover.md Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
"""1L Illusion DRL inference (2U=0.02).
|
||||
"""Illusion DRL inference (all S_DIM=14, regardless of model name).
|
||||
|
||||
All illusion models use 14-D observation space
|
||||
(sensors(6) + forces(6) + target_cd(1) + target_cl(1)),
|
||||
with target forces reconstructed from harmonics.
|
||||
|
||||
Usage:
|
||||
conda run -n pycuda_3_10 python scripts/collect_illusion.py --device 2 --steps 200
|
||||
conda run -n pycuda_3_10 python scripts/collect_illusion.py --device 2 --steps 500
|
||||
|
||||
Output: data/illusion/illusion_1L/
|
||||
Output: data/illusion/{scene_name}/
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -19,13 +23,13 @@ import numpy as np
|
||||
_REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
|
||||
if _REPO not in sys.path:
|
||||
sys.path.insert(0, _REPO)
|
||||
_ANALYSIS = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if _ANALYSIS not in sys.path:
|
||||
sys.path.insert(0, _ANALYSIS)
|
||||
_SRC = os.path.join(_REPO, "src")
|
||||
if _SRC not in sys.path:
|
||||
sys.path.insert(0, _SRC)
|
||||
|
||||
from LegacyCelerisLab import FlowField
|
||||
|
||||
from CCD_analysis.configs import get_scene, data_dir_for_scene, model_path_for_scene, LEGACY_CFG_DIR
|
||||
from CCD_analysis.configs import get_scene, get_scene_list, data_dir_for_scene, model_path_for_scene, LEGACY_CFG_DIR
|
||||
from CCD_analysis.utils.cfd_interface import (
|
||||
load_legacy_configs, save_vorticity_png, vorticity_from_ddf,
|
||||
load_ppo_model, scale_action, get_velocity_field,
|
||||
@@ -60,7 +64,9 @@ def run_single(scene_name: str, device_id: int, n_steps: int) -> dict:
|
||||
# === Target recording (separate FlowField) ===
|
||||
print("=== Target recording ===")
|
||||
ff_tgt = FlowField(field_cfg, cuda_cfg, device_id=device_id)
|
||||
ff_tgt.add_cylinder((20.0 * L0, CENTER_Y, 0.0), 1.0 * L0)
|
||||
tgt_radius = cfg["target_diameter"] * L0
|
||||
ff_tgt.add_cylinder((20.0 * L0, CENTER_Y, 0.0), tgt_radius)
|
||||
print(f" target cylinder: diameter={cfg['target_diameter']}L, radius={tgt_radius}", flush=True)
|
||||
for y_off in [2.0, 0.0, -2.0]:
|
||||
ff_tgt.add_sensor((30.0 * L0, CENTER_Y + y_off * L0, 0.0), L0 / 4.0)
|
||||
n_tgt = 4
|
||||
@@ -114,7 +120,9 @@ def run_single(scene_name: str, device_id: int, n_steps: int) -> dict:
|
||||
json.dump(norm, f, indent=2)
|
||||
print(f" force_norm_fact={force_norm_fact:.6f}")
|
||||
|
||||
# Bias FIFO (matches legacy_env_imit: [0,0,0,0,-1*U0,1*U0])
|
||||
# Preset-action FIFO init (matches legacy_env_imit: [0,0,0,0,-1*U0,1*U0])
|
||||
# NOTE: this is NOT the same as action_bias([0,-2,2]). action_bias controls DRL
|
||||
# action scaling; preset_action is a fixed Omega array used to warm up the FIFO.
|
||||
ff.apply_ddf()
|
||||
bias = np.zeros(n_env, dtype=DATA_TYPE)
|
||||
bias[4] = -1.0 * u0
|
||||
@@ -124,6 +132,12 @@ def run_single(scene_name: str, device_id: int, n_steps: int) -> dict:
|
||||
ff.run(si, bias)
|
||||
fifo.append(ff.obs.copy()[0:12])
|
||||
save_states_arr = np.array(fifo, dtype=DATA_TYPE)
|
||||
|
||||
# Save DDF+FIFO checkpoint for replay (state right after warmup, before step 0)
|
||||
ff.get_ddf()
|
||||
np.save(os.path.join(out_dir, "ddf_checkpoint.npy"), ff.ddf)
|
||||
np.save(os.path.join(out_dir, "fifo_checkpoint.npy"), save_states_arr)
|
||||
|
||||
ff.apply_ddf()
|
||||
|
||||
# === PPO inference ===
|
||||
@@ -157,13 +171,18 @@ def run_single(scene_name: str, device_id: int, n_steps: int) -> dict:
|
||||
sens_c.append(obs_slice[0:6])
|
||||
forc_c.append(obs_slice[6:12])
|
||||
|
||||
# 14-dim obs
|
||||
# obs dimension depends on model type:
|
||||
# d1a3o12_* = 12-dim (forces + sens only)
|
||||
# d1a3o14_* = 14-dim (forces + sens + target_cd + target_cl)
|
||||
forces_norm = obs_slice[6:12] / force_norm_fact
|
||||
sens_norm = (obs_slice[0:6] - sens_deviation) / sens_norm_fact
|
||||
target_recon = gen_target_states_at(step, target_harmonics)
|
||||
t_cd_n = float(target_recon[0]) / force_norm_fact
|
||||
t_cl_n = float(target_recon[1]) / force_norm_fact
|
||||
obs = np.clip(np.hstack([forces_norm, sens_norm, t_cd_n, t_cl_n]), -1.0, 1.0).astype(np.float32)
|
||||
if s_dim == 14:
|
||||
target_recon = gen_target_states_at(step, target_harmonics)
|
||||
t_cd_n = float(target_recon[0]) / force_norm_fact
|
||||
t_cl_n = float(target_recon[1]) / force_norm_fact
|
||||
obs = np.clip(np.hstack([forces_norm, sens_norm, t_cd_n, t_cl_n]), -1.0, 1.0).astype(np.float32)
|
||||
else:
|
||||
obs = np.clip(np.hstack([forces_norm, sens_norm]), -1.0, 1.0).astype(np.float32)
|
||||
|
||||
# Reward
|
||||
sarr = np.array(fifo, dtype=np.float32)
|
||||
@@ -220,12 +239,25 @@ def run_single(scene_name: str, device_id: int, n_steps: int) -> dict:
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--scene", type=str, default="illusion_1.0L",
|
||||
help="Scene name (illusion_0.75L, illusion_1.0L, illusion_1.5L)")
|
||||
ap.add_argument("--diameter", type=float, default=None,
|
||||
help="Diameter shortcut (0.75, 1.0, 1.5)")
|
||||
ap.add_argument("--device", type=int, default=2)
|
||||
ap.add_argument("--steps", type=int, default=200)
|
||||
args = ap.parse_args()
|
||||
|
||||
if args.diameter is not None:
|
||||
scene_name = f"illusion_{args.diameter}L"
|
||||
else:
|
||||
scene_name = args.scene
|
||||
|
||||
if scene_name not in get_scene_list("illusion"):
|
||||
print(f"Unknown scene: {scene_name}. Available: {get_scene_list('illusion')}")
|
||||
return 1
|
||||
|
||||
t0 = time.time()
|
||||
r = run_single("illusion_1L", args.device, args.steps)
|
||||
r = run_single(scene_name, args.device, args.steps)
|
||||
print(f"Done in {time.time()-t0:.1f}s: sim={r['similarity']:.4f}")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user