# legacy_test/test_karman_cloak_crossre.py """Karman Cloak Cross-Re — legacy test (re50, re200, re400). Same procedure as test_karman_cloak_re100.py but for alternative Reynolds numbers. Each Re uses its own PPO model and SR_analysis reference data. Usage: conda run -n pycuda_3_10 python test_karman_cloak_crossre.py --device 0 """ import argparse import json import os import sys import time from collections import deque import numpy as np _REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..")) _SRC = os.path.join(_REPO, "src") _DRL = os.path.join(_SRC, "drl_pinball") for p in [_REPO, _SRC, _DRL]: if p not in sys.path: sys.path.insert(0, p) from legacy_test.core.legacy_env_builder import ( # noqa: E402 build_karman_cloak, FIFO_LEN, CONV_LEN, U0, DATA_TYPE, ) from legacy_test.core.model_loader import load_model # noqa: E402 from legacy_test.core.comparator import compare_scene # noqa: E402 from legacy_test.core.io_helpers import save_signals, save_target, save_norm # noqa: E402 SAMPLE_INTERVAL = 800 ACTION_SCALE = 8.0 ACTION_BIAS = (0.0, -4.0, 4.0) NUM_STEPS = 200 OUT_BASE = os.path.join(os.path.dirname(__file__), "output") def log(msg): print(f"[{time.strftime('%H:%M:%S')}] {msg}", flush=True) def run_crossre(device_id: int, re_code: float) -> dict: label = f"karman_re{int(re_code)}" log(f"=== {label}: Legacy Test ===") model_name = f"d1a3o12_re{int(re_code)}" ref_dir = os.path.join(_SRC, "SR_analysis", "data", "karman", label) out_dir = os.path.join(OUT_BASE, label) os.makedirs(out_dir, exist_ok=True) data = build_karman_cloak(device_id=device_id, re_code=re_code, action_bias=ACTION_BIAS, action_scale=ACTION_SCALE) ff = data["flow_field"] target_states = data["target_states"] norm = data["norm"] n_obj = norm.get("n_obj_total", 7) f_nf = float(norm["force_norm_fact"]) s_dev = np.array(norm["sens_deviation"], dtype=np.float32) s_nf = np.array(norm["sens_norm_fact"], dtype=np.float32) save_target(out_dir, target_states); save_norm(out_dir, norm) model = load_model(model_name) log(f" Model: {model_name}") # Restore + bias FIFO ff.restore_ddf(); ff.apply_ddf() bias_arr = np.zeros(n_obj, dtype=DATA_TYPE) bias_arr[4] = float(ACTION_BIAS[0] * U0) bias_arr[5] = float(ACTION_BIAS[1] * U0) bias_arr[6] = float(ACTION_BIAS[2] * U0) fifo = deque(maxlen=FIFO_LEN) for _ in range(FIFO_LEN): ff.run(SAMPLE_INTERVAL, bias_arr) fifo.append(ff.obs.copy()[2:14]) # DRL inference sig_s = np.zeros((NUM_STEPS, 6), dtype=np.float32) sig_f = np.zeros((NUM_STEPS, 6), dtype=np.float32) sig_a = np.zeros((NUM_STEPS, 3), dtype=np.float32) raw = ff.obs.copy()[2:14] forces_norm = raw[6:12] / f_nf sens_norm = (raw[0:6] - s_dev) / s_nf obs = np.clip(np.hstack([forces_norm, sens_norm]), -1.0, 1.0).astype(np.float32) for step in range(NUM_STEPS): action, _ = model.predict(obs, deterministic=True) action = action.astype(np.float32).flatten() sig_a[step] = action.copy() action_arr = np.zeros(n_obj, dtype=DATA_TYPE) action_arr[4:] = (action * ACTION_SCALE + np.array(ACTION_BIAS, dtype=np.float32)) * U0 ff.context.push() try: ff.run(SAMPLE_INTERVAL, action_arr) finally: ff.context.pop() raw = ff.obs.copy()[2:14] fifo.append(raw) sig_s[step] = raw[0:6] sig_f[step] = raw[6:12] forces_norm = raw[6:12] / f_nf sens_norm = (raw[0:6] - s_dev) / s_nf obs = np.clip(np.hstack([forces_norm, sens_norm]), -1.0, 1.0).astype(np.float32) save_signals(out_dir, sig_s, sig_f, sig_a) np.savez_compressed(os.path.join(out_dir, "controlled.npz"), sensors=sig_s, forces=sig_f, actions=sig_a, rewards=np.zeros(NUM_STEPS, dtype=np.float32)) log(" Comparing against reference...") result = compare_scene(ref_dir, sig_s, sig_f, sig_a, conv_len=CONV_LEN, label=label) with open(os.path.join(out_dir, "result.json"), "w") as f: json.dump(result, f, indent=2) log(f" {'PASS' if result['passed'] else 'FAIL'}") del ff return result def main(): ap = argparse.ArgumentParser(); ap.add_argument("--device", type=int, default=0) ap.add_argument("--re", type=str, default="50,200,400", help="Comma-separated Re values") args = ap.parse_args() results = {} for re_str in args.re.split(","): re_val = float(re_str.strip()) results[f"re{int(re_val)}"] = run_crossre(args.device, re_val) log("\n=== Cross-Re Summary ===") for name, r in results.items(): log(f" {name}: DTW={r['dtw_sim']:.4f}, act_corr={[f'{c:.3f}' for c in r['action_corr']]} -> {'PASS' if r['passed'] else 'FAIL'}") if __name__ == "__main__": main()