diff --git a/src/SR_analysis/data/v5/ill_075L_sc/config.json b/src/SR_analysis/data/v5/ill_075L/config.json similarity index 90% rename from src/SR_analysis/data/v5/ill_075L_sc/config.json rename to src/SR_analysis/data/v5/ill_075L/config.json index e21d876..0b5f59a 100644 --- a/src/SR_analysis/data/v5/ill_075L_sc/config.json +++ b/src/SR_analysis/data/v5/ill_075L/config.json @@ -1,5 +1,5 @@ { - "case_name": "ill_075L_sc", + "case_name": "ill_075L", "scene_type": "illusion", "seed": 43, "SI": 400, diff --git a/src/SR_analysis/data/v5/ill_075L_sc/norm.json b/src/SR_analysis/data/v5/ill_075L/norm.json similarity index 100% rename from src/SR_analysis/data/v5/ill_075L_sc/norm.json rename to src/SR_analysis/data/v5/ill_075L/norm.json diff --git a/src/SR_analysis/data/v5/ill_075L_sc/target_harmonics.json b/src/SR_analysis/data/v5/ill_075L/target_harmonics.json similarity index 100% rename from src/SR_analysis/data/v5/ill_075L_sc/target_harmonics.json rename to src/SR_analysis/data/v5/ill_075L/target_harmonics.json diff --git a/src/SR_analysis/data/v5/ill_15L_sc/config.json b/src/SR_analysis/data/v5/ill_15L/config.json similarity index 90% rename from src/SR_analysis/data/v5/ill_15L_sc/config.json rename to src/SR_analysis/data/v5/ill_15L/config.json index 4dbe1b5..8738512 100644 --- a/src/SR_analysis/data/v5/ill_15L_sc/config.json +++ b/src/SR_analysis/data/v5/ill_15L/config.json @@ -1,5 +1,5 @@ { - "case_name": "ill_15L_sc", + "case_name": "ill_15L", "scene_type": "illusion", "seed": 43, "SI": 800, diff --git a/src/SR_analysis/data/v5/ill_15L_sc/norm.json b/src/SR_analysis/data/v5/ill_15L/norm.json similarity index 100% rename from src/SR_analysis/data/v5/ill_15L_sc/norm.json rename to src/SR_analysis/data/v5/ill_15L/norm.json diff --git a/src/SR_analysis/data/v5/ill_15L_sc/target_harmonics.json b/src/SR_analysis/data/v5/ill_15L/target_harmonics.json similarity index 100% rename from src/SR_analysis/data/v5/ill_15L_sc/target_harmonics.json rename to src/SR_analysis/data/v5/ill_15L/target_harmonics.json diff --git a/src/SR_analysis/data/v5/ill_1L_sc/config.json b/src/SR_analysis/data/v5/ill_1L/config.json similarity index 91% rename from src/SR_analysis/data/v5/ill_1L_sc/config.json rename to src/SR_analysis/data/v5/ill_1L/config.json index b909c3d..36bbccf 100644 --- a/src/SR_analysis/data/v5/ill_1L_sc/config.json +++ b/src/SR_analysis/data/v5/ill_1L/config.json @@ -1,5 +1,5 @@ { - "case_name": "ill_1L_sc", + "case_name": "ill_1L", "scene_type": "illusion", "seed": 43, "SI": 600, diff --git a/src/SR_analysis/data/v5/ill_1L_sc/norm.json b/src/SR_analysis/data/v5/ill_1L/norm.json similarity index 100% rename from src/SR_analysis/data/v5/ill_1L_sc/norm.json rename to src/SR_analysis/data/v5/ill_1L/norm.json diff --git a/src/SR_analysis/data/v5/ill_1L_sc/target_harmonics.json b/src/SR_analysis/data/v5/ill_1L/target_harmonics.json similarity index 100% rename from src/SR_analysis/data/v5/ill_1L_sc/target_harmonics.json rename to src/SR_analysis/data/v5/ill_1L/target_harmonics.json diff --git a/src/SR_analysis/data/v5/ill_2L_sc/config.json b/src/SR_analysis/data/v5/ill_2L/config.json similarity index 91% rename from src/SR_analysis/data/v5/ill_2L_sc/config.json rename to src/SR_analysis/data/v5/ill_2L/config.json index 2f452d7..8b221c6 100644 --- a/src/SR_analysis/data/v5/ill_2L_sc/config.json +++ b/src/SR_analysis/data/v5/ill_2L/config.json @@ -1,5 +1,5 @@ { - "case_name": "ill_2L_sc", + "case_name": "ill_2L", "scene_type": "illusion", "seed": 43, "SI": 800, diff --git a/src/SR_analysis/data/v5/ill_2L_sc/norm.json b/src/SR_analysis/data/v5/ill_2L/norm.json similarity index 100% rename from src/SR_analysis/data/v5/ill_2L_sc/norm.json rename to src/SR_analysis/data/v5/ill_2L/norm.json diff --git a/src/SR_analysis/data/v5/ill_2L_sc/target_harmonics.json b/src/SR_analysis/data/v5/ill_2L/target_harmonics.json similarity index 100% rename from src/SR_analysis/data/v5/ill_2L_sc/target_harmonics.json rename to src/SR_analysis/data/v5/ill_2L/target_harmonics.json diff --git a/src/SR_analysis/data/v5/kar_d075_sc/config.json b/src/SR_analysis/data/v5/kar_d075/config.json similarity index 89% rename from src/SR_analysis/data/v5/kar_d075_sc/config.json rename to src/SR_analysis/data/v5/kar_d075/config.json index c515169..2fc872d 100644 --- a/src/SR_analysis/data/v5/kar_d075_sc/config.json +++ b/src/SR_analysis/data/v5/kar_d075/config.json @@ -1,5 +1,5 @@ { - "case_name": "kar_d075_sc", + "case_name": "kar_d075", "scene_type": "karman", "seed": 44, "SI": 800, diff --git a/src/SR_analysis/data/v5/kar_d075_sc/norm.json b/src/SR_analysis/data/v5/kar_d075/norm.json similarity index 100% rename from src/SR_analysis/data/v5/kar_d075_sc/norm.json rename to src/SR_analysis/data/v5/kar_d075/norm.json diff --git a/src/SR_analysis/data/v5/kar_d15_sc/config.json b/src/SR_analysis/data/v5/kar_d15/config.json similarity index 90% rename from src/SR_analysis/data/v5/kar_d15_sc/config.json rename to src/SR_analysis/data/v5/kar_d15/config.json index c43b911..4a40836 100644 --- a/src/SR_analysis/data/v5/kar_d15_sc/config.json +++ b/src/SR_analysis/data/v5/kar_d15/config.json @@ -1,5 +1,5 @@ { - "case_name": "kar_d15_sc", + "case_name": "kar_d15", "scene_type": "karman", "seed": 45, "SI": 800, diff --git a/src/SR_analysis/data/v5/kar_d15_sc/norm.json b/src/SR_analysis/data/v5/kar_d15/norm.json similarity index 100% rename from src/SR_analysis/data/v5/kar_d15_sc/norm.json rename to src/SR_analysis/data/v5/kar_d15/norm.json diff --git a/src/SR_analysis/data/v5/kar_d2_sc/config.json b/src/SR_analysis/data/v5/kar_d2/config.json similarity index 90% rename from src/SR_analysis/data/v5/kar_d2_sc/config.json rename to src/SR_analysis/data/v5/kar_d2/config.json index ec7e239..419b694 100644 --- a/src/SR_analysis/data/v5/kar_d2_sc/config.json +++ b/src/SR_analysis/data/v5/kar_d2/config.json @@ -1,5 +1,5 @@ { - "case_name": "kar_d2_sc", + "case_name": "kar_d2", "scene_type": "karman", "seed": 45, "SI": 800, diff --git a/src/SR_analysis/data/v5/kar_d2_sc/norm.json b/src/SR_analysis/data/v5/kar_d2/norm.json similarity index 100% rename from src/SR_analysis/data/v5/kar_d2_sc/norm.json rename to src/SR_analysis/data/v5/kar_d2/norm.json diff --git a/src/SR_analysis/data/v5/kar_re100_sc/config.json b/src/SR_analysis/data/v5/kar_re100/config.json similarity index 89% rename from src/SR_analysis/data/v5/kar_re100_sc/config.json rename to src/SR_analysis/data/v5/kar_re100/config.json index 75d9feb..1a74c55 100644 --- a/src/SR_analysis/data/v5/kar_re100_sc/config.json +++ b/src/SR_analysis/data/v5/kar_re100/config.json @@ -1,5 +1,5 @@ { - "case_name": "kar_re100_sc", + "case_name": "kar_re100", "scene_type": "karman", "seed": 45, "SI": 800, diff --git a/src/SR_analysis/data/v5/kar_re100_sc/norm.json b/src/SR_analysis/data/v5/kar_re100/norm.json similarity index 100% rename from src/SR_analysis/data/v5/kar_re100_sc/norm.json rename to src/SR_analysis/data/v5/kar_re100/norm.json diff --git a/src/SR_analysis/experiments/v5/bridge_v5.py b/src/SR_analysis/experiments/v5/bridge_v5.py index b1eac6c..3f71ac8 100644 --- a/src/SR_analysis/experiments/v5/bridge_v5.py +++ b/src/SR_analysis/experiments/v5/bridge_v5.py @@ -9,19 +9,19 @@ For each effective V5 case: 5. Creates SR_analysis/data/v5/{case}/ with controlled.npz, target.npz, etc. Effective cases (with best seed and DTW): - - kar_re100_sc (seed45, dtw=0.918) — baseline - - kar_d075_sc (seed44, dtw=0.911) — vardist - - kar_d15_sc (seed45, dtw=0.892) — vardist - - kar_d2_sc (seed45, dtw=0.816) — vardist, borderline - - ill_075L_sc (seed43, dtw=0.807) - - ill_1L_sc (seed43, dtw=0.900) - - ill_15L_sc (seed43, dtw=0.906) - - ill_2L_sc (seed43, dtw=0.800) + - kar_re100 (seed45, dtw=0.918) — baseline + - kar_d075 (seed44, dtw=0.911) — vardist + - kar_d15 (seed45, dtw=0.892) — vardist + - kar_d2 (seed45, dtw=0.816) — vardist, borderline + - ill_075L (seed43, dtw=0.807) + - ill_1L (seed43, dtw=0.900) + - ill_15L (seed43, dtw=0.906) + - ill_2L (seed43, dtw=0.800) Usage: PYTHONPATH=src conda run -n pycuda_3_10 python -m SR_analysis.experiments.v5.bridge_v5 PYTHONPATH=src conda run -n pycuda_3_10 python -m SR_analysis.experiments.v5.bridge_v5 --include-failed - PYTHONPATH=src conda run -n pycuda_3_10 python -m SR_analysis.experiments.v5.bridge_v5 --cases kar_re100_sc,ill_1L_sc + PYTHONPATH=src conda run -n pycuda_3_10 python -m SR_analysis.experiments.v5.bridge_v5 --cases kar_re100,ill_1L """ from __future__ import annotations @@ -32,6 +32,8 @@ from pathlib import Path import numpy as np +from drl_pinball.case_registry import CASE_REGISTRY + _SR_ROOT = Path(__file__).resolve().parents[2] _REPO = _SR_ROOT.parents[1] _EVAL_OUT = _REPO / "src" / "drl_pinball" / "eval" / "output" / "train" @@ -42,71 +44,44 @@ _SR_DATA = _SR_ROOT / "data" / "v5" L0 = 20.0 U0 = 0.01 -# Best seeds from eval metrics.json -EFFECTIVE_CASES = { - "kar_re100_sc": {"seed": 45, "dtw": 0.918, "scene_type": "karman", - "cal_name": "kar_re100", - "si": 800, "action_scale": 12.0, "action_bias": (0.0, 0.0, 0.0), - "s_dim": 12, "conv_len": 30, - "obs_slice": (2, 14), "n_objects": 7}, - "kar_d075_sc": {"seed": 44, "dtw": 0.911, "scene_type": "karman", - "cal_name": "kar_d075_sc", - "si": 800, "action_scale": 12.0, "action_bias": (0.0, 0.0, 0.0), - "s_dim": 12, "conv_len": 30, - "obs_slice": (2, 14), "n_objects": 7}, - "kar_d15_sc": {"seed": 45, "dtw": 0.892, "scene_type": "karman", - "cal_name": "kar_d15_sc", - "si": 800, "action_scale": 12.0, "action_bias": (0.0, 0.0, 0.0), - "s_dim": 12, "conv_len": 30, - "obs_slice": (2, 14), "n_objects": 7}, - "kar_d2_sc": {"seed": 45, "dtw": 0.816, "scene_type": "karman", - "cal_name": "kar_d2_sc", - "si": 800, "action_scale": 12.0, "action_bias": (0.0, 0.0, 0.0), - "s_dim": 12, "conv_len": 30, - "obs_slice": (2, 14), "n_objects": 7}, - "ill_075L_sc": {"seed": 43, "dtw": 0.807, "scene_type": "illusion", - "cal_name": "ill_075L", - "si": 400, "action_scale": 12.0, "action_bias": (0.0, 0.0, 0.0), - "s_dim": 14, "conv_len": 36, - "target_diam": 0.75, - "obs_slice": (0, 12), "n_objects": 6}, - "ill_1L_sc": {"seed": 43, "dtw": 0.900, "scene_type": "illusion", - "cal_name": "ill_1L", - "si": 600, "action_scale": 12.0, "action_bias": (0.0, 0.0, 0.0), - "s_dim": 14, "conv_len": 36, - "target_diam": 1.0, - "obs_slice": (0, 12), "n_objects": 6}, - "ill_15L_sc": {"seed": 43, "dtw": 0.906, "scene_type": "illusion", - "cal_name": "ill_15L", - "si": 800, "action_scale": 12.0, "action_bias": (0.0, 0.0, 0.0), - "s_dim": 14, "conv_len": 36, - "target_diam": 1.5, - "obs_slice": (0, 12), "n_objects": 6}, - "ill_2L_sc": {"seed": 43, "dtw": 0.800, "scene_type": "illusion", - "cal_name": "ill_2L", - "si": 800, "action_scale": 12.0, "action_bias": (0.0, 0.0, 0.0), - "s_dim": 14, "conv_len": 36, - "target_diam": 2.0, - "obs_slice": (0, 12), "n_objects": 6}, +# Evaluation attributes not supplied by the canonical registry. +_CASE_METRICS = { + "kar_re100": (45, 0.918, 30, (2, 14), 7), + "kar_d075": (44, 0.911, 30, (2, 14), 7), + "kar_d15": (45, 0.892, 30, (2, 14), 7), + "kar_d2": (45, 0.816, 30, (2, 14), 7), + "ill_075L": (43, 0.807, 36, (0, 12), 6), + "ill_1L": (43, 0.900, 36, (0, 12), 6), + "ill_15L": (43, 0.906, 36, (0, 12), 6), + "ill_2L": (43, 0.800, 36, (0, 12), 6), + "kar_re60": (43, 0.364, 30, (2, 14), 7), + "kar_re200": (43, 0.712, 30, (2, 14), 7), + "kar_re400": (43, 0.565, 30, (2, 14), 7), } -FAILED_CASES = { - "kar_re60_sc": {"seed": 43, "dtw": 0.364, "scene_type": "karman", - "cal_name": "kar_re60_sc", - "si": 800, "action_scale": 12.0, "action_bias": (0.0, 0.0, 0.0), - "s_dim": 12, "conv_len": 30, - "obs_slice": (2, 14), "n_objects": 7}, - "kar_re200_sc": {"seed": 43, "dtw": 0.712, "scene_type": "karman", - "cal_name": "kar_re200_sc", - "si": 500, "action_scale": 12.0, "action_bias": (0.0, 0.0, 0.0), - "s_dim": 12, "conv_len": 30, - "obs_slice": (2, 14), "n_objects": 7}, - "kar_re400_sc": {"seed": 43, "dtw": 0.565, "scene_type": "karman", - "cal_name": "kar_re400_sc", - "si": 400, "action_scale": 12.0, "action_bias": (0.0, 0.0, 0.0), - "s_dim": 12, "conv_len": 30, - "obs_slice": (2, 14), "n_objects": 7}, -} + +def _bridge_config(case_id: str) -> dict: + case = CASE_REGISTRY[case_id] + seed, dtw, conv_len, obs_slice, n_objects = _CASE_METRICS[case_id] + cfg = { + "seed": seed, "dtw": dtw, "scene_type": case.scene_type, + "cal_name": case.calibration, "si": case.si, + "action_scale": 12.0, "action_bias": (0.0, 0.0, 0.0), + "s_dim": 14 if case.scene_type == "illusion" else 12, + "conv_len": conv_len, "obs_slice": obs_slice, "n_objects": n_objects, + } + if case.target_diam is not None: + cfg["target_diam"] = case.target_diam + return cfg + + +EFFECTIVE_CASES = {case_id: _bridge_config(case_id) for case_id in ( + "kar_re100", "kar_d075", "kar_d15", "kar_d2", + "ill_075L", "ill_1L", "ill_15L", "ill_2L", +)} +FAILED_CASES = {case_id: _bridge_config(case_id) for case_id in ( + "kar_re60", "kar_re200", "kar_re400", +)} def bridge_case(case_name: str, cfg: dict, out_dir: Path) -> bool: diff --git a/src/SR_analysis/experiments/v5/stage_2_fit_v5.py b/src/SR_analysis/experiments/v5/stage_2_fit_v5.py index 14710a8..2802786 100644 --- a/src/SR_analysis/experiments/v5/stage_2_fit_v5.py +++ b/src/SR_analysis/experiments/v5/stage_2_fit_v5.py @@ -9,10 +9,10 @@ Supports per-scene individual and cross-scene joint fitting. Usage: # Per-scene (fast, niter=40) - PYTHONPATH=src conda run -n sr_env python -m SR_analysis.experiments.v5.stage_2_fit_v5 --scene kar_re100_sc + PYTHONPATH=src conda run -n sr_env python -m SR_analysis.experiments.v5.stage_2_fit_v5 --scene kar_re100 # Per-scene deep search (niter=120) - PYTHONPATH=src conda run -n sr_env python -m SR_analysis.experiments.v5.stage_2_fit_v5 --scene kar_re100_sc --deep + PYTHONPATH=src conda run -n sr_env python -m SR_analysis.experiments.v5.stage_2_fit_v5 --scene kar_re100 --deep # Joint: all effective Karman cases PYTHONPATH=src conda run -n sr_env python -m SR_analysis.experiments.v5.stage_2_fit_v5 --group karman_v5 --mode joint @@ -21,7 +21,7 @@ Usage: PYTHONPATH=src conda run -n sr_env python -m SR_analysis.experiments.v5.stage_2_fit_v5 --group illusion_v5 --mode joint # Joint: mixed V5 + legacy (cross-solver) - PYTHONPATH=src conda run -n sr_env python -m SR_analysis.experiments.v5.stage_2_fit_v5 --scenes kar_re100_sc,kar_d075_sc,kar_d15_sc,karman_re100,karman_re200 --mode joint --output-label mixed_karman + PYTHONPATH=src conda run -n sr_env python -m SR_analysis.experiments.v5.stage_2_fit_v5 --scenes kar_re100,kar_d075,kar_d15,karman_re100,karman_re200 --mode joint --output-label mixed_karman """ from __future__ import annotations @@ -46,6 +46,7 @@ from SR_analysis.utils.feature_builder import ( PHASE_STATE_KEYS, ILLUSION_PHASE_KEYS, CORE_FEAT_KEYS_V2, MU_FEAT_KEYS, ) from SR_analysis.configs import get_scene # for legacy scenes +from drl_pinball.case_registry import CASE_REGISTRY ALL_MU = list(MU_FEAT_KEYS) + ["mu_Cl_tot"] PHYS = [k for k in CORE_FEAT_KEYS_V2 if not k.startswith(("aF_","aB_","aT_","daF","daB","daT"))] @@ -68,15 +69,10 @@ V5_ILLUSION_CFG = { "action_bias": V5_ACTION_BIAS, } -# SI overrides per case -V5_SI_OVERRIDE = { - "ill_075L_sc": 400, "ill_1L_sc": 600, "ill_15L_sc": 800, "ill_2L_sc": 800, - "kar_re60_sc": 800, "kar_re200_sc": 500, "kar_re400_sc": 400, -} - +# V5 case attributes come from drl_pinball.case_registry. def is_v5_scene(name: str) -> bool: - return "_sc" in name or "_tr" in name + return name in CASE_REGISTRY def load_controlled_v5(case_name: str): @@ -97,8 +93,8 @@ def load_controlled_v5(case_name: str): cfg["si_actual"] = v5_cfg.get("SI", 800) cfg["scene_type"] = v5_cfg.get("scene_type", "") - # Apply SI override - si = V5_SI_OVERRIDE.get(case_name, cfg.get("si_actual", 800)) + # Apply canonical training/evaluation SI. + si = CASE_REGISTRY[case_name].si cfg["sample_interval"] = si cfg["si_actual"] = si # for feature builder @@ -148,8 +144,8 @@ FORMULA_DIR = _SR_ROOT / "results" / "formulas_v5" os.makedirs(FORMULA_DIR, exist_ok=True) FIT_GROUPS_V5 = { - "karman_v5": ["kar_re100_sc", "kar_d075_sc", "kar_d15_sc", "kar_d2_sc"], - "illusion_v5": ["ill_075L_sc", "ill_1L_sc", "ill_15L_sc", "ill_2L_sc"], + "karman_v5": ["kar_re100", "kar_d075", "kar_d15", "kar_d2"], + "illusion_v5": ["ill_075L", "ill_1L", "ill_15L", "ill_2L"], } diff --git a/src/SR_analysis/results/README.md b/src/SR_analysis/results/README.md index 1612809..d360de4 100644 --- a/src/SR_analysis/results/README.md +++ b/src/SR_analysis/results/README.md @@ -59,8 +59,11 @@ It adds, without refitting formulas: - `term_contributions_sr_closed_loop_400.csv`: additive terms on 400-step SR trajectories, with exact next-action reconstruction; - `ablation_summary.csv` and `scaling_summary.csv`; - seven constant-rotation steady time-series exports; -- publication-starting-point PNG/PDF diagnostics under `figures/`; +- four publication PNG/PDF figures under `figures/` (performance/duration, pointwise generalization, steady calibration, and a phase-aligned Re100 example); +- publication tables under `tables/` for offline next-action RMSE and 40-step term deletion, replacing the former figure versions; +- package-level `README.md` and `phase_alignment.json` documenting scope and the exact example-window alignment; - a SHA-256 manifest for the complete derived package. +- a GPU-generated `07_flow_field_comparison_karman_re100` exporter/output contract: target, PPO, and canonical SR vorticity are selected by one-run-per-condition exact same-sample center-sensor phase matching and share one physical crop/color scale. The retained field comparison is one matched snapshot per controller, not an ensemble or robustness result; no field artifact exists until the exporter is run. The 400-step PPO similarities are `0.954856`, `0.946973`, `0.900280`, `0.845378` for Kármán Re50/100/200/400 and `0.976328`, `0.975728`, `0.926701` for Illusion 0.75L/1L/1.5L. All completed with finite telemetry. PPO inference runs on CPU while physical GPU 2 remains dedicated to PyCUDA CFD, avoiding PyTorch/PyCUDA context conflicts. diff --git a/src/SR_analysis/results/formulas_v5/ill_075L_sc_ill_1L_sc_ill_15L_sc_ill_2L_sc_front.json b/src/SR_analysis/results/formulas_v5/ill_075L_ill_1L_ill_15L_ill_2L_front.json similarity index 85% rename from src/SR_analysis/results/formulas_v5/ill_075L_sc_ill_1L_sc_ill_15L_sc_ill_2L_sc_front.json rename to src/SR_analysis/results/formulas_v5/ill_075L_ill_1L_ill_15L_ill_2L_front.json index 68b6d83..69145f2 100644 --- a/src/SR_analysis/results/formulas_v5/ill_075L_sc_ill_1L_sc_ill_15L_sc_ill_2L_sc_front.json +++ b/src/SR_analysis/results/formulas_v5/ill_075L_ill_1L_ill_15L_ill_2L_front.json @@ -1,5 +1,5 @@ { - "scene": "ill_075L_sc_ill_1L_sc_ill_15L_sc_ill_2L_sc", + "scene": "ill_075L_ill_1L_ill_15L_ill_2L", "channel": "front", "output": "alpha", "feature_keys": [ diff --git a/src/SR_analysis/results/formulas_v5/ill_1L_sc_front.json b/src/SR_analysis/results/formulas_v5/ill_1L_front.json similarity index 93% rename from src/SR_analysis/results/formulas_v5/ill_1L_sc_front.json rename to src/SR_analysis/results/formulas_v5/ill_1L_front.json index 877d7c3..3cf7fb1 100644 --- a/src/SR_analysis/results/formulas_v5/ill_1L_sc_front.json +++ b/src/SR_analysis/results/formulas_v5/ill_1L_front.json @@ -1,5 +1,5 @@ { - "scene": "ill_1L_sc", + "scene": "ill_1L", "channel": "front", "output": "alpha", "feature_keys": [ diff --git a/src/SR_analysis/results/formulas_v5/ill_1L_sc_top.json b/src/SR_analysis/results/formulas_v5/ill_1L_top.json similarity index 93% rename from src/SR_analysis/results/formulas_v5/ill_1L_sc_top.json rename to src/SR_analysis/results/formulas_v5/ill_1L_top.json index 4271da9..3264a7e 100644 --- a/src/SR_analysis/results/formulas_v5/ill_1L_sc_top.json +++ b/src/SR_analysis/results/formulas_v5/ill_1L_top.json @@ -1,5 +1,5 @@ { - "scene": "ill_1L_sc", + "scene": "ill_1L", "channel": "top", "output": "alpha", "feature_keys": [ diff --git a/src/SR_analysis/results/formulas_v5/kar_d075_sc_front.json b/src/SR_analysis/results/formulas_v5/kar_d075_front.json similarity index 93% rename from src/SR_analysis/results/formulas_v5/kar_d075_sc_front.json rename to src/SR_analysis/results/formulas_v5/kar_d075_front.json index eafb242..98d0b00 100644 --- a/src/SR_analysis/results/formulas_v5/kar_d075_sc_front.json +++ b/src/SR_analysis/results/formulas_v5/kar_d075_front.json @@ -1,5 +1,5 @@ { - "scene": "kar_d075_sc", + "scene": "kar_d075", "channel": "front", "output": "alpha", "feature_keys": [ diff --git a/src/SR_analysis/results/formulas_v5/kar_d075_sc_top.json b/src/SR_analysis/results/formulas_v5/kar_d075_top.json similarity index 94% rename from src/SR_analysis/results/formulas_v5/kar_d075_sc_top.json rename to src/SR_analysis/results/formulas_v5/kar_d075_top.json index c97fb5e..4ec37c4 100644 --- a/src/SR_analysis/results/formulas_v5/kar_d075_sc_top.json +++ b/src/SR_analysis/results/formulas_v5/kar_d075_top.json @@ -1,5 +1,5 @@ { - "scene": "kar_d075_sc", + "scene": "kar_d075", "channel": "top", "output": "alpha", "feature_keys": [ diff --git a/src/SR_analysis/results/formulas_v5/kar_d15_sc_front.json b/src/SR_analysis/results/formulas_v5/kar_d15_front.json similarity index 94% rename from src/SR_analysis/results/formulas_v5/kar_d15_sc_front.json rename to src/SR_analysis/results/formulas_v5/kar_d15_front.json index 1197a0a..e62a979 100644 --- a/src/SR_analysis/results/formulas_v5/kar_d15_sc_front.json +++ b/src/SR_analysis/results/formulas_v5/kar_d15_front.json @@ -1,5 +1,5 @@ { - "scene": "kar_d15_sc", + "scene": "kar_d15", "channel": "front", "output": "alpha", "feature_keys": [ diff --git a/src/SR_analysis/results/formulas_v5/kar_d15_sc_top.json b/src/SR_analysis/results/formulas_v5/kar_d15_top.json similarity index 94% rename from src/SR_analysis/results/formulas_v5/kar_d15_sc_top.json rename to src/SR_analysis/results/formulas_v5/kar_d15_top.json index deef4a0..00bee29 100644 --- a/src/SR_analysis/results/formulas_v5/kar_d15_sc_top.json +++ b/src/SR_analysis/results/formulas_v5/kar_d15_top.json @@ -1,5 +1,5 @@ { - "scene": "kar_d15_sc", + "scene": "kar_d15", "channel": "top", "output": "alpha", "feature_keys": [ diff --git a/src/SR_analysis/results/formulas_v5/kar_d2_sc_front.json b/src/SR_analysis/results/formulas_v5/kar_d2_front.json similarity index 94% rename from src/SR_analysis/results/formulas_v5/kar_d2_sc_front.json rename to src/SR_analysis/results/formulas_v5/kar_d2_front.json index 36f2336..b6712f2 100644 --- a/src/SR_analysis/results/formulas_v5/kar_d2_sc_front.json +++ b/src/SR_analysis/results/formulas_v5/kar_d2_front.json @@ -1,5 +1,5 @@ { - "scene": "kar_d2_sc", + "scene": "kar_d2", "channel": "front", "output": "alpha", "feature_keys": [ diff --git a/src/SR_analysis/results/formulas_v5/kar_d2_sc_top.json b/src/SR_analysis/results/formulas_v5/kar_d2_top.json similarity index 94% rename from src/SR_analysis/results/formulas_v5/kar_d2_sc_top.json rename to src/SR_analysis/results/formulas_v5/kar_d2_top.json index 52fc0be..f8fd348 100644 --- a/src/SR_analysis/results/formulas_v5/kar_d2_sc_top.json +++ b/src/SR_analysis/results/formulas_v5/kar_d2_top.json @@ -1,5 +1,5 @@ { - "scene": "kar_d2_sc", + "scene": "kar_d2", "channel": "top", "output": "alpha", "feature_keys": [ diff --git a/src/SR_analysis/results/formulas_v5/kar_re100_sc_front.json b/src/SR_analysis/results/formulas_v5/kar_re100_front.json similarity index 93% rename from src/SR_analysis/results/formulas_v5/kar_re100_sc_front.json rename to src/SR_analysis/results/formulas_v5/kar_re100_front.json index b7dd0c8..57e3461 100644 --- a/src/SR_analysis/results/formulas_v5/kar_re100_sc_front.json +++ b/src/SR_analysis/results/formulas_v5/kar_re100_front.json @@ -1,5 +1,5 @@ { - "scene": "kar_re100_sc", + "scene": "kar_re100", "channel": "front", "output": "alpha", "feature_keys": [ diff --git a/src/SR_analysis/results/formulas_v5/kar_re100_sc_kar_d075_sc_kar_d15_sc_kar_d2_sc_front.json b/src/SR_analysis/results/formulas_v5/kar_re100_kar_d075_kar_d15_kar_d2_front.json similarity index 87% rename from src/SR_analysis/results/formulas_v5/kar_re100_sc_kar_d075_sc_kar_d15_sc_kar_d2_sc_front.json rename to src/SR_analysis/results/formulas_v5/kar_re100_kar_d075_kar_d15_kar_d2_front.json index 71d57a0..0f132ea 100644 --- a/src/SR_analysis/results/formulas_v5/kar_re100_sc_kar_d075_sc_kar_d15_sc_kar_d2_sc_front.json +++ b/src/SR_analysis/results/formulas_v5/kar_re100_kar_d075_kar_d15_kar_d2_front.json @@ -1,5 +1,5 @@ { - "scene": "kar_re100_sc_kar_d075_sc_kar_d15_sc_kar_d2_sc", + "scene": "kar_re100_kar_d075_kar_d15_kar_d2", "channel": "front", "output": "alpha", "feature_keys": [ diff --git a/src/SR_analysis/results/runs/article2-percase-refit-illusion-20260720/formulas/illusion_0.75L_front.json b/src/SR_analysis/results/runs/article2-percase-refit-illusion-20260720/formulas/illusion_0.75L_front.json deleted file mode 100644 index 47a6711..0000000 --- a/src/SR_analysis/results/runs/article2-percase-refit-illusion-20260720/formulas/illusion_0.75L_front.json +++ /dev/null @@ -1 +0,0 @@ -{"alignment_metadata":{"alignment":"causal_post_state_to_next_action","history_policy":"drop_warmup_zero_initialized","required_target":true,"sample_segments":{"steady":[50,200],"transient":[2,50]},"warmup":2},"anchor":"upper","artifact_id":"article2-percase-refit-illusion-20260720-per-scene-front_odd-anchor-upper","deployment_expression":"-0.24201575527044217 * Cd_rear_a + -0.8372711325983693 * Cl_F","deployment_expression_hash":"8bf481656436b9a0cb8237dd65e76324e3e94d10d9c9c6bf91f1084b1526559e","discovery_parent":{"path":"/home/frank14f/DynamisLab/src/SR_analysis/results/runs/article-joint-illusion-actual_only-s0-20260718/formulas/joint_front.json","sha256":"f5106420e15e2f2c0d96afe60e43b97981fca65f42bdcf0e6f91ece002857356"},"dt_c_metadata":{"per_scene":{"illusion_0.75L":0.2},"unit":"D/U0"},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_metadata":{"builder":"SR_analysis.utils.data_contracts.build_batch_dataset","definitions_module":"SR_analysis.utils.feature_builder","order":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"]},"feature_names":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"],"fitted_expression":"-0.24201575527044217 * Cd_rear_a + -0.8372711325983693 * Cl_F","fitted_expression_hash":"8bf481656436b9a0cb8237dd65e76324e3e94d10d9c9c6bf91f1084b1526559e","g_metadata":{"deployment_architecture":"mapped_shared","deployment_semantics":"mapped_shared","fit_augmentation":"G","fitted_heads":["front_odd"],"front_rule":"front(x)=0.5*(f(x)-f(Gx)); antisymmetric projection","rear_rule":"lower(x)=-upper(Gx)","version":"legacy-mirror-front-odd-rear-swap-negate-v1"},"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","model_type":"symbolic","output":{"definition":"cylinder_surface_tangential_velocity/U0","name":"alpha","native_action_index":0,"native_action_order":["front","upper","lower"],"unit":"dimensionless"},"provenance":{"data":{"dataset_hash":"c4a425ee4618deef1ec042c107fc7d8d9767dbe0da595eef31978cc7647f7b56","paths":{"illusion_0.75L":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-illusion-20260718/illusion/illusion_0.75L/controlled.npz"},"per_scene_hashes":{"illusion_0.75L":"72c0de1d1909e72ed68fd3f2a794512e05623a5010e0564655806618e77de2c0"},"trajectories":{"illusion_0.75L":[{"hash":"2d1517b682b1049ad20da1b011db47d3c29f10a16c998c19dc64f191e562f89f","id":"illusion_0.75L:trajectory-001","path":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-illusion-20260718/illusion/illusion_0.75L/controlled.npz","sample_count":197}]}},"fit":{"command":"src/SR_analysis/stage_2_fit.py --scenes illusion_0.75L,illusion_1L,illusion_1.5L --mode per-scene --run-id article2-percase-refit-illusion-20260720 --data-root src/SR_analysis/data/runs/article-joint-data-illusion-20260718 --feature-set symmetry --feature-profile actual_only --deployment-architecture mapped_shared --fit-augmentation G --fit-purpose fixed-topology-refit --front-topology-expression=-1.826604*Cd_rear_a+2.064493*Cl_F --rear-topology-expression=1.254440*Cd_rear_a-1.528074*Cl_F --front-discovery-parent-path src/SR_analysis/results/runs/article-joint-illusion-actual_only-s0-20260718/formulas/joint_front.json --rear-discovery-parent-path src/SR_analysis/results/runs/article-joint-illusion-actual_only-s0-20260718/formulas/joint_rear_shared_upper.json","created_at":"2026-07-20T13:40:56.561612+00:00","deployment_architecture":"mapped_shared","diagnostic_metrics":{"mae":0.507387673315919,"max_error":1.2880074120007943,"r2":0.8034648267882286,"rmse":0.5658425189470068},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_profile":"actual_only","feature_set":"symmetry","fit_augmentation":"G","fit_purpose":"fixed-topology-refit","g_augmented":true,"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","mode":"per-scene","n_samples":394,"offline_metrics":{"aggregate":{"mae":0.507387673315919,"max_error":1.2880074120007943,"r2":0.8034648267882286,"rmse":0.5658425189470068},"per_case":{"illusion_0.75L":{"mae":0.507387673315919,"max_error":1.2880074120007943,"r2":0.8034648267882286,"rmse":0.5658425189470068}},"per_trajectory":{"illusion_0.75L:trajectory-001":{"mae":0.507387673315919,"max_error":1.2880074120007943,"r2":0.8034648267882286,"rmse":0.5658425189470068}}},"optimizer":{"cost":63.07501798091381,"initial_constants":[1.826604,2.064493],"name":"scipy.optimize.least_squares","nfev":5,"refitted_constants":[0.24201575527044217,-0.8372711325983693],"success":true},"sample_weighting":"case_equal_trajectory_equal_within_case","scene_sample_counts":{"illusion_0.75L":197},"source_n_samples":197,"static_safety":{"finite":true,"grid_size":9,"observed_range":[-1.8031153300859661,1.8031153300859661],"required_output_range":null,"safe":true,"within_output_range":true},"topology_expression":"-1.826604*Cd_rear_a+2.064493*Cl_F"},"pysr":{"purpose":"fixed-topology-refit","used":false}},"role":"front_odd","run_id":"article2-percase-refit-illusion-20260720","schema_version":"1.0","training_scenes":["illusion_0.75L"]} diff --git a/src/SR_analysis/results/runs/article2-percase-refit-illusion-20260720/formulas/illusion_0.75L_rear_shared_upper.json b/src/SR_analysis/results/runs/article2-percase-refit-illusion-20260720/formulas/illusion_0.75L_rear_shared_upper.json deleted file mode 100644 index 77b0a76..0000000 --- a/src/SR_analysis/results/runs/article2-percase-refit-illusion-20260720/formulas/illusion_0.75L_rear_shared_upper.json +++ /dev/null @@ -1 +0,0 @@ -{"alignment_metadata":{"alignment":"causal_post_state_to_next_action","history_policy":"drop_warmup_zero_initialized","required_target":true,"sample_segments":{"steady":[50,200],"transient":[2,50]},"warmup":2},"anchor":"upper","artifact_id":"article2-percase-refit-illusion-20260720-per-scene-rear_shared-anchor-upper","deployment_expression":"0.24859161792995263 * Cd_rear_a - -0.08286020333465423 * Cl_F","deployment_expression_hash":"ece21f94c45145703ef83d1b8d560b026e69243952653c912b6ae2241ffeb6f3","discovery_parent":{"path":"/home/frank14f/DynamisLab/src/SR_analysis/results/runs/article-joint-illusion-actual_only-s0-20260718/formulas/joint_rear_shared_upper.json","sha256":"d5c330aa50b61ae62a55a32bb45bfb395b1c0d8f8c3b0b19bbe43ada1e4fff7c"},"dt_c_metadata":{"per_scene":{"illusion_0.75L":0.2},"unit":"D/U0"},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_metadata":{"builder":"SR_analysis.utils.data_contracts.build_batch_dataset","definitions_module":"SR_analysis.utils.feature_builder","order":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"]},"feature_names":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"],"fitted_expression":"0.24859161792995263 * Cd_rear_a - -0.08286020333465423 * Cl_F","fitted_expression_hash":"ece21f94c45145703ef83d1b8d560b026e69243952653c912b6ae2241ffeb6f3","g_metadata":{"deployment_architecture":"mapped_shared","deployment_semantics":"mapped_shared","fit_augmentation":"G","fitted_heads":["rear_shared"],"front_rule":"front(x)=0.5*(f(x)-f(Gx)); antisymmetric projection","rear_rule":"lower(x)=-upper(Gx)","version":"legacy-mirror-front-odd-rear-swap-negate-v1"},"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","model_type":"symbolic","output":{"definition":"cylinder_surface_tangential_velocity/U0","name":"alpha","native_action_index":1,"native_action_order":["front","upper","lower"],"unit":"dimensionless"},"provenance":{"data":{"dataset_hash":"c4a425ee4618deef1ec042c107fc7d8d9767dbe0da595eef31978cc7647f7b56","paths":{"illusion_0.75L":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-illusion-20260718/illusion/illusion_0.75L/controlled.npz"},"per_scene_hashes":{"illusion_0.75L":"72c0de1d1909e72ed68fd3f2a794512e05623a5010e0564655806618e77de2c0"},"trajectories":{"illusion_0.75L":[{"hash":"2d1517b682b1049ad20da1b011db47d3c29f10a16c998c19dc64f191e562f89f","id":"illusion_0.75L:trajectory-001","path":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-illusion-20260718/illusion/illusion_0.75L/controlled.npz","sample_count":197}]}},"fit":{"command":"src/SR_analysis/stage_2_fit.py --scenes illusion_0.75L,illusion_1L,illusion_1.5L --mode per-scene --run-id article2-percase-refit-illusion-20260720 --data-root src/SR_analysis/data/runs/article-joint-data-illusion-20260718 --feature-set symmetry --feature-profile actual_only --deployment-architecture mapped_shared --fit-augmentation G --fit-purpose fixed-topology-refit --front-topology-expression=-1.826604*Cd_rear_a+2.064493*Cl_F --rear-topology-expression=1.254440*Cd_rear_a-1.528074*Cl_F --front-discovery-parent-path src/SR_analysis/results/runs/article-joint-illusion-actual_only-s0-20260718/formulas/joint_front.json --rear-discovery-parent-path src/SR_analysis/results/runs/article-joint-illusion-actual_only-s0-20260718/formulas/joint_rear_shared_upper.json","created_at":"2026-07-20T13:40:56.579255+00:00","deployment_architecture":"mapped_shared","diagnostic_metrics":{"mae":1.5228555424866943,"max_error":2.5172953469703403,"r2":-5.43956220650249,"rmse":1.5799369208526148},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_profile":"actual_only","feature_set":"symmetry","fit_augmentation":"G","fit_purpose":"fixed-topology-refit","g_augmented":true,"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","mode":"per-scene","n_samples":394,"offline_metrics":{"aggregate":{"mae":1.5228555424866943,"max_error":2.5172953469703403,"r2":-5.43956220650249,"rmse":1.5799369208526148},"per_case":{"illusion_0.75L":{"mae":1.5228555424866943,"max_error":2.5172953469703403,"r2":-5.43956220650249,"rmse":1.5799369208526148}},"per_trajectory":{"illusion_0.75L:trajectory-001":{"mae":1.5228555424866943,"max_error":2.5172953469703403,"r2":-5.43956220650249,"rmse":1.5799369208526148}}},"optimizer":{"cost":491.7515327530284,"initial_constants":[1.25444,1.528074],"name":"scipy.optimize.least_squares","nfev":4,"refitted_constants":[0.24859161792995263,-0.08286020333465423],"success":true},"sample_weighting":"case_equal_trajectory_equal_within_case","scene_sample_counts":{"illusion_0.75L":197},"source_n_samples":197,"static_safety":{"finite":true,"grid_size":9,"observed_range":[-0.7018357595974948,0.7018357595974948],"required_output_range":null,"safe":true,"within_output_range":true},"topology_expression":"1.254440*Cd_rear_a-1.528074*Cl_F"},"pysr":{"purpose":"fixed-topology-refit","used":false}},"role":"rear_shared","run_id":"article2-percase-refit-illusion-20260720","schema_version":"1.0","training_scenes":["illusion_0.75L"]} diff --git a/src/SR_analysis/results/runs/article2-percase-refit-illusion-20260720/formulas/illusion_1.5L_front.json b/src/SR_analysis/results/runs/article2-percase-refit-illusion-20260720/formulas/illusion_1.5L_front.json deleted file mode 100644 index f8f25ed..0000000 --- a/src/SR_analysis/results/runs/article2-percase-refit-illusion-20260720/formulas/illusion_1.5L_front.json +++ /dev/null @@ -1 +0,0 @@ -{"alignment_metadata":{"alignment":"causal_post_state_to_next_action","history_policy":"drop_warmup_zero_initialized","required_target":true,"sample_segments":{"steady":[50,200],"transient":[2,50]},"warmup":2},"anchor":"upper","artifact_id":"article2-percase-refit-illusion-20260720-per-scene-front_odd-anchor-upper","deployment_expression":"-0.044119550519154505 * Cd_rear_a + 2.1015026293420416 * Cl_F","deployment_expression_hash":"f74a54078f4a5290d1589dc1f862ec739b82c01eace53f130750edd2d415f7d7","discovery_parent":{"path":"/home/frank14f/DynamisLab/src/SR_analysis/results/runs/article-joint-illusion-actual_only-s0-20260718/formulas/joint_front.json","sha256":"f5106420e15e2f2c0d96afe60e43b97981fca65f42bdcf0e6f91ece002857356"},"dt_c_metadata":{"per_scene":{"illusion_1.5L":0.4},"unit":"D/U0"},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_metadata":{"builder":"SR_analysis.utils.data_contracts.build_batch_dataset","definitions_module":"SR_analysis.utils.feature_builder","order":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"]},"feature_names":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"],"fitted_expression":"-0.044119550519154505 * Cd_rear_a + 2.1015026293420416 * Cl_F","fitted_expression_hash":"f74a54078f4a5290d1589dc1f862ec739b82c01eace53f130750edd2d415f7d7","g_metadata":{"deployment_architecture":"mapped_shared","deployment_semantics":"mapped_shared","fit_augmentation":"G","fitted_heads":["front_odd"],"front_rule":"front(x)=0.5*(f(x)-f(Gx)); antisymmetric projection","rear_rule":"lower(x)=-upper(Gx)","version":"legacy-mirror-front-odd-rear-swap-negate-v1"},"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","model_type":"symbolic","output":{"definition":"cylinder_surface_tangential_velocity/U0","name":"alpha","native_action_index":0,"native_action_order":["front","upper","lower"],"unit":"dimensionless"},"provenance":{"data":{"dataset_hash":"149b16eadae1f2bcf51ceb92b2e024eed4593125b56fedd6cc2dd2dbfb77e89c","paths":{"illusion_1.5L":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-illusion-20260718/illusion/illusion_1.5L/controlled.npz"},"per_scene_hashes":{"illusion_1.5L":"f4de51d1faa90c6d823ac554c91dd9d218b692538329c9bda775c286d58348c5"},"trajectories":{"illusion_1.5L":[{"hash":"656ccc86c83b7a4384a513e7930265dabce0dde48e1aefb8c86f5967f508fd0c","id":"illusion_1.5L:trajectory-001","path":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-illusion-20260718/illusion/illusion_1.5L/controlled.npz","sample_count":197}]}},"fit":{"command":"src/SR_analysis/stage_2_fit.py --scenes illusion_0.75L,illusion_1L,illusion_1.5L --mode per-scene --run-id article2-percase-refit-illusion-20260720 --data-root src/SR_analysis/data/runs/article-joint-data-illusion-20260718 --feature-set symmetry --feature-profile actual_only --deployment-architecture mapped_shared --fit-augmentation G --fit-purpose fixed-topology-refit --front-topology-expression=-1.826604*Cd_rear_a+2.064493*Cl_F --rear-topology-expression=1.254440*Cd_rear_a-1.528074*Cl_F --front-discovery-parent-path src/SR_analysis/results/runs/article-joint-illusion-actual_only-s0-20260718/formulas/joint_front.json --rear-discovery-parent-path src/SR_analysis/results/runs/article-joint-illusion-actual_only-s0-20260718/formulas/joint_rear_shared_upper.json","created_at":"2026-07-20T13:40:56.631081+00:00","deployment_architecture":"mapped_shared","diagnostic_metrics":{"mae":2.5622557463777267,"max_error":7.030255100055559,"r2":0.5420581062268233,"rmse":3.084420205371026},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_profile":"actual_only","feature_set":"symmetry","fit_augmentation":"G","fit_purpose":"fixed-topology-refit","g_augmented":true,"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","mode":"per-scene","n_samples":394,"offline_metrics":{"aggregate":{"mae":2.5622557463777267,"max_error":7.030255100055559,"r2":0.5420581062268233,"rmse":3.084420205371026},"per_case":{"illusion_1.5L":{"mae":2.5622557463777267,"max_error":7.030255100055559,"r2":0.5420581062268233,"rmse":3.084420205371026}},"per_trajectory":{"illusion_1.5L:trajectory-001":{"mae":2.5622557463777267,"max_error":7.030255100055559,"r2":0.5420581062268233,"rmse":3.084420205371026}}},"optimizer":{"cost":1874.1886566503042,"initial_constants":[1.826604,2.064493],"name":"scipy.optimize.least_squares","nfev":4,"refitted_constants":[0.044119550519154505,2.1015026293420416],"success":true},"sample_weighting":"case_equal_trajectory_equal_within_case","scene_sample_counts":{"illusion_1.5L":197},"source_n_samples":197,"static_safety":{"finite":true,"grid_size":9,"observed_range":[-7.795274277620026,7.795274277620026],"required_output_range":null,"safe":true,"within_output_range":true},"topology_expression":"-1.826604*Cd_rear_a+2.064493*Cl_F"},"pysr":{"purpose":"fixed-topology-refit","used":false}},"role":"front_odd","run_id":"article2-percase-refit-illusion-20260720","schema_version":"1.0","training_scenes":["illusion_1.5L"]} diff --git a/src/SR_analysis/results/runs/article2-percase-refit-illusion-20260720/formulas/illusion_1.5L_rear_shared_upper.json b/src/SR_analysis/results/runs/article2-percase-refit-illusion-20260720/formulas/illusion_1.5L_rear_shared_upper.json deleted file mode 100644 index 31fe668..0000000 --- a/src/SR_analysis/results/runs/article2-percase-refit-illusion-20260720/formulas/illusion_1.5L_rear_shared_upper.json +++ /dev/null @@ -1 +0,0 @@ -{"alignment_metadata":{"alignment":"causal_post_state_to_next_action","history_policy":"drop_warmup_zero_initialized","required_target":true,"sample_segments":{"steady":[50,200],"transient":[2,50]},"warmup":2},"anchor":"upper","artifact_id":"article2-percase-refit-illusion-20260720-per-scene-rear_shared-anchor-upper","deployment_expression":"1.5682603086502969 * Cd_rear_a - 1.6011017720495324 * Cl_F","deployment_expression_hash":"5662e9bf6cffd0921e089485cb09edaa1bd9a48895b1135fff8ba1233eb20e7f","discovery_parent":{"path":"/home/frank14f/DynamisLab/src/SR_analysis/results/runs/article-joint-illusion-actual_only-s0-20260718/formulas/joint_rear_shared_upper.json","sha256":"d5c330aa50b61ae62a55a32bb45bfb395b1c0d8f8c3b0b19bbe43ada1e4fff7c"},"dt_c_metadata":{"per_scene":{"illusion_1.5L":0.4},"unit":"D/U0"},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_metadata":{"builder":"SR_analysis.utils.data_contracts.build_batch_dataset","definitions_module":"SR_analysis.utils.feature_builder","order":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"]},"feature_names":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"],"fitted_expression":"1.5682603086502969 * Cd_rear_a - 1.6011017720495324 * Cl_F","fitted_expression_hash":"5662e9bf6cffd0921e089485cb09edaa1bd9a48895b1135fff8ba1233eb20e7f","g_metadata":{"deployment_architecture":"mapped_shared","deployment_semantics":"mapped_shared","fit_augmentation":"G","fitted_heads":["rear_shared"],"front_rule":"front(x)=0.5*(f(x)-f(Gx)); antisymmetric projection","rear_rule":"lower(x)=-upper(Gx)","version":"legacy-mirror-front-odd-rear-swap-negate-v1"},"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","model_type":"symbolic","output":{"definition":"cylinder_surface_tangential_velocity/U0","name":"alpha","native_action_index":1,"native_action_order":["front","upper","lower"],"unit":"dimensionless"},"provenance":{"data":{"dataset_hash":"149b16eadae1f2bcf51ceb92b2e024eed4593125b56fedd6cc2dd2dbfb77e89c","paths":{"illusion_1.5L":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-illusion-20260718/illusion/illusion_1.5L/controlled.npz"},"per_scene_hashes":{"illusion_1.5L":"f4de51d1faa90c6d823ac554c91dd9d218b692538329c9bda775c286d58348c5"},"trajectories":{"illusion_1.5L":[{"hash":"656ccc86c83b7a4384a513e7930265dabce0dde48e1aefb8c86f5967f508fd0c","id":"illusion_1.5L:trajectory-001","path":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-illusion-20260718/illusion/illusion_1.5L/controlled.npz","sample_count":197}]}},"fit":{"command":"src/SR_analysis/stage_2_fit.py --scenes illusion_0.75L,illusion_1L,illusion_1.5L --mode per-scene --run-id article2-percase-refit-illusion-20260720 --data-root src/SR_analysis/data/runs/article-joint-data-illusion-20260718 --feature-set symmetry --feature-profile actual_only --deployment-architecture mapped_shared --fit-augmentation G --fit-purpose fixed-topology-refit --front-topology-expression=-1.826604*Cd_rear_a+2.064493*Cl_F --rear-topology-expression=1.254440*Cd_rear_a-1.528074*Cl_F --front-discovery-parent-path src/SR_analysis/results/runs/article-joint-illusion-actual_only-s0-20260718/formulas/joint_front.json --rear-discovery-parent-path src/SR_analysis/results/runs/article-joint-illusion-actual_only-s0-20260718/formulas/joint_rear_shared_upper.json","created_at":"2026-07-20T13:40:56.647873+00:00","deployment_architecture":"mapped_shared","diagnostic_metrics":{"mae":1.6098420125432724,"max_error":5.0089302664731585,"r2":0.5948268502056615,"rmse":1.9948656250961145},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_profile":"actual_only","feature_set":"symmetry","fit_augmentation":"G","fit_purpose":"fixed-topology-refit","g_augmented":true,"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","mode":"per-scene","n_samples":394,"offline_metrics":{"aggregate":{"mae":1.6098420125432724,"max_error":5.0089302664731585,"r2":0.5948268502056615,"rmse":1.9948656250961145},"per_case":{"illusion_1.5L":{"mae":1.6098420125432724,"max_error":5.0089302664731585,"r2":0.5948268502056615,"rmse":1.9948656250961145}},"per_trajectory":{"illusion_1.5L:trajectory-001":{"mae":1.6098420125432724,"max_error":5.0089302664731585,"r2":0.5948268502056615,"rmse":1.9948656250961145}}},"optimizer":{"cost":783.9593058514514,"initial_constants":[1.25444,1.528074],"name":"scipy.optimize.least_squares","nfev":3,"refitted_constants":[1.5682603086502969,1.6011017720495324],"success":true},"sample_weighting":"case_equal_trajectory_equal_within_case","scene_sample_counts":{"illusion_1.5L":197},"source_n_samples":197,"static_safety":{"finite":true,"grid_size":9,"observed_range":[-4.121136424190122,4.121136424190122],"required_output_range":null,"safe":true,"within_output_range":true},"topology_expression":"1.254440*Cd_rear_a-1.528074*Cl_F"},"pysr":{"purpose":"fixed-topology-refit","used":false}},"role":"rear_shared","run_id":"article2-percase-refit-illusion-20260720","schema_version":"1.0","training_scenes":["illusion_1.5L"]} diff --git a/src/SR_analysis/results/runs/article2-percase-refit-illusion-20260720/formulas/illusion_1L_front.json b/src/SR_analysis/results/runs/article2-percase-refit-illusion-20260720/formulas/illusion_1L_front.json deleted file mode 100644 index 572e7d0..0000000 --- a/src/SR_analysis/results/runs/article2-percase-refit-illusion-20260720/formulas/illusion_1L_front.json +++ /dev/null @@ -1 +0,0 @@ -{"alignment_metadata":{"alignment":"causal_post_state_to_next_action","history_policy":"drop_warmup_zero_initialized","required_target":true,"sample_segments":{"steady":[50,200],"transient":[2,50]},"warmup":2},"anchor":"upper","artifact_id":"article2-percase-refit-illusion-20260720-per-scene-front_odd-anchor-upper","deployment_expression":"--2.043848942000545 * Cd_rear_a + -2.219295123924814 * Cl_F","deployment_expression_hash":"0231cef8c9019550c64e7354d7672e13530b0a76a658151f64f73e7cbd62b160","discovery_parent":{"path":"/home/frank14f/DynamisLab/src/SR_analysis/results/runs/article-joint-illusion-actual_only-s0-20260718/formulas/joint_front.json","sha256":"f5106420e15e2f2c0d96afe60e43b97981fca65f42bdcf0e6f91ece002857356"},"dt_c_metadata":{"per_scene":{"illusion_1L":0.3},"unit":"D/U0"},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_metadata":{"builder":"SR_analysis.utils.data_contracts.build_batch_dataset","definitions_module":"SR_analysis.utils.feature_builder","order":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"]},"feature_names":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"],"fitted_expression":"--2.043848942000545 * Cd_rear_a + -2.219295123924814 * Cl_F","fitted_expression_hash":"0231cef8c9019550c64e7354d7672e13530b0a76a658151f64f73e7cbd62b160","g_metadata":{"deployment_architecture":"mapped_shared","deployment_semantics":"mapped_shared","fit_augmentation":"G","fitted_heads":["front_odd"],"front_rule":"front(x)=0.5*(f(x)-f(Gx)); antisymmetric projection","rear_rule":"lower(x)=-upper(Gx)","version":"legacy-mirror-front-odd-rear-swap-negate-v1"},"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","model_type":"symbolic","output":{"definition":"cylinder_surface_tangential_velocity/U0","name":"alpha","native_action_index":0,"native_action_order":["front","upper","lower"],"unit":"dimensionless"},"provenance":{"data":{"dataset_hash":"38176ffb3de102f1fbbbbd3b2374899c1f891916ea6afffb24df4ec6db40a352","paths":{"illusion_1L":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-illusion-20260718/illusion/illusion_1L/controlled.npz"},"per_scene_hashes":{"illusion_1L":"89ff2a18d4fec86e3ec0e0804577f9f86e9687063229771372fd260bf441bbb1"},"trajectories":{"illusion_1L":[{"hash":"4914d81de346558f0ae8d52d4280d46149367dd760f1df2ede7fae551985aa93","id":"illusion_1L:trajectory-001","path":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-illusion-20260718/illusion/illusion_1L/controlled.npz","sample_count":197}]}},"fit":{"command":"src/SR_analysis/stage_2_fit.py --scenes illusion_0.75L,illusion_1L,illusion_1.5L --mode per-scene --run-id article2-percase-refit-illusion-20260720 --data-root src/SR_analysis/data/runs/article-joint-data-illusion-20260718 --feature-set symmetry --feature-profile actual_only --deployment-architecture mapped_shared --fit-augmentation G --fit-purpose fixed-topology-refit --front-topology-expression=-1.826604*Cd_rear_a+2.064493*Cl_F --rear-topology-expression=1.254440*Cd_rear_a-1.528074*Cl_F --front-discovery-parent-path src/SR_analysis/results/runs/article-joint-illusion-actual_only-s0-20260718/formulas/joint_front.json --rear-discovery-parent-path src/SR_analysis/results/runs/article-joint-illusion-actual_only-s0-20260718/formulas/joint_rear_shared_upper.json","created_at":"2026-07-20T13:40:56.599745+00:00","deployment_architecture":"mapped_shared","diagnostic_metrics":{"mae":0.26749351633879453,"max_error":1.0441460226187893,"r2":0.6059884236814721,"rmse":0.32378763498020424},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_profile":"actual_only","feature_set":"symmetry","fit_augmentation":"G","fit_purpose":"fixed-topology-refit","g_augmented":true,"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","mode":"per-scene","n_samples":394,"offline_metrics":{"aggregate":{"mae":0.26749351633879453,"max_error":1.0441460226187893,"r2":0.6059884236814721,"rmse":0.32378763498020424},"per_case":{"illusion_1L":{"mae":0.26749351633879453,"max_error":1.0441460226187893,"r2":0.6059884236814721,"rmse":0.32378763498020424}},"per_trajectory":{"illusion_1L:trajectory-001":{"mae":0.26749351633879453,"max_error":1.0441460226187893,"r2":0.6059884236814721,"rmse":0.32378763498020424}}},"optimizer":{"cost":20.653171215516565,"initial_constants":[1.826604,2.064493],"name":"scipy.optimize.least_squares","nfev":4,"refitted_constants":[-2.043848942000545,-2.219295123924814],"success":true},"sample_weighting":"case_equal_trajectory_equal_within_case","scene_sample_counts":{"illusion_1L":197},"source_n_samples":197,"static_safety":{"finite":true,"grid_size":9,"observed_range":[-0.007531536402189065,0.007531536402189065],"required_output_range":null,"safe":true,"within_output_range":true},"topology_expression":"-1.826604*Cd_rear_a+2.064493*Cl_F"},"pysr":{"purpose":"fixed-topology-refit","used":false}},"role":"front_odd","run_id":"article2-percase-refit-illusion-20260720","schema_version":"1.0","training_scenes":["illusion_1L"]} diff --git a/src/SR_analysis/results/runs/article2-percase-refit-illusion-20260720/formulas/illusion_1L_rear_shared_upper.json b/src/SR_analysis/results/runs/article2-percase-refit-illusion-20260720/formulas/illusion_1L_rear_shared_upper.json deleted file mode 100644 index c387ebf..0000000 --- a/src/SR_analysis/results/runs/article2-percase-refit-illusion-20260720/formulas/illusion_1L_rear_shared_upper.json +++ /dev/null @@ -1 +0,0 @@ -{"alignment_metadata":{"alignment":"causal_post_state_to_next_action","history_policy":"drop_warmup_zero_initialized","required_target":true,"sample_segments":{"steady":[50,200],"transient":[2,50]},"warmup":2},"anchor":"upper","artifact_id":"article2-percase-refit-illusion-20260720-per-scene-rear_shared-anchor-upper","deployment_expression":"0.18434653705308968 * Cd_rear_a - 0.15875223987426046 * Cl_F","deployment_expression_hash":"6a10365160b492b142bd40ca093e8ad0e82af96c929cb46a6af626fcf55bb5ce","discovery_parent":{"path":"/home/frank14f/DynamisLab/src/SR_analysis/results/runs/article-joint-illusion-actual_only-s0-20260718/formulas/joint_rear_shared_upper.json","sha256":"d5c330aa50b61ae62a55a32bb45bfb395b1c0d8f8c3b0b19bbe43ada1e4fff7c"},"dt_c_metadata":{"per_scene":{"illusion_1L":0.3},"unit":"D/U0"},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_metadata":{"builder":"SR_analysis.utils.data_contracts.build_batch_dataset","definitions_module":"SR_analysis.utils.feature_builder","order":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"]},"feature_names":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"],"fitted_expression":"0.18434653705308968 * Cd_rear_a - 0.15875223987426046 * Cl_F","fitted_expression_hash":"6a10365160b492b142bd40ca093e8ad0e82af96c929cb46a6af626fcf55bb5ce","g_metadata":{"deployment_architecture":"mapped_shared","deployment_semantics":"mapped_shared","fit_augmentation":"G","fitted_heads":["rear_shared"],"front_rule":"front(x)=0.5*(f(x)-f(Gx)); antisymmetric projection","rear_rule":"lower(x)=-upper(Gx)","version":"legacy-mirror-front-odd-rear-swap-negate-v1"},"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","model_type":"symbolic","output":{"definition":"cylinder_surface_tangential_velocity/U0","name":"alpha","native_action_index":1,"native_action_order":["front","upper","lower"],"unit":"dimensionless"},"provenance":{"data":{"dataset_hash":"38176ffb3de102f1fbbbbd3b2374899c1f891916ea6afffb24df4ec6db40a352","paths":{"illusion_1L":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-illusion-20260718/illusion/illusion_1L/controlled.npz"},"per_scene_hashes":{"illusion_1L":"89ff2a18d4fec86e3ec0e0804577f9f86e9687063229771372fd260bf441bbb1"},"trajectories":{"illusion_1L":[{"hash":"4914d81de346558f0ae8d52d4280d46149367dd760f1df2ede7fae551985aa93","id":"illusion_1L:trajectory-001","path":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-illusion-20260718/illusion/illusion_1L/controlled.npz","sample_count":197}]}},"fit":{"command":"src/SR_analysis/stage_2_fit.py --scenes illusion_0.75L,illusion_1L,illusion_1.5L --mode per-scene --run-id article2-percase-refit-illusion-20260720 --data-root src/SR_analysis/data/runs/article-joint-data-illusion-20260718 --feature-set symmetry --feature-profile actual_only --deployment-architecture mapped_shared --fit-augmentation G --fit-purpose fixed-topology-refit --front-topology-expression=-1.826604*Cd_rear_a+2.064493*Cl_F --rear-topology-expression=1.254440*Cd_rear_a-1.528074*Cl_F --front-discovery-parent-path src/SR_analysis/results/runs/article-joint-illusion-actual_only-s0-20260718/formulas/joint_front.json --rear-discovery-parent-path src/SR_analysis/results/runs/article-joint-illusion-actual_only-s0-20260718/formulas/joint_rear_shared_upper.json","created_at":"2026-07-20T13:40:56.616390+00:00","deployment_architecture":"mapped_shared","diagnostic_metrics":{"mae":0.8679547536796725,"max_error":1.5008460737775966,"r2":-11.247969651640615,"rmse":0.905159878077149},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_profile":"actual_only","feature_set":"symmetry","fit_augmentation":"G","fit_purpose":"fixed-topology-refit","g_augmented":true,"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","mode":"per-scene","n_samples":394,"offline_metrics":{"aggregate":{"mae":0.8679547536796725,"max_error":1.5008460737775966,"r2":-11.247969651640615,"rmse":0.905159878077149},"per_case":{"illusion_1L":{"mae":0.8679547536796725,"max_error":1.5008460737775966,"r2":-11.247969651640615,"rmse":0.905159878077149}},"per_trajectory":{"illusion_1L:trajectory-001":{"mae":0.8679547536796725,"max_error":1.5008460737775966,"r2":-11.247969651640615,"rmse":0.905159878077149}}},"optimizer":{"cost":161.40493776148585,"initial_constants":[1.25444,1.528074],"name":"scipy.optimize.least_squares","nfev":5,"refitted_constants":[0.18434653705308968,0.15875223987426046],"success":true},"sample_weighting":"case_equal_trajectory_equal_within_case","scene_sample_counts":{"illusion_1L":197},"source_n_samples":197,"static_safety":{"finite":true,"grid_size":9,"observed_range":[-0.03826880648297437,0.03826880648297437],"required_output_range":null,"safe":true,"within_output_range":true},"topology_expression":"1.254440*Cd_rear_a-1.528074*Cl_F"},"pysr":{"purpose":"fixed-topology-refit","used":false}},"role":"rear_shared","run_id":"article2-percase-refit-illusion-20260720","schema_version":"1.0","training_scenes":["illusion_1L"]} diff --git a/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re100_front.json b/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re100_front.json deleted file mode 100644 index 657949f..0000000 --- a/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re100_front.json +++ /dev/null @@ -1 +0,0 @@ -{"alignment_metadata":{"alignment":"causal_post_state_to_next_action","history_policy":"drop_warmup_zero_initialized","required_target":false,"sample_segments":{"steady":[50,200],"transient":[2,50]},"warmup":2},"anchor":"upper","artifact_id":"article2-percase-refit-karman-20260720-per-scene-front_odd-anchor-upper","deployment_expression":"-0.324842776541943 * Cd_rear_a","deployment_expression_hash":"ced7269af5ec63c0b71ea67a366b319258fa5717d243ab13e1ba32e628dcdb6a","discovery_parent":{"path":"/home/frank14f/DynamisLab/src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_front.json","sha256":"49af614459fefee3ebeed5c1e8761bdd1f0f8860a8d2e8f303dd1bb010ad67bf"},"dt_c_metadata":{"per_scene":{"karman_re100":0.4},"unit":"D/U0"},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_metadata":{"builder":"SR_analysis.utils.data_contracts.build_batch_dataset","definitions_module":"SR_analysis.utils.feature_builder","order":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"]},"feature_names":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"],"fitted_expression":"-0.324842776541943 * Cd_rear_a","fitted_expression_hash":"ced7269af5ec63c0b71ea67a366b319258fa5717d243ab13e1ba32e628dcdb6a","g_metadata":{"deployment_architecture":"mapped_shared","deployment_semantics":"mapped_shared","fit_augmentation":"G","fitted_heads":["front_odd"],"front_rule":"front(x)=0.5*(f(x)-f(Gx)); antisymmetric projection","rear_rule":"lower(x)=-upper(Gx)","version":"legacy-mirror-front-odd-rear-swap-negate-v1"},"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","model_type":"symbolic","output":{"definition":"cylinder_surface_tangential_velocity/U0","name":"alpha","native_action_index":0,"native_action_order":["front","upper","lower"],"unit":"dimensionless"},"provenance":{"data":{"dataset_hash":"87f80572552541551060eb2f849b53e09f5c81374f1f30f9dead6d197f073bda","paths":{"karman_re100":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-karman-20260718/karman/karman_re100/controlled.npz"},"per_scene_hashes":{"karman_re100":"094ef9bfdfb32a3335011bbc8490a52cda20139bfeb04fd1b49d5870e997c77f"},"trajectories":{"karman_re100":[{"hash":"d3b486f671ce40188af10076aeb92b18d0f39be285be3be1fd78775f3d65935e","id":"karman_re100:trajectory-001","path":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-karman-20260718/karman/karman_re100/controlled.npz","sample_count":197}]}},"fit":{"command":"src/SR_analysis/stage_2_fit.py --scenes karman_re50,karman_re100,karman_re200,karman_re400 --mode per-scene --run-id article2-percase-refit-karman-20260720 --data-root src/SR_analysis/data/runs/article-joint-data-karman-20260718 --feature-set symmetry --feature-profile actual_only --deployment-architecture mapped_shared --fit-augmentation G --fit-purpose fixed-topology-refit --front-topology-expression=-0.381391*Cd_rear_a --rear-topology-expression=1.307782*Cl_rear_s-3.431209 --front-discovery-parent-path src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_front.json --rear-discovery-parent-path src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_rear_shared_upper.json","created_at":"2026-07-20T13:40:56.610395+00:00","deployment_architecture":"mapped_shared","diagnostic_metrics":{"mae":0.36762602777500997,"max_error":0.9996270856110621,"r2":0.8942459148735239,"rmse":0.45820137299549274},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_profile":"actual_only","feature_set":"symmetry","fit_augmentation":"G","fit_purpose":"fixed-topology-refit","g_augmented":true,"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","mode":"per-scene","n_samples":394,"offline_metrics":{"aggregate":{"mae":0.36762602777500997,"max_error":0.9996270856110621,"r2":0.8942459148735239,"rmse":0.45820137299549274},"per_case":{"karman_re100":{"mae":0.36762602777500997,"max_error":0.9996270856110621,"r2":0.8942459148735239,"rmse":0.45820137299549274}},"per_trajectory":{"karman_re100:trajectory-001":{"mae":0.36762602777500997,"max_error":0.9996270856110621,"r2":0.8942459148735239,"rmse":0.45820137299549274}}},"optimizer":{"cost":41.35985414834604,"initial_constants":[0.381391],"name":"scipy.optimize.least_squares","nfev":3,"refitted_constants":[0.324842776541943],"success":true},"sample_weighting":"case_equal_trajectory_equal_within_case","scene_sample_counts":{"karman_re100":197},"source_n_samples":197,"static_safety":{"finite":true,"grid_size":9,"observed_range":[-2.1770422999512413,2.1770422999512413],"required_output_range":null,"safe":true,"within_output_range":true},"topology_expression":"-0.381391*Cd_rear_a"},"pysr":{"purpose":"fixed-topology-refit","used":false}},"role":"front_odd","run_id":"article2-percase-refit-karman-20260720","schema_version":"1.0","training_scenes":["karman_re100"]} diff --git a/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re100_rear_shared_upper.json b/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re100_rear_shared_upper.json deleted file mode 100644 index c8facff..0000000 --- a/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re100_rear_shared_upper.json +++ /dev/null @@ -1 +0,0 @@ -{"alignment_metadata":{"alignment":"causal_post_state_to_next_action","history_policy":"drop_warmup_zero_initialized","required_target":false,"sample_segments":{"steady":[50,200],"transient":[2,50]},"warmup":2},"anchor":"upper","artifact_id":"article2-percase-refit-karman-20260720-per-scene-rear_shared-anchor-upper","deployment_expression":"1.3203186589016584 * Cl_rear_s - 4.177446928237706","deployment_expression_hash":"f9a73a2d7947a5737a923946846a14591260128111d152908d451c892eaa65cf","discovery_parent":{"path":"/home/frank14f/DynamisLab/src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_rear_shared_upper.json","sha256":"f24114508350fd6a9bbd6e5aa93713b2b136590539602c0ba6334e3af7508982"},"dt_c_metadata":{"per_scene":{"karman_re100":0.4},"unit":"D/U0"},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_metadata":{"builder":"SR_analysis.utils.data_contracts.build_batch_dataset","definitions_module":"SR_analysis.utils.feature_builder","order":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"]},"feature_names":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"],"fitted_expression":"1.3203186589016584 * Cl_rear_s - 4.177446928237706","fitted_expression_hash":"f9a73a2d7947a5737a923946846a14591260128111d152908d451c892eaa65cf","g_metadata":{"deployment_architecture":"mapped_shared","deployment_semantics":"mapped_shared","fit_augmentation":"G","fitted_heads":["rear_shared"],"front_rule":"front(x)=0.5*(f(x)-f(Gx)); antisymmetric projection","rear_rule":"lower(x)=-upper(Gx)","version":"legacy-mirror-front-odd-rear-swap-negate-v1"},"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","model_type":"symbolic","output":{"definition":"cylinder_surface_tangential_velocity/U0","name":"alpha","native_action_index":1,"native_action_order":["front","upper","lower"],"unit":"dimensionless"},"provenance":{"data":{"dataset_hash":"87f80572552541551060eb2f849b53e09f5c81374f1f30f9dead6d197f073bda","paths":{"karman_re100":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-karman-20260718/karman/karman_re100/controlled.npz"},"per_scene_hashes":{"karman_re100":"094ef9bfdfb32a3335011bbc8490a52cda20139bfeb04fd1b49d5870e997c77f"},"trajectories":{"karman_re100":[{"hash":"d3b486f671ce40188af10076aeb92b18d0f39be285be3be1fd78775f3d65935e","id":"karman_re100:trajectory-001","path":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-karman-20260718/karman/karman_re100/controlled.npz","sample_count":197}]}},"fit":{"command":"src/SR_analysis/stage_2_fit.py --scenes karman_re50,karman_re100,karman_re200,karman_re400 --mode per-scene --run-id article2-percase-refit-karman-20260720 --data-root src/SR_analysis/data/runs/article-joint-data-karman-20260718 --feature-set symmetry --feature-profile actual_only --deployment-architecture mapped_shared --fit-augmentation G --fit-purpose fixed-topology-refit --front-topology-expression=-0.381391*Cd_rear_a --rear-topology-expression=1.307782*Cl_rear_s-3.431209 --front-discovery-parent-path src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_front.json --rear-discovery-parent-path src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_rear_shared_upper.json","created_at":"2026-07-20T13:40:56.629939+00:00","deployment_architecture":"mapped_shared","diagnostic_metrics":{"mae":0.7736831127890521,"max_error":2.7880229053394796,"r2":0.7130789672514801,"rmse":1.0448153119880985},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_profile":"actual_only","feature_set":"symmetry","fit_augmentation":"G","fit_purpose":"fixed-topology-refit","g_augmented":true,"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","mode":"per-scene","n_samples":394,"offline_metrics":{"aggregate":{"mae":0.7736831127890521,"max_error":2.7880229053394796,"r2":0.7130789672514801,"rmse":1.0448153119880985},"per_case":{"karman_re100":{"mae":0.7736831127890521,"max_error":2.7880229053394796,"r2":0.7130789672514801,"rmse":1.0448153119880985}},"per_trajectory":{"karman_re100:trajectory-001":{"mae":0.7736831127890521,"max_error":2.7880229053394796,"r2":0.7130789672514801,"rmse":1.0448153119880985}}},"optimizer":{"cost":215.05289012446303,"initial_constants":[1.307782,3.431209],"name":"scipy.optimize.least_squares","nfev":3,"refitted_constants":[1.3203186589016584,4.177446928237706],"success":true},"sample_weighting":"case_equal_trajectory_equal_within_case","scene_sample_counts":{"karman_re100":197},"source_n_samples":197,"static_safety":{"finite":true,"grid_size":9,"observed_range":[-7.272020453252877,-1.0828734032225347],"required_output_range":null,"safe":true,"within_output_range":true},"topology_expression":"1.307782*Cl_rear_s-3.431209"},"pysr":{"purpose":"fixed-topology-refit","used":false}},"role":"rear_shared","run_id":"article2-percase-refit-karman-20260720","schema_version":"1.0","training_scenes":["karman_re100"]} diff --git a/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re200_front.json b/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re200_front.json deleted file mode 100644 index 554ec4f..0000000 --- a/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re200_front.json +++ /dev/null @@ -1 +0,0 @@ -{"alignment_metadata":{"alignment":"causal_post_state_to_next_action","history_policy":"drop_warmup_zero_initialized","required_target":false,"sample_segments":{"steady":[50,200],"transient":[2,50]},"warmup":2},"anchor":"upper","artifact_id":"article2-percase-refit-karman-20260720-per-scene-front_odd-anchor-upper","deployment_expression":"-0.5002775221133345 * Cd_rear_a","deployment_expression_hash":"3b792dfe02857f398c1d2d1d579e8739428a23297fda027b0da9529ad651bb2a","discovery_parent":{"path":"/home/frank14f/DynamisLab/src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_front.json","sha256":"49af614459fefee3ebeed5c1e8761bdd1f0f8860a8d2e8f303dd1bb010ad67bf"},"dt_c_metadata":{"per_scene":{"karman_re200":0.4},"unit":"D/U0"},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_metadata":{"builder":"SR_analysis.utils.data_contracts.build_batch_dataset","definitions_module":"SR_analysis.utils.feature_builder","order":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"]},"feature_names":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"],"fitted_expression":"-0.5002775221133345 * Cd_rear_a","fitted_expression_hash":"3b792dfe02857f398c1d2d1d579e8739428a23297fda027b0da9529ad651bb2a","g_metadata":{"deployment_architecture":"mapped_shared","deployment_semantics":"mapped_shared","fit_augmentation":"G","fitted_heads":["front_odd"],"front_rule":"front(x)=0.5*(f(x)-f(Gx)); antisymmetric projection","rear_rule":"lower(x)=-upper(Gx)","version":"legacy-mirror-front-odd-rear-swap-negate-v1"},"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","model_type":"symbolic","output":{"definition":"cylinder_surface_tangential_velocity/U0","name":"alpha","native_action_index":0,"native_action_order":["front","upper","lower"],"unit":"dimensionless"},"provenance":{"data":{"dataset_hash":"e018b5dadd19343480424aefa86eb0564f9dd6a9723451032cee4c88aa09dc89","paths":{"karman_re200":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-karman-20260718/karman/karman_re200/controlled.npz"},"per_scene_hashes":{"karman_re200":"553a3217f408b9bfa1a9864f2c07ec509727a742de906369bd69128bcd8a3e54"},"trajectories":{"karman_re200":[{"hash":"027f19ac5b858bc1dc8465e9d2ae93eb95760d8705a47da8c8814489204180c4","id":"karman_re200:trajectory-001","path":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-karman-20260718/karman/karman_re200/controlled.npz","sample_count":197}]}},"fit":{"command":"src/SR_analysis/stage_2_fit.py --scenes karman_re50,karman_re100,karman_re200,karman_re400 --mode per-scene --run-id article2-percase-refit-karman-20260720 --data-root src/SR_analysis/data/runs/article-joint-data-karman-20260718 --feature-set symmetry --feature-profile actual_only --deployment-architecture mapped_shared --fit-augmentation G --fit-purpose fixed-topology-refit --front-topology-expression=-0.381391*Cd_rear_a --rear-topology-expression=1.307782*Cl_rear_s-3.431209 --front-discovery-parent-path src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_front.json --rear-discovery-parent-path src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_rear_shared_upper.json","created_at":"2026-07-20T13:40:56.644539+00:00","deployment_architecture":"mapped_shared","diagnostic_metrics":{"mae":1.1792826984245248,"max_error":3.052569064879571,"r2":0.6465569445532036,"rmse":1.4089506676834735},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_profile":"actual_only","feature_set":"symmetry","fit_augmentation":"G","fit_purpose":"fixed-topology-refit","g_augmented":true,"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","mode":"per-scene","n_samples":394,"offline_metrics":{"aggregate":{"mae":1.1792826984245248,"max_error":3.052569064879571,"r2":0.6465569445532036,"rmse":1.4089506676834735},"per_case":{"karman_re200":{"mae":1.1792826984245248,"max_error":3.052569064879571,"r2":0.6465569445532036,"rmse":1.4089506676834735}},"per_trajectory":{"karman_re200:trajectory-001":{"mae":1.1792826984245248,"max_error":3.052569064879571,"r2":0.6465569445532036,"rmse":1.4089506676834735}}},"optimizer":{"cost":391.07297084124383,"initial_constants":[0.381391],"name":"scipy.optimize.least_squares","nfev":3,"refitted_constants":[0.5002775221133345],"success":true},"sample_weighting":"case_equal_trajectory_equal_within_case","scene_sample_counts":{"karman_re200":197},"source_n_samples":197,"static_safety":{"finite":true,"grid_size":9,"observed_range":[-4.9287835586276865,4.9287835586276865],"required_output_range":null,"safe":true,"within_output_range":true},"topology_expression":"-0.381391*Cd_rear_a"},"pysr":{"purpose":"fixed-topology-refit","used":false}},"role":"front_odd","run_id":"article2-percase-refit-karman-20260720","schema_version":"1.0","training_scenes":["karman_re200"]} diff --git a/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re200_rear_shared_upper.json b/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re200_rear_shared_upper.json deleted file mode 100644 index 2635868..0000000 --- a/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re200_rear_shared_upper.json +++ /dev/null @@ -1 +0,0 @@ -{"alignment_metadata":{"alignment":"causal_post_state_to_next_action","history_policy":"drop_warmup_zero_initialized","required_target":false,"sample_segments":{"steady":[50,200],"transient":[2,50]},"warmup":2},"anchor":"upper","artifact_id":"article2-percase-refit-karman-20260720-per-scene-rear_shared-anchor-upper","deployment_expression":"1.6971078570952467 * Cl_rear_s - 3.410910965502206","deployment_expression_hash":"eeceac7a87978bbbca9c8e0792c3c7292f1c7b2aa08940f31504cc2f8de6e62a","discovery_parent":{"path":"/home/frank14f/DynamisLab/src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_rear_shared_upper.json","sha256":"f24114508350fd6a9bbd6e5aa93713b2b136590539602c0ba6334e3af7508982"},"dt_c_metadata":{"per_scene":{"karman_re200":0.4},"unit":"D/U0"},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_metadata":{"builder":"SR_analysis.utils.data_contracts.build_batch_dataset","definitions_module":"SR_analysis.utils.feature_builder","order":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"]},"feature_names":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"],"fitted_expression":"1.6971078570952467 * Cl_rear_s - 3.410910965502206","fitted_expression_hash":"eeceac7a87978bbbca9c8e0792c3c7292f1c7b2aa08940f31504cc2f8de6e62a","g_metadata":{"deployment_architecture":"mapped_shared","deployment_semantics":"mapped_shared","fit_augmentation":"G","fitted_heads":["rear_shared"],"front_rule":"front(x)=0.5*(f(x)-f(Gx)); antisymmetric projection","rear_rule":"lower(x)=-upper(Gx)","version":"legacy-mirror-front-odd-rear-swap-negate-v1"},"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","model_type":"symbolic","output":{"definition":"cylinder_surface_tangential_velocity/U0","name":"alpha","native_action_index":1,"native_action_order":["front","upper","lower"],"unit":"dimensionless"},"provenance":{"data":{"dataset_hash":"e018b5dadd19343480424aefa86eb0564f9dd6a9723451032cee4c88aa09dc89","paths":{"karman_re200":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-karman-20260718/karman/karman_re200/controlled.npz"},"per_scene_hashes":{"karman_re200":"553a3217f408b9bfa1a9864f2c07ec509727a742de906369bd69128bcd8a3e54"},"trajectories":{"karman_re200":[{"hash":"027f19ac5b858bc1dc8465e9d2ae93eb95760d8705a47da8c8814489204180c4","id":"karman_re200:trajectory-001","path":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-karman-20260718/karman/karman_re200/controlled.npz","sample_count":197}]}},"fit":{"command":"src/SR_analysis/stage_2_fit.py --scenes karman_re50,karman_re100,karman_re200,karman_re400 --mode per-scene --run-id article2-percase-refit-karman-20260720 --data-root src/SR_analysis/data/runs/article-joint-data-karman-20260718 --feature-set symmetry --feature-profile actual_only --deployment-architecture mapped_shared --fit-augmentation G --fit-purpose fixed-topology-refit --front-topology-expression=-0.381391*Cd_rear_a --rear-topology-expression=1.307782*Cl_rear_s-3.431209 --front-discovery-parent-path src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_front.json --rear-discovery-parent-path src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_rear_shared_upper.json","created_at":"2026-07-20T13:40:56.662178+00:00","deployment_architecture":"mapped_shared","diagnostic_metrics":{"mae":2.099878553243882,"max_error":5.730734240383761,"r2":0.5787723199954065,"rmse":2.5076971709926044},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_profile":"actual_only","feature_set":"symmetry","fit_augmentation":"G","fit_purpose":"fixed-topology-refit","g_augmented":true,"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","mode":"per-scene","n_samples":394,"offline_metrics":{"aggregate":{"mae":2.099878553243882,"max_error":5.730734240383761,"r2":0.5787723199954065,"rmse":2.5076971709926044},"per_case":{"karman_re200":{"mae":2.099878553243882,"max_error":5.730734240383761,"r2":0.5787723199954065,"rmse":2.5076971709926044}},"per_trajectory":{"karman_re200:trajectory-001":{"mae":2.099878553243882,"max_error":5.730734240383761,"r2":0.5787723199954065,"rmse":2.5076971709926044}}},"optimizer":{"cost":1238.8433849766488,"initial_constants":[1.307782,3.431209],"name":"scipy.optimize.least_squares","nfev":3,"refitted_constants":[1.6971078570952467,3.410910965502206],"success":true},"sample_weighting":"case_equal_trajectory_equal_within_case","scene_sample_counts":{"karman_re200":197},"source_n_samples":197,"static_safety":{"finite":true,"grid_size":9,"observed_range":[-9.308095491109563,2.486273560105151],"required_output_range":null,"safe":true,"within_output_range":true},"topology_expression":"1.307782*Cl_rear_s-3.431209"},"pysr":{"purpose":"fixed-topology-refit","used":false}},"role":"rear_shared","run_id":"article2-percase-refit-karman-20260720","schema_version":"1.0","training_scenes":["karman_re200"]} diff --git a/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re400_front.json b/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re400_front.json deleted file mode 100644 index c97141b..0000000 --- a/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re400_front.json +++ /dev/null @@ -1 +0,0 @@ -{"alignment_metadata":{"alignment":"causal_post_state_to_next_action","history_policy":"drop_warmup_zero_initialized","required_target":false,"sample_segments":{"steady":[50,200],"transient":[2,50]},"warmup":2},"anchor":"upper","artifact_id":"article2-percase-refit-karman-20260720-per-scene-front_odd-anchor-upper","deployment_expression":"-0.29472162211279335 * Cd_rear_a","deployment_expression_hash":"e00546b5f916e6dc62c458600a30a7f9a71bcfcc7467521607b98aafccd88531","discovery_parent":{"path":"/home/frank14f/DynamisLab/src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_front.json","sha256":"49af614459fefee3ebeed5c1e8761bdd1f0f8860a8d2e8f303dd1bb010ad67bf"},"dt_c_metadata":{"per_scene":{"karman_re400":0.4},"unit":"D/U0"},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_metadata":{"builder":"SR_analysis.utils.data_contracts.build_batch_dataset","definitions_module":"SR_analysis.utils.feature_builder","order":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"]},"feature_names":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"],"fitted_expression":"-0.29472162211279335 * Cd_rear_a","fitted_expression_hash":"e00546b5f916e6dc62c458600a30a7f9a71bcfcc7467521607b98aafccd88531","g_metadata":{"deployment_architecture":"mapped_shared","deployment_semantics":"mapped_shared","fit_augmentation":"G","fitted_heads":["front_odd"],"front_rule":"front(x)=0.5*(f(x)-f(Gx)); antisymmetric projection","rear_rule":"lower(x)=-upper(Gx)","version":"legacy-mirror-front-odd-rear-swap-negate-v1"},"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","model_type":"symbolic","output":{"definition":"cylinder_surface_tangential_velocity/U0","name":"alpha","native_action_index":0,"native_action_order":["front","upper","lower"],"unit":"dimensionless"},"provenance":{"data":{"dataset_hash":"3a3bcf2fc1424d669bb82099a717bf2261cc26fab51a1b5465b0f31461916e19","paths":{"karman_re400":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-karman-20260718/karman/karman_re400/controlled.npz"},"per_scene_hashes":{"karman_re400":"30b602d592c64cec6d037fb9210c2cc745912ede96412d1241fe88ab873bd297"},"trajectories":{"karman_re400":[{"hash":"338306a36182af77bd510216dba14c2d0937a65742098abfa32f5af7f2d49677","id":"karman_re400:trajectory-001","path":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-karman-20260718/karman/karman_re400/controlled.npz","sample_count":197}]}},"fit":{"command":"src/SR_analysis/stage_2_fit.py --scenes karman_re50,karman_re100,karman_re200,karman_re400 --mode per-scene --run-id article2-percase-refit-karman-20260720 --data-root src/SR_analysis/data/runs/article-joint-data-karman-20260718 --feature-set symmetry --feature-profile actual_only --deployment-architecture mapped_shared --fit-augmentation G --fit-purpose fixed-topology-refit --front-topology-expression=-0.381391*Cd_rear_a --rear-topology-expression=1.307782*Cl_rear_s-3.431209 --front-discovery-parent-path src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_front.json --rear-discovery-parent-path src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_rear_shared_upper.json","created_at":"2026-07-20T13:40:56.675951+00:00","deployment_architecture":"mapped_shared","diagnostic_metrics":{"mae":0.1479559237498144,"max_error":0.8564633439697262,"r2":0.9735052865964828,"rmse":0.19880018636789204},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_profile":"actual_only","feature_set":"symmetry","fit_augmentation":"G","fit_purpose":"fixed-topology-refit","g_augmented":true,"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","mode":"per-scene","n_samples":394,"offline_metrics":{"aggregate":{"mae":0.1479559237498144,"max_error":0.8564633439697262,"r2":0.9735052865964828,"rmse":0.19880018636789204},"per_case":{"karman_re400":{"mae":0.1479559237498144,"max_error":0.8564633439697262,"r2":0.9735052865964828,"rmse":0.19880018636789204}},"per_trajectory":{"karman_re400:trajectory-001":{"mae":0.1479559237498144,"max_error":0.8564633439697262,"r2":0.9735052865964828,"rmse":0.19880018636789204}}},"optimizer":{"cost":7.785738277681992,"initial_constants":[0.381391],"name":"scipy.optimize.least_squares","nfev":3,"refitted_constants":[0.29472162211279335],"success":true},"sample_weighting":"case_equal_trajectory_equal_within_case","scene_sample_counts":{"karman_re400":197},"source_n_samples":197,"static_safety":{"finite":true,"grid_size":9,"observed_range":[-2.452399761549377,2.452399761549377],"required_output_range":null,"safe":true,"within_output_range":true},"topology_expression":"-0.381391*Cd_rear_a"},"pysr":{"purpose":"fixed-topology-refit","used":false}},"role":"front_odd","run_id":"article2-percase-refit-karman-20260720","schema_version":"1.0","training_scenes":["karman_re400"]} diff --git a/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re400_rear_shared_upper.json b/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re400_rear_shared_upper.json deleted file mode 100644 index d953e33..0000000 --- a/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re400_rear_shared_upper.json +++ /dev/null @@ -1 +0,0 @@ -{"alignment_metadata":{"alignment":"causal_post_state_to_next_action","history_policy":"drop_warmup_zero_initialized","required_target":false,"sample_segments":{"steady":[50,200],"transient":[2,50]},"warmup":2},"anchor":"upper","artifact_id":"article2-percase-refit-karman-20260720-per-scene-rear_shared-anchor-upper","deployment_expression":"1.054929993313117 * Cl_rear_s - 2.5544553575675293","deployment_expression_hash":"6b2aac7935b455e3d7044989565e4b60d52c56bc7d44ba5989887da7e638250e","discovery_parent":{"path":"/home/frank14f/DynamisLab/src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_rear_shared_upper.json","sha256":"f24114508350fd6a9bbd6e5aa93713b2b136590539602c0ba6334e3af7508982"},"dt_c_metadata":{"per_scene":{"karman_re400":0.4},"unit":"D/U0"},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_metadata":{"builder":"SR_analysis.utils.data_contracts.build_batch_dataset","definitions_module":"SR_analysis.utils.feature_builder","order":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"]},"feature_names":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"],"fitted_expression":"1.054929993313117 * Cl_rear_s - 2.5544553575675293","fitted_expression_hash":"6b2aac7935b455e3d7044989565e4b60d52c56bc7d44ba5989887da7e638250e","g_metadata":{"deployment_architecture":"mapped_shared","deployment_semantics":"mapped_shared","fit_augmentation":"G","fitted_heads":["rear_shared"],"front_rule":"front(x)=0.5*(f(x)-f(Gx)); antisymmetric projection","rear_rule":"lower(x)=-upper(Gx)","version":"legacy-mirror-front-odd-rear-swap-negate-v1"},"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","model_type":"symbolic","output":{"definition":"cylinder_surface_tangential_velocity/U0","name":"alpha","native_action_index":1,"native_action_order":["front","upper","lower"],"unit":"dimensionless"},"provenance":{"data":{"dataset_hash":"3a3bcf2fc1424d669bb82099a717bf2261cc26fab51a1b5465b0f31461916e19","paths":{"karman_re400":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-karman-20260718/karman/karman_re400/controlled.npz"},"per_scene_hashes":{"karman_re400":"30b602d592c64cec6d037fb9210c2cc745912ede96412d1241fe88ab873bd297"},"trajectories":{"karman_re400":[{"hash":"338306a36182af77bd510216dba14c2d0937a65742098abfa32f5af7f2d49677","id":"karman_re400:trajectory-001","path":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-karman-20260718/karman/karman_re400/controlled.npz","sample_count":197}]}},"fit":{"command":"src/SR_analysis/stage_2_fit.py --scenes karman_re50,karman_re100,karman_re200,karman_re400 --mode per-scene --run-id article2-percase-refit-karman-20260720 --data-root src/SR_analysis/data/runs/article-joint-data-karman-20260718 --feature-set symmetry --feature-profile actual_only --deployment-architecture mapped_shared --fit-augmentation G --fit-purpose fixed-topology-refit --front-topology-expression=-0.381391*Cd_rear_a --rear-topology-expression=1.307782*Cl_rear_s-3.431209 --front-discovery-parent-path src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_front.json --rear-discovery-parent-path src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_rear_shared_upper.json","created_at":"2026-07-20T13:40:56.733569+00:00","deployment_architecture":"mapped_shared","diagnostic_metrics":{"mae":2.180989822453082,"max_error":4.877274411462385,"r2":0.32847453250491054,"rmse":2.4580453982854116},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_profile":"actual_only","feature_set":"symmetry","fit_augmentation":"G","fit_purpose":"fixed-topology-refit","g_augmented":true,"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","mode":"per-scene","n_samples":394,"offline_metrics":{"aggregate":{"mae":2.180989822453082,"max_error":4.877274411462385,"r2":0.32847453250491054,"rmse":2.4580453982854116},"per_case":{"karman_re400":{"mae":2.180989822453082,"max_error":4.877274411462385,"r2":0.32847453250491054,"rmse":2.4580453982854116}},"per_trajectory":{"karman_re400:trajectory-001":{"mae":2.180989822453082,"max_error":4.877274411462385,"r2":0.32847453250491054,"rmse":2.4580453982854116}}},"optimizer":{"cost":1190.2714744663203,"initial_constants":[1.307782,3.431209],"name":"scipy.optimize.least_squares","nfev":3,"refitted_constants":[1.054929993313117,2.5544553575675293],"success":true},"sample_weighting":"case_equal_trajectory_equal_within_case","scene_sample_counts":{"karman_re400":197},"source_n_samples":197,"static_safety":{"finite":true,"grid_size":9,"observed_range":[-7.848958918828123,2.740048203693064],"required_output_range":null,"safe":true,"within_output_range":true},"topology_expression":"1.307782*Cl_rear_s-3.431209"},"pysr":{"purpose":"fixed-topology-refit","used":false}},"role":"rear_shared","run_id":"article2-percase-refit-karman-20260720","schema_version":"1.0","training_scenes":["karman_re400"]} diff --git a/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re50_front.json b/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re50_front.json deleted file mode 100644 index dd38eff..0000000 --- a/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re50_front.json +++ /dev/null @@ -1 +0,0 @@ -{"alignment_metadata":{"alignment":"causal_post_state_to_next_action","history_policy":"drop_warmup_zero_initialized","required_target":false,"sample_segments":{"steady":[50,200],"transient":[2,50]},"warmup":2},"anchor":"upper","artifact_id":"article2-percase-refit-karman-20260720-per-scene-front_odd-anchor-upper","deployment_expression":"-0.5835788969350136 * Cd_rear_a","deployment_expression_hash":"2408f83e555cb7e289660b533c8ee49036bd86e8d985db3bf4fafe69682fb412","discovery_parent":{"path":"/home/frank14f/DynamisLab/src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_front.json","sha256":"49af614459fefee3ebeed5c1e8761bdd1f0f8860a8d2e8f303dd1bb010ad67bf"},"dt_c_metadata":{"per_scene":{"karman_re50":0.4},"unit":"D/U0"},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_metadata":{"builder":"SR_analysis.utils.data_contracts.build_batch_dataset","definitions_module":"SR_analysis.utils.feature_builder","order":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"]},"feature_names":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"],"fitted_expression":"-0.5835788969350136 * Cd_rear_a","fitted_expression_hash":"2408f83e555cb7e289660b533c8ee49036bd86e8d985db3bf4fafe69682fb412","g_metadata":{"deployment_architecture":"mapped_shared","deployment_semantics":"mapped_shared","fit_augmentation":"G","fitted_heads":["front_odd"],"front_rule":"front(x)=0.5*(f(x)-f(Gx)); antisymmetric projection","rear_rule":"lower(x)=-upper(Gx)","version":"legacy-mirror-front-odd-rear-swap-negate-v1"},"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","model_type":"symbolic","output":{"definition":"cylinder_surface_tangential_velocity/U0","name":"alpha","native_action_index":0,"native_action_order":["front","upper","lower"],"unit":"dimensionless"},"provenance":{"data":{"dataset_hash":"b95c6f35a5f7f73f38051d0f720261d114346440b7ea0f89830d0f782f81cb97","paths":{"karman_re50":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-karman-20260718/karman/karman_re50/controlled.npz"},"per_scene_hashes":{"karman_re50":"1477703d46c51dd32f3cb9e3754d1bae973ed784500d0e9d0ffa7afda25ea04a"},"trajectories":{"karman_re50":[{"hash":"a1780dd327d329ac154d17cf733a7c4141627d01cd7f07398ece04008e51c0d7","id":"karman_re50:trajectory-001","path":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-karman-20260718/karman/karman_re50/controlled.npz","sample_count":197}]}},"fit":{"command":"src/SR_analysis/stage_2_fit.py --scenes karman_re50,karman_re100,karman_re200,karman_re400 --mode per-scene --run-id article2-percase-refit-karman-20260720 --data-root src/SR_analysis/data/runs/article-joint-data-karman-20260718 --feature-set symmetry --feature-profile actual_only --deployment-architecture mapped_shared --fit-augmentation G --fit-purpose fixed-topology-refit --front-topology-expression=-0.381391*Cd_rear_a --rear-topology-expression=1.307782*Cl_rear_s-3.431209 --front-discovery-parent-path src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_front.json --rear-discovery-parent-path src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_rear_shared_upper.json","created_at":"2026-07-20T13:40:56.515810+00:00","deployment_architecture":"mapped_shared","diagnostic_metrics":{"mae":1.5402098101682624,"max_error":2.701252546683189,"r2":0.28270513879302694,"rmse":1.6988826421169128},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_profile":"actual_only","feature_set":"symmetry","fit_augmentation":"G","fit_purpose":"fixed-topology-refit","g_augmented":true,"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","mode":"per-scene","n_samples":394,"offline_metrics":{"aggregate":{"mae":1.5402098101682624,"max_error":2.701252546683189,"r2":0.28270513879302694,"rmse":1.6988826421169128},"per_case":{"karman_re50":{"mae":1.5402098101682624,"max_error":2.701252546683189,"r2":0.28270513879302694,"rmse":1.6988826421169128}},"per_trajectory":{"karman_re50:trajectory-001":{"mae":1.5402098101682624,"max_error":2.701252546683189,"r2":0.28270513879302694,"rmse":1.6988826421169128}}},"optimizer":{"cost":568.5818396421696,"initial_constants":[0.381391],"name":"scipy.optimize.least_squares","nfev":3,"refitted_constants":[0.5835788969350136],"success":true},"sample_weighting":"case_equal_trajectory_equal_within_case","scene_sample_counts":{"karman_re50":197},"source_n_samples":197,"static_safety":{"finite":true,"grid_size":9,"observed_range":[-2.1324229019040257,2.1324229019040257],"required_output_range":null,"safe":true,"within_output_range":true},"topology_expression":"-0.381391*Cd_rear_a"},"pysr":{"purpose":"fixed-topology-refit","used":false}},"role":"front_odd","run_id":"article2-percase-refit-karman-20260720","schema_version":"1.0","training_scenes":["karman_re50"]} diff --git a/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re50_rear_shared_upper.json b/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re50_rear_shared_upper.json deleted file mode 100644 index f32afb7..0000000 --- a/src/SR_analysis/results/runs/article2-percase-refit-karman-20260720/formulas/karman_re50_rear_shared_upper.json +++ /dev/null @@ -1 +0,0 @@ -{"alignment_metadata":{"alignment":"causal_post_state_to_next_action","history_policy":"drop_warmup_zero_initialized","required_target":false,"sample_segments":{"steady":[50,200],"transient":[2,50]},"warmup":2},"anchor":"upper","artifact_id":"article2-percase-refit-karman-20260720-per-scene-rear_shared-anchor-upper","deployment_expression":"0.22124106819225497 * Cl_rear_s - 3.582021485268801","deployment_expression_hash":"5c8498a409d5c918db3239cf09706ac54d55b1f00268bcba656117e4b57523ce","discovery_parent":{"path":"/home/frank14f/DynamisLab/src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_rear_shared_upper.json","sha256":"f24114508350fd6a9bbd6e5aa93713b2b136590539602c0ba6334e3af7508982"},"dt_c_metadata":{"per_scene":{"karman_re50":0.4},"unit":"D/U0"},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_metadata":{"builder":"SR_analysis.utils.data_contracts.build_batch_dataset","definitions_module":"SR_analysis.utils.feature_builder","order":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"]},"feature_names":["u_s","u_a","u_c","v_s","v_a","v_c","Cd_F","Cd_rear_s","Cd_rear_a","Cd_tot","Cl_F","Cl_rear_s","Cl_rear_a","Cl_tot"],"fitted_expression":"0.22124106819225497 * Cl_rear_s - 3.582021485268801","fitted_expression_hash":"5c8498a409d5c918db3239cf09706ac54d55b1f00268bcba656117e4b57523ce","g_metadata":{"deployment_architecture":"mapped_shared","deployment_semantics":"mapped_shared","fit_augmentation":"G","fitted_heads":["rear_shared"],"front_rule":"front(x)=0.5*(f(x)-f(Gx)); antisymmetric projection","rear_rule":"lower(x)=-upper(Gx)","version":"legacy-mirror-front-odd-rear-swap-negate-v1"},"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","model_type":"symbolic","output":{"definition":"cylinder_surface_tangential_velocity/U0","name":"alpha","native_action_index":1,"native_action_order":["front","upper","lower"],"unit":"dimensionless"},"provenance":{"data":{"dataset_hash":"b95c6f35a5f7f73f38051d0f720261d114346440b7ea0f89830d0f782f81cb97","paths":{"karman_re50":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-karman-20260718/karman/karman_re50/controlled.npz"},"per_scene_hashes":{"karman_re50":"1477703d46c51dd32f3cb9e3754d1bae973ed784500d0e9d0ffa7afda25ea04a"},"trajectories":{"karman_re50":[{"hash":"a1780dd327d329ac154d17cf733a7c4141627d01cd7f07398ece04008e51c0d7","id":"karman_re50:trajectory-001","path":"/home/frank14f/DynamisLab/src/SR_analysis/data/runs/article-joint-data-karman-20260718/karman/karman_re50/controlled.npz","sample_count":197}]}},"fit":{"command":"src/SR_analysis/stage_2_fit.py --scenes karman_re50,karman_re100,karman_re200,karman_re400 --mode per-scene --run-id article2-percase-refit-karman-20260720 --data-root src/SR_analysis/data/runs/article-joint-data-karman-20260718 --feature-set symmetry --feature-profile actual_only --deployment-architecture mapped_shared --fit-augmentation G --fit-purpose fixed-topology-refit --front-topology-expression=-0.381391*Cd_rear_a --rear-topology-expression=1.307782*Cl_rear_s-3.431209 --front-discovery-parent-path src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_front.json --rear-discovery-parent-path src/SR_analysis/results/runs/article-joint-karman-symmetry-s0-20260718/formulas/joint_rear_shared_upper.json","created_at":"2026-07-20T13:40:56.593679+00:00","deployment_architecture":"mapped_shared","diagnostic_metrics":{"mae":0.8173865388047218,"max_error":2.5468694656114192,"r2":0.022057303194492284,"rmse":1.0151963163022433},"environment":{"numpy":"2.2.6","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","pysr":null,"python":"3.10.20"},"feature_profile":"actual_only","feature_set":"symmetry","fit_augmentation":"G","fit_purpose":"fixed-topology-refit","g_augmented":true,"git_sha":"ca8ee5f238ee58eaaf48027ad026c35784f76d4d","mode":"per-scene","n_samples":394,"offline_metrics":{"aggregate":{"mae":0.8173865388047218,"max_error":2.5468694656114192,"r2":0.022057303194492284,"rmse":1.0151963163022433},"per_case":{"karman_re50":{"mae":0.8173865388047218,"max_error":2.5468694656114192,"r2":0.022057303194492284,"rmse":1.0151963163022433}},"per_trajectory":{"karman_re50:trajectory-001":{"mae":0.8173865388047218,"max_error":2.5468694656114192,"r2":0.022057303194492284,"rmse":1.0151963163022433}}},"optimizer":{"cost":203.03284144482782,"initial_constants":[1.307782,3.431209],"name":"scipy.optimize.least_squares","nfev":3,"refitted_constants":[0.22124106819225497,3.582021485268801],"success":true},"sample_weighting":"case_equal_trajectory_equal_within_case","scene_sample_counts":{"karman_re50":197},"source_n_samples":197,"static_safety":{"finite":true,"grid_size":9,"observed_range":[-3.8903943217292403,-3.2736486488083614],"required_output_range":null,"safe":true,"within_output_range":true},"topology_expression":"1.307782*Cl_rear_s-3.431209"},"pysr":{"purpose":"fixed-topology-refit","used":false}},"role":"rear_shared","run_id":"article2-percase-refit-karman-20260720","schema_version":"1.0","training_scenes":["karman_re50"]} diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/07_flow_field_comparison_karman_re100.json b/src/SR_analysis/results/runs/article2-plotting-package-20260721/07_flow_field_comparison_karman_re100.json new file mode 100644 index 0000000..52ea0fc --- /dev/null +++ b/src/SR_analysis/results/runs/article2-plotting-package-20260721/07_flow_field_comparison_karman_re100.json @@ -0,0 +1 @@ +{"contracts":{"cfd_logical_device":0,"controlled":"stage_3_validate.prepare_plan/build_karman_environment/build_policy","ppo_model_device":"cpu","target":"exact build_karman_cloak_env geometry, stabilization, and sample interval; target has no pinball"},"description":"single deterministic downstream center-sensor limit-cycle phase snapshot; not full-field identity or an ensemble","field":{"crop":{"extent_xD_yD":[7.0,48.0,-5.975,5.975],"x_slice":[140,961],"y_slice":[136,376]},"geometry_D":{"disturbance":[10.0,0.0,1.0],"pinball":[[30.0,0.0,0.5],[31.3,0.75,0.5],[31.3,-0.75,0.5]],"sensors":[[40.0,2.0],[40.0,0.0],[40.0,-2.0]]},"quantity":"omega_z D/U0","shared_symmetric_vmax_percentile":{"percentile":99.5,"vmax":4.820084953308124},"source":"vorticity_from_ddf","source_shape_order":"(NX,NY)","stored_shape_order":"(NY,NX)"},"hashes":{"alignment_metadata":"fc95749546c8ee63bcf55d38c4f7b80aaa418e337a0e4a51fbe8ad4e0daad7d3","config":"bcf32eb026ee2935fa236b5c65f08b05a613988a2464614d3c74183bd1cc4952","exporter":"ce2a93f42c05b2b50d9d7b4063a2db9be53014011d81c0cf85a0bc0bc2fde42c","formula_front":"bb85a2110e86925119d2c2ac45de7ec919cad6bed82f3faa2c36d648cc18b0f3","formula_pair":"a678f2019e0da7a0e1263f0c911b377106a668a6716ba93d6efd78f58de0a3f1","formula_rear":"460c2b9b568440f5bd4aacfdd30c29f60dd705821d115598af80e9d21767b05b","npz":"3f26e963b6379e1998cae242248eca4e9fcb7205420ae0fe926049aac70bd93f","pdf":"1a60bd8f17f7c6f78a443f407ccca771b53b5ae90205f800c8a0ef5ac4bd91ee","png":"1a74b9cd14fdc3f45da90010ab59c35c177be2a5c1a927e5b24684f77a4f2380","ppo_model":"148240c1dcb8b11d8e5a0a9e991f6874c4e799c9744707bf0fb32a8efeb0468d"},"paths":{"formula_front":"/home/frank14f/DynamisLab/src/SR_analysis/results/runs/article-refit-karman-topology-a-20260718/formulas/joint_front.json","formula_rear":"/home/frank14f/DynamisLab/src/SR_analysis/results/runs/article-refit-karman-topology-a-20260718/formulas/joint_rear_shared_upper.json","npz":"src/SR_analysis/results/runs/article2-plotting-package-20260721/07_flow_field_comparison_karman_re100.npz","ppo_model":"/home/frank14f/DynamisLab/models/old/d1a3o12_re100.zip"},"phase_matching":{"candidate_count_per_trajectory":50,"direction_check":"all selected local unwrapped-phase derivatives have the same sign","figure06_alignment_context_only":{"alignment_method":"mean-centered normalized circular cross-correlation; smallest maximizing roll selected","alignment_signal":"sensors_center_uy","control_dt_D_over_U0":0.4,"displayed_target_cycles":3,"roll_lags_samples":{"PPO":0,"SR":-4,"Target":0},"samples":50,"scene":"karman_re100","source_package":"article2-timeseries-csv-20260720","target_actions_plotted":false,"target_index_start_inclusive":96,"target_index_stop_exclusive":146,"target_t_D_end":58.0,"target_t_D_start":38.4},"indices":{"PPO":117,"SR":139,"Target":132},"local_phase_direction":{"PPO":1,"SR":1,"Target":1},"method":"one run per condition with same-sample stable-window field capture; exhaustive joint phase search","objective_formula":"|wrap(theta_PPO-theta_Target)| + |wrap(theta_SR-theta_Target)| + w*(|i_PPO-i_Target|+|i_SR-i_Target|)","objective_terms":{"angle_error_sum_rad":0.052774253843211216,"objective":0.07477425384321121,"temporal_distance_samples":22,"temporal_penalty_rad":0.022,"temporal_weight_rad_per_sample":0.001},"phase_angles_rad":{"PPO":-2.377409239966042,"SR":-2.3246349861228306,"Target":-2.3634943521378413},"phase_definition":"theta=atan2(z(center_uy), z(center_ux)); each trajectory standardized separately over stable window","stable_window_center_mean":{"PPO":[1.0087128973007202,-0.04213709145784378],"SR":[0.9318643200397492,-0.027546274811029434],"Target":[1.1288272762298583,0.024781318455934523]},"stable_window_center_std":{"PPO":[0.014080033556814659,0.43356319595610704],"SR":[0.03199349238876583,0.42947474587980883],"Target":[0.017061010104864743,0.4609903059172819]},"target_window":[96,146],"wrapped_angle_errors_rad":{"PPO":0.013914887828200495,"SR":0.03885936601501072}},"provenance":{"CUDA_VISIBLE_DEVICES":"1","command":"src/SR_analysis/tools/export_flow_comparison.py --scene karman_re100 --device 0 --model-device cpu --replace","created_utc":"2026-07-24T03:49:24.629472+00:00","git_sha":"8d760145de79dd3fbcd70213a9372e219d6757cf","numpy":"1.26.4","platform":"Linux-6.8.0-124-generic-x86_64-with-glibc2.35","python":"3.10.13 (main, Sep 11 2023, 13:44:35) [GCC 11.2.0]"},"record_hash":"a77dcfd2eddb1e3a56b565f1309d3869e26a24b2e0f4499bd752751576137c6b","sample_coupling":{"candidate_count_per_condition":50,"candidate_dtype":"float32","candidate_field_shape_yx":[512,1280],"candidate_host_memory_bytes":393216000,"candidate_host_memory_mib":375.0,"candidate_trace_index_mapping":"candidate_index = trace_index - stable_window_start; exact contiguous mapping asserted before selection","candidate_window":[96,146],"contract":"for every condition, the selected field and phase-diagnostic sensor values come from the same CFD sample in the same run","disk_contract":"only the three selected full fields and complete sensor traces are stored; stable-window candidates are memory-only"},"scene":"karman_re100","schema_version":"sr-flow-comparison-v2","selected":{"PPO":{"candidate_index":21,"index":117,"phase_angle_rad":-2.377409239966042,"t_D_over_U0":46.800000000000004,"wrapped_phase_error_rad":0.013914887828200495},"SR":{"candidate_index":43,"index":139,"phase_angle_rad":-2.3246349861228306,"t_D_over_U0":55.6,"wrapped_phase_error_rad":0.03885936601501072},"Target":{"candidate_index":36,"index":132,"phase_angle_rad":-2.3634943521378413,"t_D_over_U0":52.800000000000004,"wrapped_phase_error_rad":0.0}}} diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/README.md b/src/SR_analysis/results/runs/article2-plotting-package-20260721/README.md new file mode 100644 index 0000000..ccdae40 --- /dev/null +++ b/src/SR_analysis/results/runs/article2-plotting-package-20260721/README.md @@ -0,0 +1,13 @@ +# SR publication plotting package + +This package contains five generated publication figures, two replacement tables, and two PPT-ready 16:9 summary pages. All values are derived from frozen canonical artifacts without formula refitting. + +- `01_training_case_performance`: panel (a) compares PPO and SR absolute closed-loop legacy DTW similarity at **400 control steps** for all seven training cases; panel (b) reports each controller's 400-minus-200 change. Here 200 and 400 are run durations in control steps, not Reynolds numbers. +- `02_pointwise_generalization`: 200-step SR results at training conditions and sampled unseen interpolation/extrapolation conditions. Each unseen point is one realization. Categories are unconnected because the evidence does not establish a continuous parameter law or statistical robustness. +- `03_steady_rotation_calibration`: the retained disturbance-free constant rear-rotation sweep diagnostic. +- `06_example_timeseries_karman_re100`: late-window phase portraits and PPO/SR actions over approximately three target cycles. Target indices 96–145 (`t_D=38.4`–`58.0`) are used. PPO and SR segments at the same indices are circularly aligned to target `sensors_center_uy` by mean-centered normalized cross-correlation; deterministic roll lags are PPO=0 and SR=-4 samples. No target actions exist or are plotted. +- `07_flow_field_comparison_karman_re100` (generated by `tools/export_flow_comparison.py`): target wake, PPO-controlled pinball, and canonical SR-controlled pinball vorticity at one deterministic stable-cycle phase. Over indices [96,146), each trajectory standardizes downstream center-sensor (ux,uy) separately and defines theta=atan2(v_z,u_z). An exhaustive joint search minimizes the two wrapped angular errors plus 0.001 rad/sample times total temporal separation, with a same-direction branch check. Each condition runs once through index 145 while all 50 stable-window fields are held as float32 host-memory candidates (375 MiB total for three 512x1280 buffers); the selected field and phase diagnostic are therefore the exact same CFD sample from the same run. This matches center-sensor limit-cycle phase; it does not assert six-sensor state equality or exact full-field identity. It stores one compressed NPZ, JSON provenance, and PNG/PDF. This is a single snapshot, not an ensemble or uncertainty estimate. +- `tables/offline_action_rmse.*`: offline SR-vs-PPO next-action RMSE on causally aligned PPO-visited states. This is not closed-loop performance. +- `tables/term_deletion.*`: actual 40-step closed-loop deletion results, canonical formula identities, same-window parent comparisons, and aggregate mean/min summaries. + +The `presentation/` directory contains two white-background 16:9 PNG/PDF pages and its source-artifact README. PNG and PDF files share each retained figure stem. `phase_alignment.json` records the exact Figure 06 selection/alignment contract. `manifest.json` hashes every package CSV, Markdown, JSON/NPZ metadata or field artifact, and publication figure. diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/01_training_case_performance.pdf b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/01_training_case_performance.pdf index 139c53a..e88813b 100644 Binary files a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/01_training_case_performance.pdf and b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/01_training_case_performance.pdf differ diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/01_training_case_performance.png b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/01_training_case_performance.png index 1b7dfd0..f836b4f 100644 Binary files a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/01_training_case_performance.png and b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/01_training_case_performance.png differ diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/02_pointwise_generalization.pdf b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/02_pointwise_generalization.pdf index e3a67b2..383f930 100644 Binary files a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/02_pointwise_generalization.pdf and b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/02_pointwise_generalization.pdf differ diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/02_pointwise_generalization.png b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/02_pointwise_generalization.png index 6589302..4536bd2 100644 Binary files a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/02_pointwise_generalization.png and b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/02_pointwise_generalization.png differ diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/03_steady_rotation_calibration.pdf b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/03_steady_rotation_calibration.pdf index 31bc5d9..7e50fd7 100644 Binary files a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/03_steady_rotation_calibration.pdf and b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/03_steady_rotation_calibration.pdf differ diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/03_steady_rotation_calibration.png b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/03_steady_rotation_calibration.png index 6ba1c81..1235577 100644 Binary files a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/03_steady_rotation_calibration.png and b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/03_steady_rotation_calibration.png differ diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/04_offline_action_rmse.pdf b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/04_offline_action_rmse.pdf deleted file mode 100644 index c427ce7..0000000 Binary files a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/04_offline_action_rmse.pdf and /dev/null differ diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/04_offline_action_rmse.png b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/04_offline_action_rmse.png deleted file mode 100644 index 1927c09..0000000 Binary files a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/04_offline_action_rmse.png and /dev/null differ diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/05_term_deletion.pdf b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/05_term_deletion.pdf deleted file mode 100644 index f72d821..0000000 Binary files a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/05_term_deletion.pdf and /dev/null differ diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/05_term_deletion.png b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/05_term_deletion.png deleted file mode 100644 index 998637b..0000000 Binary files a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/05_term_deletion.png and /dev/null differ diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/06_example_timeseries_karman_re100.pdf b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/06_example_timeseries_karman_re100.pdf index f8b86a3..e7061e4 100644 Binary files a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/06_example_timeseries_karman_re100.pdf and b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/06_example_timeseries_karman_re100.pdf differ diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/06_example_timeseries_karman_re100.png b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/06_example_timeseries_karman_re100.png index e78afbe..c89e8b1 100644 Binary files a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/06_example_timeseries_karman_re100.png and b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/06_example_timeseries_karman_re100.png differ diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/07_flow_field_comparison_karman_re100.pdf b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/07_flow_field_comparison_karman_re100.pdf new file mode 100644 index 0000000..73e8fc8 Binary files /dev/null and b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/07_flow_field_comparison_karman_re100.pdf differ diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/07_flow_field_comparison_karman_re100.png b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/07_flow_field_comparison_karman_re100.png new file mode 100644 index 0000000..a5db9f8 Binary files /dev/null and b/src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/07_flow_field_comparison_karman_re100.png differ diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/manifest.json b/src/SR_analysis/results/runs/article2-plotting-package-20260721/manifest.json index 48f6795..fbcf088 100644 --- a/src/SR_analysis/results/runs/article2-plotting-package-20260721/manifest.json +++ b/src/SR_analysis/results/runs/article2-plotting-package-20260721/manifest.json @@ -1,364 +1 @@ -{ - "schema_version": "sr-plotting-package-v1", - "source_policy": "immutable article and Article2 artifacts; no scientific refit", - "summary": { - "offline_rows": 1379, - "offline_term_rows": 1379, - "closed_loop_term_rows": 2793, - "ablation_rows": 24, - "scaling_rows": 37, - "steady_timeseries_cases": 7, - "long_ppo_scenes": 7, - "diagnostic_figures": 6 - }, - "artifacts": [ - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/ablation_summary.csv", - "sha256": "66d1f25c217d82a8151b0a53f774f18d3dfac8d1be2c20354ebb441494705056" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/01_training_case_performance.pdf", - "sha256": "7e1c47efe5155dd305be39984ea6e17553904135b47cb97576e2ac34d692fb27" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/01_training_case_performance.png", - "sha256": "bbfb3204a31bab7e59c4c2a8a4d4812acbd7470d144dbb5dff5ada3473805dc4" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/02_pointwise_generalization.pdf", - "sha256": "ddb5fad80d5ac371279855b2c3cf523cceb850c6b27f593013da4b5316f9487d" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/02_pointwise_generalization.png", - "sha256": "1be86def0f5eb4c66673d6d4f7c80e5e3ae11ad15837647a7e0f23556e16786b" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/03_steady_rotation_calibration.pdf", - "sha256": "d2aacd625219fd0382a9ea8ce232343dfa5f98d6cc9444f406e80698923dcbd2" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/03_steady_rotation_calibration.png", - "sha256": "e0815d50108555740ae496dd8617e4c700f89214cd4a704ca5546e2661cd9a5d" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/04_offline_action_rmse.pdf", - "sha256": "da34af575ceb38b5edf82096ca99c1b1a475de1eddb5c34b14e75590181fe09a" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/04_offline_action_rmse.png", - "sha256": "82fc536d9934f974a67954b4cdb0e0650b4592659027adb5072b6a07b393c7b2" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/05_term_deletion.pdf", - "sha256": "c04500cedf1fc766ca1897cac5333a78cc79905d95b9d9c85988e24cadf2d92f" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/05_term_deletion.png", - "sha256": "29aa4572d1dbd64c1482203fb7d738980485a59b802d3bb46cf69e6328d29846" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/06_example_timeseries_karman_re100.pdf", - "sha256": "974fa0f6e6f7ab13f33ebcc0b17406f1a1962b65922526fb72520d75df6f7d3e" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/06_example_timeseries_karman_re100.png", - "sha256": "4364cd75135cd72bada98c6fb786e291eead3980fd02f04720daac84cc45599c" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_0.75L/ppo_dtw_convergence.csv", - "sha256": "2f49211f85c44c2b940d131b5178fb7aecbd109a4d1b9910f3a2b7983833e69a" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_0.75L/ppo_long.csv", - "sha256": "e7d7023da3a64a9d27d4ec7237a6e96faaeeba268c75c317b4b64688818e5e59" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_0.75L/ppo_wide.csv", - "sha256": "f9ecbf75e62ec55229c9623329f08898258f25f3dc47ecfabedd3984f888c764" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_0.75L/target_long.csv", - "sha256": "f530a259a0ec6efa5666766070e29f3479f3f7c6854c2e1345b51f777e5a66d6" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_0.75L/target_wide.csv", - "sha256": "7e5e714023817974c460d8eaf517399a96113f27ad1fd524a74c211d0f17fd88" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1.5L/ppo_dtw_convergence.csv", - "sha256": "05b28809c65fb95a316ca90da3e603d7a3507cae1cbab861fe5b89bc20f26e79" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1.5L/ppo_long.csv", - "sha256": "a804a82de5ff9fbe5d11637a36fb6faa7df386c87e528a3ba16fcb8e6a3ae872" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1.5L/ppo_wide.csv", - "sha256": "68bd8553c94e4d2d2529b9f32503addc9c0a295368049d21312dbba892de824e" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1.5L/target_long.csv", - "sha256": "3618da795a8a462d548c04d570bbdb1572750a07abcf36ef6fff2cdf0a002eb3" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1.5L/target_wide.csv", - "sha256": "0c0f9eb5a1c8a333c10dc3fcf92ba09e098dfa1e53268d33b04ea849abc813c8" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1L/ppo_dtw_convergence.csv", - "sha256": "ca1675823570918a36ff18846ab087ec746b014f3b1c7ea2b5314194b3b243d5" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1L/ppo_long.csv", - "sha256": "55d8024bda6a36a0aaf4536f7a390d9bffb8b2d9daf91e8edcf5d8f555e17528" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1L/ppo_wide.csv", - "sha256": "1fc1258228acf0a90e089112405e70c274ea07077ca285068f45e69e2b13d203" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1L/target_long.csv", - "sha256": "999f5b16c15e6c8adf98d57659be875886adbd52d098d8aac2d5ea7d669bf51c" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1L/target_wide.csv", - "sha256": "9ac8ada9234e9e638c877714514b2ef0ef499e40a13877b1291310edd15c1cd1" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re100/ppo_dtw_convergence.csv", - "sha256": "4769c68614a41488c2f8596b40d6fdbf06bf5106f24c5be410de8e8ea82a6800" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re100/ppo_long.csv", - "sha256": "be263dfa5945e6209aed92523b2cbf61f0e7d6cbb20b5357b1f27d2ecbd79529" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re100/ppo_wide.csv", - "sha256": "23f9bf3a364a8551f2d527ef8d21ce378bcf6abbf833b19b1d4b7314bc53a627" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re100/target_long.csv", - "sha256": "6894bdb4d74329574be653080a7010e6104c3798f843373924b79ce4bafb203a" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re100/target_wide.csv", - "sha256": "b871501e93384d4a9bfb5ed29172a171a26fd61415ece3029d65b13558c4125f" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re200/ppo_dtw_convergence.csv", - "sha256": "fcca94b8220a192644eb0103432dd8bcf700020be8115d34d18f05d4dc88faed" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re200/ppo_long.csv", - "sha256": "25383c90882f8b290aaf4643135803e3afd1e492f1658530823b4d3b3dad322d" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re200/ppo_wide.csv", - "sha256": "a26bd1ebbf67016d7395ce95470b1786e5d87cc46b267078d11b6151086ad118" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re200/target_long.csv", - "sha256": "98735c5d9c78e4563df95add8760d2d21b1762c58cf62607aa3cd549b6ff2579" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re200/target_wide.csv", - "sha256": "e474644557b46d7913e3325f6695c3333e35aa444e0e4844040b501105ec50d1" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re400/ppo_dtw_convergence.csv", - "sha256": "9e447f5e9f33415210bcc8115e28a9b22d926d68e163dc5dbc3f5392a8397c37" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re400/ppo_long.csv", - "sha256": "e68e339ca3d89fc881249e13d1c87a4d98de80a1b240af2932a97c46c800edf7" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re400/ppo_wide.csv", - "sha256": "a429eb0bbf9cfb1307186b00bc6938a0a45e2a3b21b34e92a619d6666665be05" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re400/target_long.csv", - "sha256": "e316642eb6d46f801b93bca790ee95f1f3d39ff272db710c4a97566b12227a45" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re400/target_wide.csv", - "sha256": "f917597ef7e2e5ec28fb0b15482f05192cb3eec11bbcdee749727ea2d13d74da" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re50/ppo_dtw_convergence.csv", - "sha256": "586d11b79a2a57e3d6e8c06da4336e8fee9993463a8be1899b6de70be307e525" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re50/ppo_long.csv", - "sha256": "0fec1360f42c713069c10bee8b10a48fc911f394b49ca7e1985cdd64e3862788" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re50/ppo_wide.csv", - "sha256": "696e8354ab0448afdd66a6a49532f39f3e2a5e65276cc619d7c3fb3a0b1b0097" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re50/target_long.csv", - "sha256": "eb77e289a5332cbc3415a04be1f6599bec56642681e21396e3a5af4428f32001" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re50/target_wide.csv", - "sha256": "e6e0615341adfa94a323b1332ef03274cec7ea3fd87dd704d4a80026507366bc" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/offline_predictions.csv", - "sha256": "1a0d65dc5cf11e7585f869ef320fe79417bc968637b3e75cc1038389046db1fb" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/scaling_summary.csv", - "sha256": "b155a35ed1cf0509a613fa28986cf20310dde58ef763c826440833455a09f34e" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_0/sr_dtw_convergence.csv", - "sha256": "9f6005dfb191edcbf393e5b2f5da992364aa25bbe1b57d7e2295749e1b3668b4" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_0/sr_long.csv", - "sha256": "a35741b51c2bb0052de95d8d69a2a5f87f63f7b7f64ad5ee771f0d8cc551ff14" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_0/sr_wide.csv", - "sha256": "ed2b666f958b54dd8eda1c5fa045bc64782d5be8377954ce17d6fdd91531f1d2" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_0/target_long.csv", - "sha256": "686dc1021c77466bb67460bd480384bc18ed0e1f4c84261e8245bfb46e421062" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_0/target_wide.csv", - "sha256": "fe7b02dc0d9d4841be19b2f9f7787eed392e0780069b04f22ec0c5066093b4b9" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_1/sr_dtw_convergence.csv", - "sha256": "b844d6f4e9b8057087471b8b1a069a6c2bbba29530c9b13c696dcd0cbdbc74de" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_1/sr_long.csv", - "sha256": "e6ca7ac89d155e23454f7d5d979a813da4dacbcfbac5d1e65ef8d7b151295ce6" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_1/sr_wide.csv", - "sha256": "25e1af504daeb6824435647c049168f234f89995052cc5b01b27f0c5a5eb86da" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_1/target_long.csv", - "sha256": "5408f78f02cef617c3832356b741013d1c876c4fe1eef413e3dc9340e1feed0c" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_1/target_wide.csv", - "sha256": "b4c1397297dd9f391d33341af5171c26900367ddf82e7ea0a765d154029f61f9" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_2/sr_dtw_convergence.csv", - "sha256": "174fa9d160ede85b6f77970982c146381da781767461edbad8b60c9ca47b5825" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_2/sr_long.csv", - "sha256": "9dadea796ab09a2931011d113fd9261e4ee8f87c7d9d90cc006a0c3d98db1dc4" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_2/sr_wide.csv", - "sha256": "1ec33e173236e843563251d0773214cde9c2c7a3d69481cf6db16201524a2f01" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_2/target_long.csv", - "sha256": "7d1bd2d6e75eb8d42dbc1f0ae21cc4bf8fdca14114780414042fd1b4dce85468" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_2/target_wide.csv", - "sha256": "5a1d7e1da1c012e472165a02947b0a3abb73fb85778f7702858a7193c6299a8d" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_3/sr_dtw_convergence.csv", - "sha256": "a00a244abfe1513062ffeb45eee5907648573b2f995e9bbe5d95ac538209efe6" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_3/sr_long.csv", - "sha256": "23b0399b66f90b01caf0aadddc9d20e80f37f166442d25db9b7563286321fd92" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_3/sr_wide.csv", - "sha256": "8791f0b37c9f2eeb99b5f54a3cd8178dc4bb182725e6dd81be32df763f7a0ff3" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_3/target_long.csv", - "sha256": "34436f99cd043850062565893c31458c1f1b4858b2031e7fed928cafbba8a8d7" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_3/target_wide.csv", - "sha256": "02cc2f8c8de82a583d5fca2bbdad8b98288b5647e9edc23145bd067cef7d7daf" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_4/sr_dtw_convergence.csv", - "sha256": "d1e85efc22599e80cd2854b57f9130ef368bda2f373b7b28f047105d2d7106d1" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_4/sr_long.csv", - "sha256": "c1e32871d4fdbf9398b270dc58db055b372234de42865e3969f88796a7238a21" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_4/sr_wide.csv", - "sha256": "6da1d2a148df552f05fd77aaf6c91ad07cf6cc5b31ebc1c9b95db86ecb0dcd34" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_4/target_long.csv", - "sha256": "c9710b0fa850428205e8df389ad710b843b726904c92d2961090deaf1fb89c8a" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_4/target_wide.csv", - "sha256": "4585f08137b565f56c4db3c673e494c8ae9089b7f683498022a7c9c4f72243e4" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_5/sr_dtw_convergence.csv", - "sha256": "e1d8b5b1377565d4b1203db05577c30cb741414a6ed640448fa182fb161bd896" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_5/sr_long.csv", - "sha256": "21d0d26fd3ca7eb1591705593f2b4fb73e0c7c11a29a87e449b6941f3e81e656" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_5/sr_wide.csv", - "sha256": "4af522b066cecc2f9b69ac94d5e76410e7c830db4ecddf1e07366a21b9dfbc89" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_5/target_long.csv", - "sha256": "74483430917e3b5e42ecc71d51334ce38c3cd53ef908d390ee9a4d18b95f312a" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_5/target_wide.csv", - "sha256": "c0e5e96f51bfcca6b8a39f4229beb2d5c958806f3099ab4f315cf642dc53ed7c" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_6/sr_dtw_convergence.csv", - "sha256": "0255e92e88742d7610d93f4e1f7a885e9c897bf4a8d8db0fcea0726323d7437d" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_6/sr_long.csv", - "sha256": "f63cbc354784920358d87a94b62a821b5b6b1800f8a1b4c76c0e3a3bc47e1205" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_6/sr_wide.csv", - "sha256": "005321a7a3334e97160e15ef9923618468e5a50d2257c35b35a140fa1ffb4615" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_6/target_long.csv", - "sha256": "79369b750da6ced2c6646f8ec662621535e32ecbff1eca16ab8f51c12d3560e2" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_6/target_wide.csv", - "sha256": "297917ec804909bb5318b4255885544ca8ecfe22cc138ae3d56c31ef65c5e6fe" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/term_contributions_ppo_states.csv", - "sha256": "13716f661fdae79564cb2c9deb15461eca856adac31bdce32f262a0324eb8cd4" - }, - { - "path": "src/SR_analysis/results/runs/article2-plotting-package-20260721/term_contributions_sr_closed_loop_400.csv", - "sha256": "194bb3c6e597464cdb9d14a6dfcf3d65408a6ad20cf35ba4e7c02a168fa7b84a" - } - ] -} +{"artifacts":[{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/07_flow_field_comparison_karman_re100.json","sha256":"78480b21f7186f91782c805a03eccb1d7c478c359e26c3ac536601d521f5c57d"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/07_flow_field_comparison_karman_re100.npz","sha256":"3f26e963b6379e1998cae242248eca4e9fcb7205420ae0fe926049aac70bd93f"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/README.md","sha256":"9027fdd86aee732d54e751dfd6b30dcbbdb6d8b01c78641ec221ea583a796936"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/ablation_summary.csv","sha256":"66d1f25c217d82a8151b0a53f774f18d3dfac8d1be2c20354ebb441494705056"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/01_training_case_performance.pdf","sha256":"a9d359c8cc3f41d9e748253695fbe507cb0d0463b3963de544924b4a1131dcff"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/01_training_case_performance.png","sha256":"f8fad9fcb70b6b14b23f1ca8bd1d6de0b45f1df7b874399a131e1990c05e317b"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/02_pointwise_generalization.pdf","sha256":"245937c218e18ef9c726351235c6342776304e494c81935b11a9e2eb7e470546"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/02_pointwise_generalization.png","sha256":"955cad0483f6be4a8d6db7b4f35b72de2dba605f937e91b2ab1e432d291ce36a"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/03_steady_rotation_calibration.pdf","sha256":"c35c1e5ccdcf02696bb4dd20d6e747281387bb54426dd41d17d7249592ca152f"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/03_steady_rotation_calibration.png","sha256":"abd7214783b54ddbfa995cd6bbe9e991759ea579622eed9a4ca6318b7cd8ef66"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/06_example_timeseries_karman_re100.pdf","sha256":"9fe7741bcc958ada9822389fed843d6ebc02bfe2252741e09238d013cbfeb3c8"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/06_example_timeseries_karman_re100.png","sha256":"73e1a64e200f9b8288a3ee71872cf330eba27c5723d64c12d534d2de88eccc63"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/07_flow_field_comparison_karman_re100.pdf","sha256":"1a60bd8f17f7c6f78a443f407ccca771b53b5ae90205f800c8a0ef5ac4bd91ee"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/figures/07_flow_field_comparison_karman_re100.png","sha256":"1a74b9cd14fdc3f45da90010ab59c35c177be2a5c1a927e5b24684f77a4f2380"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_0.75L/ppo_dtw_convergence.csv","sha256":"2f49211f85c44c2b940d131b5178fb7aecbd109a4d1b9910f3a2b7983833e69a"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_0.75L/ppo_long.csv","sha256":"e7d7023da3a64a9d27d4ec7237a6e96faaeeba268c75c317b4b64688818e5e59"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_0.75L/ppo_manifest.json","sha256":"16a94fb4cf4df0c262578acd3d69492fb2d2fae99e548575976b1fb50add0303"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_0.75L/ppo_wide.csv","sha256":"f9ecbf75e62ec55229c9623329f08898258f25f3dc47ecfabedd3984f888c764"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_0.75L/target_long.csv","sha256":"f530a259a0ec6efa5666766070e29f3479f3f7c6854c2e1345b51f777e5a66d6"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_0.75L/target_wide.csv","sha256":"7e5e714023817974c460d8eaf517399a96113f27ad1fd524a74c211d0f17fd88"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1.5L/ppo_dtw_convergence.csv","sha256":"05b28809c65fb95a316ca90da3e603d7a3507cae1cbab861fe5b89bc20f26e79"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1.5L/ppo_long.csv","sha256":"a804a82de5ff9fbe5d11637a36fb6faa7df386c87e528a3ba16fcb8e6a3ae872"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1.5L/ppo_manifest.json","sha256":"e1798311505ce1257a98ce816aed9da2d5b6a3df59d48c011313b251d2903241"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1.5L/ppo_wide.csv","sha256":"68bd8553c94e4d2d2529b9f32503addc9c0a295368049d21312dbba892de824e"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1.5L/target_long.csv","sha256":"3618da795a8a462d548c04d570bbdb1572750a07abcf36ef6fff2cdf0a002eb3"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1.5L/target_wide.csv","sha256":"0c0f9eb5a1c8a333c10dc3fcf92ba09e098dfa1e53268d33b04ea849abc813c8"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1L/ppo_dtw_convergence.csv","sha256":"ca1675823570918a36ff18846ab087ec746b014f3b1c7ea2b5314194b3b243d5"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1L/ppo_long.csv","sha256":"55d8024bda6a36a0aaf4536f7a390d9bffb8b2d9daf91e8edcf5d8f555e17528"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1L/ppo_manifest.json","sha256":"8f038cb3bef26f0ea13c1e7542905bddfab7b6f69ada82b46ca58bb943e23d13"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1L/ppo_wide.csv","sha256":"1fc1258228acf0a90e089112405e70c274ea07077ca285068f45e69e2b13d203"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1L/target_long.csv","sha256":"999f5b16c15e6c8adf98d57659be875886adbd52d098d8aac2d5ea7d669bf51c"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/illusion_1L/target_wide.csv","sha256":"9ac8ada9234e9e638c877714514b2ef0ef499e40a13877b1291310edd15c1cd1"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re100/ppo_dtw_convergence.csv","sha256":"4769c68614a41488c2f8596b40d6fdbf06bf5106f24c5be410de8e8ea82a6800"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re100/ppo_long.csv","sha256":"be263dfa5945e6209aed92523b2cbf61f0e7d6cbb20b5357b1f27d2ecbd79529"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re100/ppo_manifest.json","sha256":"abb5394786f6cfa0c56a88e2579f946f20c819579d08fdb304e97724f3cf8fc8"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re100/ppo_wide.csv","sha256":"23f9bf3a364a8551f2d527ef8d21ce378bcf6abbf833b19b1d4b7314bc53a627"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re100/target_long.csv","sha256":"6894bdb4d74329574be653080a7010e6104c3798f843373924b79ce4bafb203a"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re100/target_wide.csv","sha256":"b871501e93384d4a9bfb5ed29172a171a26fd61415ece3029d65b13558c4125f"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re200/ppo_dtw_convergence.csv","sha256":"fcca94b8220a192644eb0103432dd8bcf700020be8115d34d18f05d4dc88faed"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re200/ppo_long.csv","sha256":"25383c90882f8b290aaf4643135803e3afd1e492f1658530823b4d3b3dad322d"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re200/ppo_manifest.json","sha256":"6c7bdfbfbf9308794225c76a0ac39f3ed4fd564e05f0ab7894714ad56b54e7db"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re200/ppo_wide.csv","sha256":"a26bd1ebbf67016d7395ce95470b1786e5d87cc46b267078d11b6151086ad118"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re200/target_long.csv","sha256":"98735c5d9c78e4563df95add8760d2d21b1762c58cf62607aa3cd549b6ff2579"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re200/target_wide.csv","sha256":"e474644557b46d7913e3325f6695c3333e35aa444e0e4844040b501105ec50d1"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re400/ppo_dtw_convergence.csv","sha256":"9e447f5e9f33415210bcc8115e28a9b22d926d68e163dc5dbc3f5392a8397c37"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re400/ppo_long.csv","sha256":"e68e339ca3d89fc881249e13d1c87a4d98de80a1b240af2932a97c46c800edf7"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re400/ppo_manifest.json","sha256":"8ccd4a1c902ce5468720a00314c9f61ec23d0df6169f0e3fd38a841e8a91611e"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re400/ppo_wide.csv","sha256":"a429eb0bbf9cfb1307186b00bc6938a0a45e2a3b21b34e92a619d6666665be05"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re400/target_long.csv","sha256":"e316642eb6d46f801b93bca790ee95f1f3d39ff272db710c4a97566b12227a45"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re400/target_wide.csv","sha256":"f917597ef7e2e5ec28fb0b15482f05192cb3eec11bbcdee749727ea2d13d74da"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re50/ppo_dtw_convergence.csv","sha256":"586d11b79a2a57e3d6e8c06da4336e8fee9993463a8be1899b6de70be307e525"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re50/ppo_long.csv","sha256":"0fec1360f42c713069c10bee8b10a48fc911f394b49ca7e1985cdd64e3862788"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re50/ppo_manifest.json","sha256":"2a7edd027bdaf8e70ac5ee0c33862048d6f8e1eb801681588d61c463af71b0bc"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re50/ppo_wide.csv","sha256":"696e8354ab0448afdd66a6a49532f39f3e2a5e65276cc619d7c3fb3a0b1b0097"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re50/target_long.csv","sha256":"eb77e289a5332cbc3415a04be1f6599bec56642681e21396e3a5af4428f32001"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/long_ppo/karman_re50/target_wide.csv","sha256":"e6e0615341adfa94a323b1332ef03274cec7ea3fd87dd704d4a80026507366bc"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/offline_predictions.csv","sha256":"1a0d65dc5cf11e7585f869ef320fe79417bc968637b3e75cc1038389046db1fb"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/phase_alignment.json","sha256":"fc95749546c8ee63bcf55d38c4f7b80aaa418e337a0e4a51fbe8ad4e0daad7d3"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/presentation/01_sr_method_and_formulas.pdf","sha256":"7e1e6ca4d472443d59d7394268c95c6e72f35b7d94f9e523f8a5d49000a04d21"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/presentation/01_sr_method_and_formulas.png","sha256":"0b710c94b44cc2b4ea871cacfc49015becb85e817ddffc8131ea6cc8fc42f4a1"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/presentation/02_sr_quantitative_evidence.pdf","sha256":"4f480aaec1569a12219a72daf827fdaadaf99b36a0c956378d9f2366753ab658"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/presentation/02_sr_quantitative_evidence.png","sha256":"494dbb22274520f736b2e8e2eb86e8ffd3b4761ca831228f0697cc2c79259aa8"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/presentation/README.md","sha256":"cf5b9bf66f4cb2107c4e0fbf3e55c6d7633bc64aabd07aae4cd6edc2f957de3f"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/scaling_summary.csv","sha256":"b155a35ed1cf0509a613fa28986cf20310dde58ef763c826440833455a09f34e"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_0/sr_dtw_convergence.csv","sha256":"9f6005dfb191edcbf393e5b2f5da992364aa25bbe1b57d7e2295749e1b3668b4"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_0/sr_long.csv","sha256":"a35741b51c2bb0052de95d8d69a2a5f87f63f7b7f64ad5ee771f0d8cc551ff14"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_0/sr_manifest.json","sha256":"73cd569bcc5e6e0ca892d37c9f1ffd7017132471cbcdcdf23603bcaa1182406b"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_0/sr_wide.csv","sha256":"ed2b666f958b54dd8eda1c5fa045bc64782d5be8377954ce17d6fdd91531f1d2"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_0/target_long.csv","sha256":"686dc1021c77466bb67460bd480384bc18ed0e1f4c84261e8245bfb46e421062"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_0/target_wide.csv","sha256":"fe7b02dc0d9d4841be19b2f9f7787eed392e0780069b04f22ec0c5066093b4b9"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_1/sr_dtw_convergence.csv","sha256":"b844d6f4e9b8057087471b8b1a069a6c2bbba29530c9b13c696dcd0cbdbc74de"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_1/sr_long.csv","sha256":"e6ca7ac89d155e23454f7d5d979a813da4dacbcfbac5d1e65ef8d7b151295ce6"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_1/sr_manifest.json","sha256":"3d72be256eebf1017e894c71b5aadec7fb686a37f4397326349a3959dfe5d751"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_1/sr_wide.csv","sha256":"25e1af504daeb6824435647c049168f234f89995052cc5b01b27f0c5a5eb86da"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_1/target_long.csv","sha256":"5408f78f02cef617c3832356b741013d1c876c4fe1eef413e3dc9340e1feed0c"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_1/target_wide.csv","sha256":"b4c1397297dd9f391d33341af5171c26900367ddf82e7ea0a765d154029f61f9"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_2/sr_dtw_convergence.csv","sha256":"174fa9d160ede85b6f77970982c146381da781767461edbad8b60c9ca47b5825"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_2/sr_long.csv","sha256":"9dadea796ab09a2931011d113fd9261e4ee8f87c7d9d90cc006a0c3d98db1dc4"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_2/sr_manifest.json","sha256":"0e8c698562ce983feb8a088464982097d84fbeb7bf48595d113b70190e23e411"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_2/sr_wide.csv","sha256":"1ec33e173236e843563251d0773214cde9c2c7a3d69481cf6db16201524a2f01"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_2/target_long.csv","sha256":"7d1bd2d6e75eb8d42dbc1f0ae21cc4bf8fdca14114780414042fd1b4dce85468"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_2/target_wide.csv","sha256":"5a1d7e1da1c012e472165a02947b0a3abb73fb85778f7702858a7193c6299a8d"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_3/sr_dtw_convergence.csv","sha256":"a00a244abfe1513062ffeb45eee5907648573b2f995e9bbe5d95ac538209efe6"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_3/sr_long.csv","sha256":"23b0399b66f90b01caf0aadddc9d20e80f37f166442d25db9b7563286321fd92"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_3/sr_manifest.json","sha256":"d3e7b391019363a9d2d9866ae9fafe3505d37f96e59946016c04b3943d60eeac"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_3/sr_wide.csv","sha256":"8791f0b37c9f2eeb99b5f54a3cd8178dc4bb182725e6dd81be32df763f7a0ff3"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_3/target_long.csv","sha256":"34436f99cd043850062565893c31458c1f1b4858b2031e7fed928cafbba8a8d7"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_3/target_wide.csv","sha256":"02cc2f8c8de82a583d5fca2bbdad8b98288b5647e9edc23145bd067cef7d7daf"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_4/sr_dtw_convergence.csv","sha256":"d1e85efc22599e80cd2854b57f9130ef368bda2f373b7b28f047105d2d7106d1"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_4/sr_long.csv","sha256":"c1e32871d4fdbf9398b270dc58db055b372234de42865e3969f88796a7238a21"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_4/sr_manifest.json","sha256":"897f2fb111b9316a5a861f704a053ab78c0fd6ef603803683453497ec8ae425e"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_4/sr_wide.csv","sha256":"6da1d2a148df552f05fd77aaf6c91ad07cf6cc5b31ebc1c9b95db86ecb0dcd34"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_4/target_long.csv","sha256":"c9710b0fa850428205e8df389ad710b843b726904c92d2961090deaf1fb89c8a"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_4/target_wide.csv","sha256":"4585f08137b565f56c4db3c673e494c8ae9089b7f683498022a7c9c4f72243e4"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_5/sr_dtw_convergence.csv","sha256":"e1d8b5b1377565d4b1203db05577c30cb741414a6ed640448fa182fb161bd896"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_5/sr_long.csv","sha256":"21d0d26fd3ca7eb1591705593f2b4fb73e0c7c11a29a87e449b6941f3e81e656"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_5/sr_manifest.json","sha256":"dd0abf6f7b31cbe638ee2549bd90a18731b6c57ed57078b93ad0120a9c873f12"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_5/sr_wide.csv","sha256":"4af522b066cecc2f9b69ac94d5e76410e7c830db4ecddf1e07366a21b9dfbc89"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_5/target_long.csv","sha256":"74483430917e3b5e42ecc71d51334ce38c3cd53ef908d390ee9a4d18b95f312a"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_5/target_wide.csv","sha256":"c0e5e96f51bfcca6b8a39f4229beb2d5c958806f3099ab4f315cf642dc53ed7c"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_6/sr_dtw_convergence.csv","sha256":"0255e92e88742d7610d93f4e1f7a885e9c897bf4a8d8db0fcea0726323d7437d"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_6/sr_long.csv","sha256":"f63cbc354784920358d87a94b62a821b5b6b1800f8a1b4c76c0e3a3bc47e1205"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_6/sr_manifest.json","sha256":"e13f3565b96f424ef9f9381106c0cdfe2a6ebe4c60dc4c2abd35c7262ac9b9a2"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_6/sr_wide.csv","sha256":"005321a7a3334e97160e15ef9923618468e5a50d2257c35b35a140fa1ffb4615"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_6/target_long.csv","sha256":"79369b750da6ced2c6646f8ec662621535e32ecbff1eca16ab8f51c12d3560e2"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/steady_timeseries/alpha_6/target_wide.csv","sha256":"297917ec804909bb5318b4255885544ca8ecfe22cc138ae3d56c31ef65c5e6fe"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/tables/offline_action_rmse.csv","sha256":"cca9f9606edf05d3d9834fa1712faf4de2e10e0e7b2c1bc73cfc43f551ace4fc"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/tables/offline_action_rmse.md","sha256":"3ab789d7cf56192b067df58192d947eba3d0ef46a77a230727f0a15883830889"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/tables/term_deletion.csv","sha256":"00ef72ad424fe5cd1a4c6e1becd0b3367e346ae418f740bb3e98a1e1e7bea59a"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/tables/term_deletion.md","sha256":"0493944fe87c3d35bc98be6e4ec634ce0099ad408b8ffbd563ee3a18c06fd493"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/term_contributions_ppo_states.csv","sha256":"13716f661fdae79564cb2c9deb15461eca856adac31bdce32f262a0324eb8cd4"},{"path":"src/SR_analysis/results/runs/article2-plotting-package-20260721/term_contributions_sr_closed_loop_400.csv","sha256":"194bb3c6e597464cdb9d14a6dfcf3d65408a6ad20cf35ba4e7c02a168fa7b84a"}],"schema_version":"sr-plotting-package-v3","source_policy":"immutable article and Article2 artifacts; no scientific refit","summary":{"ablation_rows":24,"closed_loop_term_rows":2793,"flow_field_npz":1,"long_ppo_scenes":7,"offline_rows":1379,"offline_term_rows":1379,"presentation_pages":2,"publication_figures":5,"publication_tables":2,"scaling_rows":37,"steady_timeseries_cases":7}} diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/phase_alignment.json b/src/SR_analysis/results/runs/article2-plotting-package-20260721/phase_alignment.json new file mode 100644 index 0000000..dc7754a --- /dev/null +++ b/src/SR_analysis/results/runs/article2-plotting-package-20260721/phase_alignment.json @@ -0,0 +1,19 @@ +{ + "scene": "karman_re100", + "source_package": "article2-timeseries-csv-20260720", + "target_index_start_inclusive": 96, + "target_index_stop_exclusive": 146, + "target_t_D_start": 38.4, + "target_t_D_end": 58.0, + "samples": 50, + "displayed_target_cycles": 3, + "alignment_signal": "sensors_center_uy", + "alignment_method": "mean-centered normalized circular cross-correlation; smallest maximizing roll selected", + "roll_lags_samples": { + "Target": 0, + "PPO": 0, + "SR": -4 + }, + "control_dt_D_over_U0": 0.4, + "target_actions_plotted": false +} diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/presentation/01_sr_method_and_formulas.pdf b/src/SR_analysis/results/runs/article2-plotting-package-20260721/presentation/01_sr_method_and_formulas.pdf new file mode 100644 index 0000000..88fafc9 Binary files /dev/null and b/src/SR_analysis/results/runs/article2-plotting-package-20260721/presentation/01_sr_method_and_formulas.pdf differ diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/presentation/01_sr_method_and_formulas.png b/src/SR_analysis/results/runs/article2-plotting-package-20260721/presentation/01_sr_method_and_formulas.png new file mode 100644 index 0000000..73fe373 Binary files /dev/null and b/src/SR_analysis/results/runs/article2-plotting-package-20260721/presentation/01_sr_method_and_formulas.png differ diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/presentation/02_sr_quantitative_evidence.pdf b/src/SR_analysis/results/runs/article2-plotting-package-20260721/presentation/02_sr_quantitative_evidence.pdf new file mode 100644 index 0000000..bb2c716 Binary files /dev/null and b/src/SR_analysis/results/runs/article2-plotting-package-20260721/presentation/02_sr_quantitative_evidence.pdf differ diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/presentation/02_sr_quantitative_evidence.png b/src/SR_analysis/results/runs/article2-plotting-package-20260721/presentation/02_sr_quantitative_evidence.png new file mode 100644 index 0000000..951bdbf Binary files /dev/null and b/src/SR_analysis/results/runs/article2-plotting-package-20260721/presentation/02_sr_quantitative_evidence.png differ diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/presentation/README.md b/src/SR_analysis/results/runs/article2-plotting-package-20260721/presentation/README.md new file mode 100644 index 0000000..a3a4366 --- /dev/null +++ b/src/SR_analysis/results/runs/article2-plotting-package-20260721/presentation/README.md @@ -0,0 +1,10 @@ +# SR presentation graphics + +- `01_sr_method_and_formulas`: a 16:9 methodology-to-formula summary. Formula coefficients and expressions are read dynamically from the canonical Kármán and Illusion JSON artifacts. The page states the front odd projection, lower-cylinder symmetry map, and evidence-bounded physical interpretation. +- `02_sr_quantitative_evidence`: a 16:9 summary of frozen closed-loop evidence: 400-control-step SR/PPO training-case ranges and means, 200-step pointwise generalization ranges, and 40-step parent-relative term-deletion effects. It deliberately excludes offline imitation RMSE from article-performance claims. + +Reproduce both PNG and PDF pages with: + +`conda run -n base python src/SR_analysis/tools/plot_sr_presentation.py` + +Sources: canonical formula JSONs in `article-refit-{karman,illusion}-topology-a-20260718`; 400-step SR and PPO DTW convergence CSVs; `article2-generalization-summary-20260720/generalization.csv`; and `tables/term_deletion.csv`. All similarities use `legacy_dtw_v1_abs_n_unclipped`. Windows and limitations are printed on the pages. diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/tables/offline_action_rmse.csv b/src/SR_analysis/results/runs/article2-plotting-package-20260721/tables/offline_action_rmse.csv new file mode 100644 index 0000000..fc75b67 --- /dev/null +++ b/src/SR_analysis/results/runs/article2-plotting-package-20260721/tables/offline_action_rmse.csv @@ -0,0 +1,8 @@ +objective,training_case,n_causally_aligned_states,rmse_alpha_front,rmse_alpha_upper,rmse_alpha_lower,aggregate_rmse_alpha +karman,karman_re50,197,1.73860463,1.39458231,1.13278257,1.44346830 +karman,karman_re100,197,0.51356311,1.24024221,1.32638969,1.08953467 +karman,karman_re200,197,1.47993917,2.29449643,2.86751651,2.28601434 +karman,karman_re400,197,0.40632824,2.55717318,2.72429918,2.16994956 +illusion,illusion_0.75L,197,0.82228198,1.77041216,1.43962577,1.40036058 +illusion,illusion_1L,197,0.85035894,1.20967232,0.58682215,0.91847295 +illusion,illusion_1.5L,197,3.20534050,2.22584755,1.74754340,2.46863748 diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/tables/offline_action_rmse.md b/src/SR_analysis/results/runs/article2-plotting-package-20260721/tables/offline_action_rmse.md new file mode 100644 index 0000000..0be2644 --- /dev/null +++ b/src/SR_analysis/results/runs/article2-plotting-package-20260721/tables/offline_action_rmse.md @@ -0,0 +1,15 @@ +# Offline SR-vs-PPO next-action RMSE + +RMSE is evaluated on causally aligned PPO-visited states: each post-action state predicts the next PPO action. This is an offline imitation diagnostic, not closed-loop performance. + +| Objective | Training case | States | Front RMSE | Upper RMSE | Lower RMSE | Aggregate RMSE | +|---|---|---:|---:|---:|---:|---:| +| karman | karman_re50 | 197 | 1.73860463 | 1.39458231 | 1.13278257 | 1.44346830 | +| karman | karman_re100 | 197 | 0.51356311 | 1.24024221 | 1.32638969 | 1.08953467 | +| karman | karman_re200 | 197 | 1.47993917 | 2.29449643 | 2.86751651 | 2.28601434 | +| karman | karman_re400 | 197 | 0.40632824 | 2.55717318 | 2.72429918 | 2.16994956 | +| illusion | illusion_0.75L | 197 | 0.82228198 | 1.77041216 | 1.43962577 | 1.40036058 | +| illusion | illusion_1L | 197 | 0.85035894 | 1.20967232 | 0.58682215 | 0.91847295 | +| illusion | illusion_1.5L | 197 | 3.20534050 | 2.22584755 | 1.74754340 | 2.46863748 | + +All action errors are in dimensionless surface-speed α. Aggregate RMSE pools the three action channels and all aligned states within a case. diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/tables/term_deletion.csv b/src/SR_analysis/results/runs/article2-plotting-package-20260721/tables/term_deletion.csv new file mode 100644 index 0000000..acfb21e --- /dev/null +++ b/src/SR_analysis/results/runs/article2-plotting-package-20260721/tables/term_deletion.csv @@ -0,0 +1,25 @@ +objective,head,variant,parent_formula,deleted_term,retained_formula,case,steps,legacy_dtw,parent_legacy_dtw_40_step,delta_vs_parent_40_step,variant_mean,variant_min +karman,front odd projection,k_front0,-0.3813914338074549 * Cd_rear_a,-0.3813914338074549*Cd_rear_a,0,karman_re50,40,0.93936105,0.94588134,-0.00652029,0.88656666,0.81328922 +karman,front odd projection,k_front0,-0.3813914338074549 * Cd_rear_a,-0.3813914338074549*Cd_rear_a,0,karman_re100,40,0.91218229,0.91160360,+0.00057869,0.88656666,0.81328922 +karman,front odd projection,k_front0,-0.3813914338074549 * Cd_rear_a,-0.3813914338074549*Cd_rear_a,0,karman_re200,40,0.88143408,0.88661335,-0.00517927,0.88656666,0.81328922 +karman,front odd projection,k_front0,-0.3813914338074549 * Cd_rear_a,-0.3813914338074549*Cd_rear_a,0,karman_re400,40,0.81328922,0.83848301,-0.02519379,0.88656666,0.81328922 +karman,upper/lower shared-symmetry rear,k_rear0,1.3077817865589976 * Cl_rear_s - 3.431208680510616,1.3077817865589976*Cl_rear_s,-3.431208680510616,karman_re50,40,0.91712633,0.94588134,-0.02875501,0.85046363,0.78953465 +karman,upper/lower shared-symmetry rear,k_rear0,1.3077817865589976 * Cl_rear_s - 3.431208680510616,1.3077817865589976*Cl_rear_s,-3.431208680510616,karman_re100,40,0.86994335,0.91160360,-0.04166025,0.85046363,0.78953465 +karman,upper/lower shared-symmetry rear,k_rear0,1.3077817865589976 * Cl_rear_s - 3.431208680510616,1.3077817865589976*Cl_rear_s,-3.431208680510616,karman_re200,40,0.82525019,0.88661335,-0.06136315,0.85046363,0.78953465 +karman,upper/lower shared-symmetry rear,k_rear0,1.3077817865589976 * Cl_rear_s - 3.431208680510616,1.3077817865589976*Cl_rear_s,-3.431208680510616,karman_re400,40,0.78953465,0.83848301,-0.04894836,0.85046363,0.78953465 +karman,upper/lower shared-symmetry rear,k_rear1,1.3077817865589976 * Cl_rear_s - 3.431208680510616,-3.431208680510616,1.3077817865589976*Cl_rear_s,karman_re50,40,0.83934413,0.94588134,-0.10653720,0.75314220,0.64740091 +karman,upper/lower shared-symmetry rear,k_rear1,1.3077817865589976 * Cl_rear_s - 3.431208680510616,-3.431208680510616,1.3077817865589976*Cl_rear_s,karman_re100,40,0.81773500,0.91160360,-0.09386860,0.75314220,0.64740091 +karman,upper/lower shared-symmetry rear,k_rear1,1.3077817865589976 * Cl_rear_s - 3.431208680510616,-3.431208680510616,1.3077817865589976*Cl_rear_s,karman_re200,40,0.70808876,0.88661335,-0.17852458,0.75314220,0.64740091 +karman,upper/lower shared-symmetry rear,k_rear1,1.3077817865589976 * Cl_rear_s - 3.431208680510616,-3.431208680510616,1.3077817865589976*Cl_rear_s,karman_re400,40,0.64740091,0.83848301,-0.19108210,0.75314220,0.64740091 +illusion,front odd projection,i_front0,-1.8266041890847688 * Cd_rear_a + 2.064492712013321 * Cl_F,-1.8266041890847688*Cd_rear_a,2.064492712013321*Cl_F,illusion_0.75L,40,0.93937493,0.95545614,-0.01608120,0.91931368,0.88508469 +illusion,front odd projection,i_front0,-1.8266041890847688 * Cd_rear_a + 2.064492712013321 * Cl_F,-1.8266041890847688*Cd_rear_a,2.064492712013321*Cl_F,illusion_1L,40,0.93348141,0.94330800,-0.00982659,0.91931368,0.88508469 +illusion,front odd projection,i_front0,-1.8266041890847688 * Cd_rear_a + 2.064492712013321 * Cl_F,-1.8266041890847688*Cd_rear_a,2.064492712013321*Cl_F,illusion_1.5L,40,0.88508469,0.87939766,+0.00568703,0.91931368,0.88508469 +illusion,front odd projection,i_front1,-1.8266041890847688 * Cd_rear_a + 2.064492712013321 * Cl_F,2.064492712013321*Cl_F,-1.8266041890847688*Cd_rear_a,illusion_0.75L,40,0.96006748,0.95545614,+0.00461134,0.90788838,0.83656314 +illusion,front odd projection,i_front1,-1.8266041890847688 * Cd_rear_a + 2.064492712013321 * Cl_F,2.064492712013321*Cl_F,-1.8266041890847688*Cd_rear_a,illusion_1L,40,0.92703454,0.94330800,-0.01627347,0.90788838,0.83656314 +illusion,front odd projection,i_front1,-1.8266041890847688 * Cd_rear_a + 2.064492712013321 * Cl_F,2.064492712013321*Cl_F,-1.8266041890847688*Cd_rear_a,illusion_1.5L,40,0.83656314,0.87939766,-0.04283452,0.90788838,0.83656314 +illusion,upper/lower shared-symmetry rear,i_rear0,1.2544396437730243 * Cd_rear_a - 1.5280742226055937 * Cl_F,1.2544396437730243*Cd_rear_a,-1.5280742226055937*Cl_F,illusion_0.75L,40,0.95448757,0.95545614,-0.00096857,0.92631380,0.88760304 +illusion,upper/lower shared-symmetry rear,i_rear0,1.2544396437730243 * Cd_rear_a - 1.5280742226055937 * Cl_F,1.2544396437730243*Cd_rear_a,-1.5280742226055937*Cl_F,illusion_1L,40,0.93685081,0.94330800,-0.00645719,0.92631380,0.88760304 +illusion,upper/lower shared-symmetry rear,i_rear0,1.2544396437730243 * Cd_rear_a - 1.5280742226055937 * Cl_F,1.2544396437730243*Cd_rear_a,-1.5280742226055937*Cl_F,illusion_1.5L,40,0.88760304,0.87939766,+0.00820537,0.92631380,0.88760304 +illusion,upper/lower shared-symmetry rear,i_rear1,1.2544396437730243 * Cd_rear_a - 1.5280742226055937 * Cl_F,-1.5280742226055937*Cl_F,1.2544396437730243*Cd_rear_a,illusion_0.75L,40,0.95464065,0.95545614,-0.00081549,0.91197030,0.85081581 +illusion,upper/lower shared-symmetry rear,i_rear1,1.2544396437730243 * Cd_rear_a - 1.5280742226055937 * Cl_F,-1.5280742226055937*Cl_F,1.2544396437730243*Cd_rear_a,illusion_1L,40,0.93045444,0.94330800,-0.01285357,0.91197030,0.85081581 +illusion,upper/lower shared-symmetry rear,i_rear1,1.2544396437730243 * Cd_rear_a - 1.5280742226055937 * Cl_F,-1.5280742226055937*Cl_F,1.2544396437730243*Cd_rear_a,illusion_1.5L,40,0.85081581,0.87939766,-0.02858185,0.91197030,0.85081581 diff --git a/src/SR_analysis/results/runs/article2-plotting-package-20260721/tables/term_deletion.md b/src/SR_analysis/results/runs/article2-plotting-package-20260721/tables/term_deletion.md new file mode 100644 index 0000000..aaf8a5c --- /dev/null +++ b/src/SR_analysis/results/runs/article2-plotting-package-20260721/tables/term_deletion.md @@ -0,0 +1,34 @@ +# Forty-step closed-loop term deletion + +Every comparison uses the same 40-control-step window and the legacy DTW metric. Parent values come from the scientifically comparable parent L2 runs; deltas are deletion minus parent. Formula identities are read from the canonical parent and deletion JSON artifacts. + +Front formulas are scalar generators deployed through the odd projection `α_F(x) = ½[f(x) − f(Gx)]`. Rear formulas define the upper action; the lower action is mapped by `α_L(x) = −α_U(Gx)`. + +| Objective | Head | Variant | Parent formula | Deleted term | Retained formula | Case | DTW | Parent DTW | Δ | Mean | Min | +|---|---|---|---|---|---|---|---:|---:|---:|---:|---:| +| karman | front odd projection | k_front0 | `-0.3813914338074549 · Cd_rear_a` | `-0.3813914338074549·Cd_rear_a` | `0` | karman_re50 | 0.93936105 | 0.94588134 | -0.00652029 | 0.88656666 | 0.81328922 | +| karman | front odd projection | k_front0 | `-0.3813914338074549 · Cd_rear_a` | `-0.3813914338074549·Cd_rear_a` | `0` | karman_re100 | 0.91218229 | 0.91160360 | +0.00057869 | 0.88656666 | 0.81328922 | +| karman | front odd projection | k_front0 | `-0.3813914338074549 · Cd_rear_a` | `-0.3813914338074549·Cd_rear_a` | `0` | karman_re200 | 0.88143408 | 0.88661335 | -0.00517927 | 0.88656666 | 0.81328922 | +| karman | front odd projection | k_front0 | `-0.3813914338074549 · Cd_rear_a` | `-0.3813914338074549·Cd_rear_a` | `0` | karman_re400 | 0.81328922 | 0.83848301 | -0.02519379 | 0.88656666 | 0.81328922 | +| karman | upper/lower shared-symmetry rear | k_rear0 | `1.3077817865589976 · Cl_rear_s - 3.431208680510616` | `1.3077817865589976·Cl_rear_s` | `-3.431208680510616` | karman_re50 | 0.91712633 | 0.94588134 | -0.02875501 | 0.85046363 | 0.78953465 | +| karman | upper/lower shared-symmetry rear | k_rear0 | `1.3077817865589976 · Cl_rear_s - 3.431208680510616` | `1.3077817865589976·Cl_rear_s` | `-3.431208680510616` | karman_re100 | 0.86994335 | 0.91160360 | -0.04166025 | 0.85046363 | 0.78953465 | +| karman | upper/lower shared-symmetry rear | k_rear0 | `1.3077817865589976 · Cl_rear_s - 3.431208680510616` | `1.3077817865589976·Cl_rear_s` | `-3.431208680510616` | karman_re200 | 0.82525019 | 0.88661335 | -0.06136315 | 0.85046363 | 0.78953465 | +| karman | upper/lower shared-symmetry rear | k_rear0 | `1.3077817865589976 · Cl_rear_s - 3.431208680510616` | `1.3077817865589976·Cl_rear_s` | `-3.431208680510616` | karman_re400 | 0.78953465 | 0.83848301 | -0.04894836 | 0.85046363 | 0.78953465 | +| karman | upper/lower shared-symmetry rear | k_rear1 | `1.3077817865589976 · Cl_rear_s - 3.431208680510616` | `-3.431208680510616` | `1.3077817865589976·Cl_rear_s` | karman_re50 | 0.83934413 | 0.94588134 | -0.10653720 | 0.75314220 | 0.64740091 | +| karman | upper/lower shared-symmetry rear | k_rear1 | `1.3077817865589976 · Cl_rear_s - 3.431208680510616` | `-3.431208680510616` | `1.3077817865589976·Cl_rear_s` | karman_re100 | 0.81773500 | 0.91160360 | -0.09386860 | 0.75314220 | 0.64740091 | +| karman | upper/lower shared-symmetry rear | k_rear1 | `1.3077817865589976 · Cl_rear_s - 3.431208680510616` | `-3.431208680510616` | `1.3077817865589976·Cl_rear_s` | karman_re200 | 0.70808876 | 0.88661335 | -0.17852458 | 0.75314220 | 0.64740091 | +| karman | upper/lower shared-symmetry rear | k_rear1 | `1.3077817865589976 · Cl_rear_s - 3.431208680510616` | `-3.431208680510616` | `1.3077817865589976·Cl_rear_s` | karman_re400 | 0.64740091 | 0.83848301 | -0.19108210 | 0.75314220 | 0.64740091 | +| illusion | front odd projection | i_front0 | `-1.8266041890847688 · Cd_rear_a + 2.064492712013321 · Cl_F` | `-1.8266041890847688·Cd_rear_a` | `2.064492712013321·Cl_F` | illusion_0.75L | 0.93937493 | 0.95545614 | -0.01608120 | 0.91931368 | 0.88508469 | +| illusion | front odd projection | i_front0 | `-1.8266041890847688 · Cd_rear_a + 2.064492712013321 · Cl_F` | `-1.8266041890847688·Cd_rear_a` | `2.064492712013321·Cl_F` | illusion_1L | 0.93348141 | 0.94330800 | -0.00982659 | 0.91931368 | 0.88508469 | +| illusion | front odd projection | i_front0 | `-1.8266041890847688 · Cd_rear_a + 2.064492712013321 · Cl_F` | `-1.8266041890847688·Cd_rear_a` | `2.064492712013321·Cl_F` | illusion_1.5L | 0.88508469 | 0.87939766 | +0.00568703 | 0.91931368 | 0.88508469 | +| illusion | front odd projection | i_front1 | `-1.8266041890847688 · Cd_rear_a + 2.064492712013321 · Cl_F` | `2.064492712013321·Cl_F` | `-1.8266041890847688·Cd_rear_a` | illusion_0.75L | 0.96006748 | 0.95545614 | +0.00461134 | 0.90788838 | 0.83656314 | +| illusion | front odd projection | i_front1 | `-1.8266041890847688 · Cd_rear_a + 2.064492712013321 · Cl_F` | `2.064492712013321·Cl_F` | `-1.8266041890847688·Cd_rear_a` | illusion_1L | 0.92703454 | 0.94330800 | -0.01627347 | 0.90788838 | 0.83656314 | +| illusion | front odd projection | i_front1 | `-1.8266041890847688 · Cd_rear_a + 2.064492712013321 · Cl_F` | `2.064492712013321·Cl_F` | `-1.8266041890847688·Cd_rear_a` | illusion_1.5L | 0.83656314 | 0.87939766 | -0.04283452 | 0.90788838 | 0.83656314 | +| illusion | upper/lower shared-symmetry rear | i_rear0 | `1.2544396437730243 · Cd_rear_a - 1.5280742226055937 · Cl_F` | `1.2544396437730243·Cd_rear_a` | `-1.5280742226055937·Cl_F` | illusion_0.75L | 0.95448757 | 0.95545614 | -0.00096857 | 0.92631380 | 0.88760304 | +| illusion | upper/lower shared-symmetry rear | i_rear0 | `1.2544396437730243 · Cd_rear_a - 1.5280742226055937 · Cl_F` | `1.2544396437730243·Cd_rear_a` | `-1.5280742226055937·Cl_F` | illusion_1L | 0.93685081 | 0.94330800 | -0.00645719 | 0.92631380 | 0.88760304 | +| illusion | upper/lower shared-symmetry rear | i_rear0 | `1.2544396437730243 · Cd_rear_a - 1.5280742226055937 · Cl_F` | `1.2544396437730243·Cd_rear_a` | `-1.5280742226055937·Cl_F` | illusion_1.5L | 0.88760304 | 0.87939766 | +0.00820537 | 0.92631380 | 0.88760304 | +| illusion | upper/lower shared-symmetry rear | i_rear1 | `1.2544396437730243 · Cd_rear_a - 1.5280742226055937 · Cl_F` | `-1.5280742226055937·Cl_F` | `1.2544396437730243·Cd_rear_a` | illusion_0.75L | 0.95464065 | 0.95545614 | -0.00081549 | 0.91197030 | 0.85081581 | +| illusion | upper/lower shared-symmetry rear | i_rear1 | `1.2544396437730243 · Cd_rear_a - 1.5280742226055937 · Cl_F` | `-1.5280742226055937·Cl_F` | `1.2544396437730243·Cd_rear_a` | illusion_1L | 0.93045444 | 0.94330800 | -0.01285357 | 0.91197030 | 0.85081581 | +| illusion | upper/lower shared-symmetry rear | i_rear1 | `1.2544396437730243 · Cd_rear_a - 1.5280742226055937 · Cl_F` | `-1.5280742226055937·Cl_F` | `1.2544396437730243·Cd_rear_a` | illusion_1.5L | 0.85081581 | 0.87939766 | -0.02858185 | 0.91197030 | 0.85081581 | + +Interpretation: Kármán deletion effects are term-dependent, with deleting the rear constant producing the largest degradation; the tested front term is weak over this short window. Illusion deletions remain stable and comparatively close to their parent, so these runs do not establish uniqueness of every term. Means and minima summarize the listed training cases only. diff --git a/src/SR_analysis/tests/test_export_flow_comparison.py b/src/SR_analysis/tests/test_export_flow_comparison.py new file mode 100644 index 0000000..5f9db82 --- /dev/null +++ b/src/SR_analysis/tests/test_export_flow_comparison.py @@ -0,0 +1,96 @@ +from __future__ import annotations + +import importlib.util +import json +from pathlib import Path + +import numpy as np + +MODULE_PATH = Path(__file__).resolve().parents[1] / "tools/export_flow_comparison.py" +spec = importlib.util.spec_from_file_location("export_flow_comparison", MODULE_PATH) +module = importlib.util.module_from_spec(spec) +assert spec.loader +spec.loader.exec_module(module) + + +def test_wrapped_phase_difference_crosses_branch_cut(): + delta = module.wrapped_phase_difference(-np.pi + 0.02, np.pi - 0.03) + assert np.isclose(delta, 0.05) + + +def test_joint_phase_selection_is_deterministic_and_nearby(): + n = 20 + phase = np.linspace(0, 4 * np.pi, n, endpoint=False) + def trace(offset: float) -> np.ndarray: + out = np.zeros((n, 6)) + out[:, 2] = 2.0 + 3.0 * np.cos(phase + offset) + out[:, 3] = -1.0 + 0.5 * np.sin(phase + offset) + return out + first = module.select_joint_phase_match(trace(0), trace(0.05), trace(-0.04), 0, n, temporal_weight=0.001) + second = module.select_joint_phase_match(trace(0), trace(0.05), trace(-0.04), 0, n, temporal_weight=0.001) + assert first == second + assert len(set(first["indices"].values())) == 1 + assert np.isclose(first["wrapped_angle_errors_rad"]["PPO"], 0.05) + assert np.isclose(first["wrapped_angle_errors_rad"]["SR"], 0.04) + assert first["objective_terms"]["temporal_distance_samples"] == 0 + + +def test_exact_candidate_selection_uses_trace_index_mapping(): + candidates = np.empty((4, 2, 3), dtype=np.float32) + for candidate_index, trace_index in enumerate(range(96, 100)): + candidates[candidate_index].fill(trace_index) + field, candidate_index = module.select_exact_candidate_field( + candidates, np.arange(96, 100), 98, expected_start=96, expected_stop=100 + ) + assert candidate_index == 2 + np.testing.assert_array_equal(field, np.full((2, 3), 98, dtype=np.float32)) + + +def test_exact_candidate_selection_rejects_mapping_mismatch(): + candidates = np.zeros((4, 2, 3), dtype=np.float32) + mismatched = np.array([96, 97, 99, 100]) + with np.testing.assert_raises_regex(ValueError, "does not exactly cover"): + module.select_exact_candidate_field( + candidates, mismatched, 99, expected_start=96, expected_stop=100 + ) + + +def test_exact_candidate_selection_requires_float32(): + candidates = np.zeros((4, 2, 3), dtype=np.float64) + with np.testing.assert_raises_regex(ValueError, "float32"): + module.select_exact_candidate_field( + candidates, np.arange(96, 100), 98, expected_start=96, expected_stop=100 + ) + + +def test_orientation_transposes_xy_to_image_yx(): + omega_xy = np.arange(12).reshape(4, 3) + oriented = module.orient_vorticity_xy_to_yx(omega_xy, (4, 3)) + assert oriented.shape == (3, 4) + np.testing.assert_array_equal(oriented[:, 2], omega_xy[2, :]) + + +def test_crop_uses_physical_x_and_centered_y_coordinates(): + field = np.arange(7 * 10).reshape(7, 10) + cropped, meta = module.crop_yx(field, (1.0, 3.0), (-1.0, 1.0), d_lattice=2.0, center_y_lattice=3.0) + np.testing.assert_array_equal(cropped, field[1:6, 2:7]) + assert meta["x_slice"] == [2, 7] + assert meta["y_slice"] == [1, 6] + assert meta["extent_xD_yD"] == [1.0, 3.0, -1.0, 1.0] + + +def test_manifest_includes_npz(tmp_path: Path): + repo = tmp_path + package = repo / "package" + package.mkdir() + (package / "data.npz").write_bytes(b"npz") + (package / "figure.png").write_bytes(b"png") + (package / "ignored.txt").write_text("ignored", encoding="utf-8") + (package / "manifest.json").write_text(json.dumps({"source_policy": "test", "summary": {}}), encoding="utf-8") + module.update_package_manifest(package, repo) + manifest = json.loads((package / "manifest.json").read_text(encoding="utf-8")) + paths = {entry["path"] for entry in manifest["artifacts"]} + assert "package/data.npz" in paths + assert "package/figure.png" in paths + assert "package/ignored.txt" not in paths + assert manifest["summary"]["flow_field_npz"] == 1 diff --git a/src/SR_analysis/tests/test_formula_provenance.py b/src/SR_analysis/tests/test_formula_provenance.py index 311f3f8..3e75863 100644 --- a/src/SR_analysis/tests/test_formula_provenance.py +++ b/src/SR_analysis/tests/test_formula_provenance.py @@ -20,10 +20,10 @@ from SR_analysis.utils.provenance import atomic_write_json_below, hash_json, saf FORMULA_DIR = Path(__file__).parents[1] / "results" / "formulas_v5" CANONICAL_FORMULAS = ( - "ill_1L_sc_front.json", - "ill_1L_sc_top.json", - "kar_d075_sc_front.json", - "kar_d075_sc_top.json", + "ill_1L_front.json", + "ill_1L_top.json", + "kar_d075_front.json", + "kar_d075_top.json", ) diff --git a/src/SR_analysis/tests/test_plot_sr_diagnostics.py b/src/SR_analysis/tests/test_plot_sr_diagnostics.py new file mode 100644 index 0000000..6b67ad7 --- /dev/null +++ b/src/SR_analysis/tests/test_plot_sr_diagnostics.py @@ -0,0 +1,22 @@ +from __future__ import annotations +import importlib.util +from pathlib import Path +import numpy as np + +MODULE_PATH = Path(__file__).resolve().parents[1] / "tools/plot_sr_diagnostics.py" +spec = importlib.util.spec_from_file_location("plot_sr_diagnostics", MODULE_PATH) +module = importlib.util.module_from_spec(spec); assert spec.loader; spec.loader.exec_module(module) + + +def test_circular_lag_recovers_roll(): + reference = np.array([0.0, 1.0, 0.0, -1.0, 0.2]) + signal = np.roll(reference, 2) + assert module.circular_lag(reference, signal) == -2 + + +def test_generalization_includes_all_training_conditions(): + repo = Path(__file__).resolve().parents[3] + points = module.generalization_points(repo) + training = [p for p in points if p["classification"] == "training"] + assert {p["scene"] for p in training} == set(module.TRAINING_SCENES) + assert len(training) == 7 diff --git a/src/SR_analysis/tools/export_flow_comparison.py b/src/SR_analysis/tools/export_flow_comparison.py new file mode 100755 index 0000000..298bdd6 --- /dev/null +++ b/src/SR_analysis/tools/export_flow_comparison.py @@ -0,0 +1,433 @@ +#!/usr/bin/env python3 +"""Export a phase-matched Target/PPO/SR Kármán vorticity comparison. + +CUDA-backed modules are imported only after argument validation. The exporter +uses the canonical Stage-3 environment and policy constructors for controlled +runs and mirrors ``build_karman_cloak_env`` exactly for the target-only run. +""" +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import platform +import subprocess +import sys +from datetime import datetime, timezone +from pathlib import Path +from typing import Any, Mapping, Sequence + +import numpy as np + +REPO_ROOT = Path(__file__).resolve().parents[3] +SRC_ROOT = REPO_ROOT / "src" +for root in (REPO_ROOT, SRC_ROOT): + if str(root) not in sys.path: + sys.path.insert(0, str(root)) + +from SR_analysis.configs import FIFO_LEN, LEGACY_CFG_DIR, get_scene # noqa: E402 +from SR_analysis.stage_3_validate import ( # noqa: E402 + DATA_TYPE, + ValidationPlan, + build_karman_environment, + build_policy, + load_formula_pair, + prepare_plan, +) +from SR_analysis.utils.provenance import atomic_write_json, hash_file, hash_json # noqa: E402 + +STEM = "07_flow_field_comparison_karman_re100" +SCHEMA = "sr-flow-comparison-v2" +CANDIDATE_DTYPE = np.dtype(np.float32) +DEFAULT_PACKAGE = REPO_ROOT / "src/SR_analysis/results/runs/article2-plotting-package-20260721" +DEFAULT_FORMULAS = REPO_ROOT / "src/SR_analysis/results/runs/article-refit-karman-topology-a-20260718/formulas" +DEFAULT_ALIGNMENT = DEFAULT_PACKAGE / "phase_alignment.json" +D_LATTICE = 20.0 +SENSOR_LAYOUT = ("upper_ux", "upper_uy", "center_ux", "center_uy", "lower_ux", "lower_uy") + + +def sha256_bytes(data: bytes) -> str: + return hashlib.sha256(data).hexdigest() + + +def git_sha() -> str | None: + try: + return subprocess.run(["git", "-C", str(REPO_ROOT), "rev-parse", "HEAD"], check=True, capture_output=True, text=True).stdout.strip() + except (OSError, subprocess.CalledProcessError): + return None + + +def wrapped_phase_difference(a: np.ndarray | float, b: np.ndarray | float) -> np.ndarray: + """Signed shortest angular difference ``a-b`` in [-pi, pi].""" + delta = np.asarray(a, dtype=np.float64) - np.asarray(b, dtype=np.float64) + return np.arctan2(np.sin(delta), np.cos(delta)) + + +def standardized_center_phase(trace: np.ndarray, start: int, stop: int) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + """Return center-sensor phase from per-trajectory stable-window z scores.""" + sensors = np.asarray(trace, dtype=np.float64) + if sensors.ndim != 2 or sensors.shape[1] < 4 or not 0 <= start < stop <= len(sensors): + raise ValueError("trace/window must provide center (ux,uy) channels") + center = sensors[start:stop, 2:4] + mean = center.mean(axis=0) + scale = center.std(axis=0, ddof=0) + if np.any(~np.isfinite(scale)) or np.any(scale <= np.finfo(float).eps): + raise ValueError("stable-window center-sensor scale is zero or non-finite") + z = (center - mean) / scale + return np.arctan2(z[:, 1], z[:, 0]), mean, scale + + +def select_joint_phase_match( + target_trace: np.ndarray, + ppo_trace: np.ndarray, + sr_trace: np.ndarray, + start: int, + stop: int, + *, + temporal_weight: float = 0.001, +) -> dict[str, Any]: + """Jointly match downstream center-sensor limit-cycle phase. + + Exhaustive stable-window search minimizes the two wrapped phase errors plus + ``temporal_weight * (|i_ppo-i_target| + |i_sr-i_target|)``. Requiring the + same local phase direction rejects branch-reversed phase-portrait matches. + """ + if temporal_weight < 0: + raise ValueError("temporal_weight must be non-negative") + phases, means, scales, directions = [], {}, {}, [] + for label, trace in (("Target", target_trace), ("PPO", ppo_trace), ("SR", sr_trace)): + theta, mean, scale = standardized_center_phase(trace, start, stop) + phases.append(theta); means[label] = mean.tolist(); scales[label] = scale.tolist() + directions.append(np.sign(np.gradient(np.unwrap(theta)))) + best = None + for ti, target_index in enumerate(range(start, stop)): + for pi, ppo_index in enumerate(range(start, stop)): + if directions[1][pi] != directions[0][ti]: + continue + for si, sr_index in enumerate(range(start, stop)): + if directions[2][si] != directions[0][ti]: + continue + ppo_error = abs(float(wrapped_phase_difference(phases[1][pi], phases[0][ti]))) + sr_error = abs(float(wrapped_phase_difference(phases[2][si], phases[0][ti]))) + temporal_distance = abs(ppo_index-target_index) + abs(sr_index-target_index) + penalty = temporal_weight * temporal_distance + objective = ppo_error + sr_error + penalty + key = (objective, temporal_distance, target_index, ppo_index, sr_index) + if best is None or key < best[0]: + best = (key, ti, pi, si, ppo_error, sr_error, penalty) + if best is None: + raise ValueError("no same-direction phase match in stable window") + key, ti, pi, si, ppo_error, sr_error, penalty = best + indices = {"Target": start+ti, "PPO": start+pi, "SR": start+si} + return { + "indices": indices, + "phase_angles_rad": {"Target": float(phases[0][ti]), "PPO": float(phases[1][pi]), "SR": float(phases[2][si])}, + "wrapped_angle_errors_rad": {"PPO": ppo_error, "SR": sr_error}, + "local_phase_direction": {"Target": int(directions[0][ti]), "PPO": int(directions[1][pi]), "SR": int(directions[2][si])}, + "stable_window_center_mean": means, + "stable_window_center_std": scales, + "objective_terms": {"angle_error_sum_rad": ppo_error+sr_error, "temporal_distance_samples": int(key[1]), "temporal_weight_rad_per_sample": temporal_weight, "temporal_penalty_rad": penalty, "objective": float(key[0])}, + "candidate_count_per_trajectory": stop-start, + } + + +def orient_vorticity_xy_to_yx(omega_xy: np.ndarray, field_shape: Sequence[int]) -> np.ndarray: + """Convert vorticity_from_ddf's (NX, NY) result to image (NY, NX).""" + nx, ny = map(int, field_shape[:2]) + omega = np.asarray(omega_xy) + if omega.shape != (nx, ny): + raise ValueError(f"expected vorticity shape {(nx, ny)}, got {omega.shape}") + return omega.T.copy() + + +def crop_yx(field_yx: np.ndarray, xlim_d: Sequence[float], ylim_d: Sequence[float], *, d_lattice: float = D_LATTICE, center_y_lattice: float | None = None) -> tuple[np.ndarray, dict[str, Any]]: + """Crop an image-oriented field using physical x/D and centered y/D.""" + field = np.asarray(field_yx) + if field.ndim != 2: + raise ValueError("field must be 2-D in (y, x) order") + ny, nx = field.shape + cy = (ny - 1) / 2 if center_y_lattice is None else float(center_y_lattice) + x0 = max(0, int(np.ceil(float(xlim_d[0]) * d_lattice))) + x1 = min(nx, int(np.floor(float(xlim_d[1]) * d_lattice)) + 1) + y0 = max(0, int(np.ceil(cy + float(ylim_d[0]) * d_lattice))) + y1 = min(ny, int(np.floor(cy + float(ylim_d[1]) * d_lattice)) + 1) + if x0 >= x1 or y0 >= y1: + raise ValueError("requested crop does not intersect field") + extent = ((x0 / d_lattice), ((x1 - 1) / d_lattice), ((y0 - cy) / d_lattice), ((y1 - 1 - cy) / d_lattice)) + return field[y0:y1, x0:x1].copy(), {"x_slice": [x0, x1], "y_slice": [y0, y1], "extent_xD_yD": list(extent)} + + +def manifest_artifacts(package_dir: Path, repo_root: Path = REPO_ROOT) -> list[dict[str, str]]: + suffixes = {".csv", ".png", ".pdf", ".md", ".json", ".npz"} + return [ + {"path": str(path.relative_to(repo_root)), "sha256": hash_file(path)} + for path in sorted(package_dir.rglob("*")) + if path.is_file() and path.suffix.lower() in suffixes and path.name != "manifest.json" + ] + + +def update_package_manifest(package_dir: Path, repo_root: Path = REPO_ROOT) -> None: + path = package_dir / "manifest.json" + old = json.loads(path.read_text(encoding="utf-8")) if path.is_file() else {} + summary = dict(old.get("summary", {})) + summary["publication_figures"] = max(5, int(summary.get("publication_figures", 0))) + summary["flow_field_npz"] = 1 + summary["presentation_pages"] = 2 + atomic_write_json(path, {"schema_version": "sr-plotting-package-v3", "source_policy": old.get("source_policy", "immutable article artifacts; no scientific refit"), "summary": summary, "artifacts": manifest_artifacts(package_dir, repo_root)}) + + +def _runtime_cfd() -> tuple[Any, Any, Any]: + from LegacyCelerisLab import FlowField + from SR_analysis.utils.cfd_interface import load_legacy_configs, vorticity_from_ddf + return FlowField, load_legacy_configs, vorticity_from_ddf + + +def _candidate_index_array(start: int, stop: int) -> np.ndarray: + """Return the exact trace indices represented by stable-window fields.""" + if not 0 <= start < stop: + raise ValueError("candidate window must satisfy 0 <= start < stop") + return np.arange(start, stop, dtype=np.int64) + + +def select_exact_candidate_field( + candidates: np.ndarray, + candidate_trace_indices: np.ndarray, + selected_trace_index: int, + *, + expected_start: int, + expected_stop: int, +) -> tuple[np.ndarray, int]: + """Select a field by its same-run trace index, rejecting mapping drift.""" + fields = np.asarray(candidates) + indices = np.asarray(candidate_trace_indices) + expected = _candidate_index_array(expected_start, expected_stop) + if fields.ndim != 3 or fields.dtype != CANDIDATE_DTYPE: + raise ValueError("candidate fields must be a float32 (sample,y,x) array") + if indices.ndim != 1 or not np.issubdtype(indices.dtype, np.integer): + raise ValueError("candidate trace indices must be a one-dimensional integer array") + if fields.shape[0] != len(indices): + raise ValueError("candidate field/index counts differ") + if not np.array_equal(indices, expected): + raise ValueError("candidate trace-index mapping does not exactly cover the stable window") + matches = np.flatnonzero(indices == int(selected_trace_index)) + if len(matches) != 1: + raise IndexError("selected trace index has no unique same-run candidate field") + candidate_index = int(matches[0]) + if candidate_index != int(selected_trace_index) - expected_start: + raise AssertionError("candidate offset and trace index disagree") + return fields[candidate_index], candidate_index + + +def _allocate_candidates(field_shape: Sequence[int], start: int, stop: int) -> np.ndarray: + nx, ny = map(int, field_shape[:2]) + return np.empty((stop - start, ny, nx), dtype=CANDIDATE_DTYPE) + + +def _store_candidate( + candidates: np.ndarray, + trace_index: int, + start: int, + omega_xy: np.ndarray, + field_shape: Sequence[int], +) -> None: + candidate_index = trace_index - start + if not 0 <= candidate_index < len(candidates): + raise IndexError("candidate trace index lies outside allocated stable window") + field = D_LATTICE * orient_vorticity_xy_to_yx(omega_xy, field_shape) + candidates[candidate_index] = np.asarray(field, dtype=CANDIDATE_DTYPE) + + +def _target_trace_and_candidates( + cfg: Mapping[str, Any], device: int, n_samples: int, candidate_start: int +) -> tuple[np.ndarray, np.ndarray, np.ndarray, tuple[int, int]]: + """Run Target once and couple every stable trace sample to its field.""" + FlowField, load_configs, vorticity_from_ddf = _runtime_cfd() + cuda_cfg, field_cfg = load_configs(LEGACY_CFG_DIR) + ff = FlowField(field_cfg._replace(viscosity=float(cfg["nu"])), cuda_cfg, device_id=device) + try: + cy = (ff.FIELD_SHAPE[1] - 1) / 2 + ff.add_cylinder((10.0 * D_LATTICE, cy, 0.0), D_LATTICE) + for y_off in (2.0, 0.0, -2.0): + ff.add_sensor((40.0 * D_LATTICE, cy + y_off * D_LATTICE, 0.0), D_LATTICE / 4.0) + n_obj = ff.obs.size // 2 + zero = np.zeros(n_obj, dtype=DATA_TYPE) + ff.run(int(4 * ff.FIELD_SHAPE[0] / float(cfg["u0"])), zero) + rows: list[np.ndarray] = [] + candidates = _allocate_candidates(ff.FIELD_SHAPE, candidate_start, n_samples) + candidate_indices = _candidate_index_array(candidate_start, n_samples) + for index in range(n_samples): + ff.run(int(cfg["sample_interval"]), zero) + rows.append(ff.obs.copy()[2:8].astype(np.float64)) + if index >= candidate_start: + _store_candidate(candidates, index, candidate_start, vorticity_from_ddf(ff, float(cfg["u0"])), ff.FIELD_SHAPE) + if len(rows) != n_samples or len(candidates) != n_samples - candidate_start: + raise AssertionError("Target trace/candidate collection is incomplete") + return np.asarray(rows), candidates, candidate_indices, tuple(map(int, ff.FIELD_SHAPE[:2])) + finally: + del ff + + +def _controlled_trace_and_candidates( + plan: ValidationPlan, device: int, n_samples: int, candidate_start: int +) -> tuple[np.ndarray, np.ndarray, np.ndarray, tuple[int, int]]: + """Run one controller once and couple stable trace samples to fields.""" + env = build_karman_environment(plan, device) + try: + policy = build_policy(plan) + raw = np.asarray(env.current_raw, dtype=np.float64) + rows: list[np.ndarray] = [] + candidates = _allocate_candidates(env.ff.FIELD_SHAPE, candidate_start, n_samples) + candidate_indices = _candidate_index_array(candidate_start, n_samples) + _, _, vorticity_from_ddf = _runtime_cfd() + for index in range(n_samples): + omega, _, _ = policy.action(raw, index) + raw = env.step(omega) + policy.observe(omega) + rows.append(raw[:6].copy()) + if index >= candidate_start: + _store_candidate(candidates, index, candidate_start, vorticity_from_ddf(env.ff, float(plan.cfg["u0"])), env.ff.FIELD_SHAPE) + if len(rows) != n_samples or len(candidates) != n_samples - candidate_start: + raise AssertionError("controlled trace/candidate collection is incomplete") + return np.asarray(rows), candidates, candidate_indices, tuple(map(int, env.ff.FIELD_SHAPE[:2])) + finally: + env.close() + + +def _make_plan(scene: str, mode: str, n_steps: int, pair: Any | None, model_device: str) -> ValidationPlan: + plan = prepare_plan(scene=scene, mode=mode, n_steps=n_steps, run_id="flow-comparison-in-memory", output_root=REPO_ROOT / ".flow-comparison-unused", formula_pair=pair) + return ValidationPlan(**{**plan.__dict__, "cfg": {**plan.cfg, "model_device": model_device}}) + + +def _plot(fields: Mapping[str, np.ndarray], crop_meta: Mapping[str, Any], cfg: Mapping[str, Any], output_dir: Path, vmax: float) -> None: + import matplotlib + matplotlib.use("Agg") + import matplotlib.pyplot as plt + from matplotlib.patches import Circle + + fig, axes = plt.subplots(1, 3, figsize=(14.5, 4.3), sharex=True, sharey=True, constrained_layout=True) + extent = crop_meta["extent_xD_yD"] + for ax, label in zip(axes, ("Target", "PPO", "SR")): + ax.imshow(fields[label], origin="lower", extent=extent, aspect="equal", cmap="RdBu_r", vmin=-vmax, vmax=vmax, interpolation="nearest") + geometry = [(10.0, 0.0, 1.0, "disturbance")] if label == "Target" else [ + (10.0, 0.0, 1.0, "disturbance"), + (float(cfg["pinball_front_x"]), 0.0, 0.5, "front"), + (float(cfg["pinball_rear_x"]), 0.75, 0.5, "upper"), + (float(cfg["pinball_rear_x"]), -0.75, 0.5, "lower"), + ] + for x, y, radius, name in geometry: + ax.add_patch(Circle((x, y), radius, facecolor="white", edgecolor="black", linewidth=0.8, zorder=4)) + ax.scatter([40.0] * 3, [2.0, 0.0, -2.0], s=12, marker="x", color="black", linewidths=0.8, zorder=5) + ax.set_title(label) + ax.set_xlabel(r"$x/D$") + axes[0].set_ylabel(r"$y/D$") + for suffix in ("png", "pdf"): + fig.savefig(output_dir / f"{STEM}.{suffix}", dpi=300 if suffix == "png" else None) + plt.close(fig) + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--scene", default="karman_re100", choices=("karman_re100",), help="explicit canonical scene") + parser.add_argument("--package-dir", type=Path, default=DEFAULT_PACKAGE) + parser.add_argument("--alignment-metadata", type=Path, default=DEFAULT_ALIGNMENT) + parser.add_argument("--formula-front", type=Path, default=DEFAULT_FORMULAS / "joint_front.json") + parser.add_argument("--formula-rear", type=Path, default=DEFAULT_FORMULAS / "joint_rear_shared_upper.json") + parser.add_argument("--temporal-weight", type=float, default=0.001, help="phase objective penalty in radians per sample of temporal separation") + parser.add_argument("--device", type=int, default=0, help="logical CFD device after CUDA_VISIBLE_DEVICES masking") + parser.add_argument("--model-device", choices=("cpu",), default="cpu") + parser.add_argument("--xlim", nargs=2, type=float, default=(7.0, 48.0), metavar=("XMIN_D", "XMAX_D")) + parser.add_argument("--ylim", nargs=2, type=float, default=(-6.0, 6.0), metavar=("YMIN_D", "YMAX_D")) + parser.add_argument("--replace", action="store_true") + return parser + + +def main(argv: Sequence[str] | None = None) -> int: + args = build_parser().parse_args(argv) + package = args.package_dir.resolve() + outputs = [package / f"{STEM}.npz", package / f"{STEM}.json", package / "figures" / f"{STEM}.png", package / "figures" / f"{STEM}.pdf"] + existing = [p for p in outputs if p.exists()] + if existing and not args.replace: + raise FileExistsError("refusing to overwrite: " + ", ".join(map(str, existing))) + alignment = json.loads(args.alignment_metadata.read_text(encoding="utf-8")) + start, stop = int(alignment["target_index_start_inclusive"]), int(alignment["target_index_stop_exclusive"]) + cfg = get_scene(args.scene) + if cfg["scene_id"] != "karman": + raise ValueError("only the Kármán environment is supported") + pair = load_formula_pair(args.formula_front, args.formula_rear) + n_samples = stop + ppo_plan = _make_plan(args.scene, "ppo", n_samples, None, args.model_device) + sr_plan = _make_plan(args.scene, "pysr", n_samples, pair, args.model_device) + + # Each condition is initialized and run exactly once. The three float32 + # stable-window buffers remain in host memory until joint phase selection. + target_trace, target_candidates, target_candidate_indices, shape = _target_trace_and_candidates( + cfg, args.device, n_samples, start + ) + ppo_trace, ppo_candidates, ppo_candidate_indices, ppo_shape = _controlled_trace_and_candidates( + ppo_plan, args.device, n_samples, start + ) + sr_trace, sr_candidates, sr_candidate_indices, sr_shape = _controlled_trace_and_candidates( + sr_plan, args.device, n_samples, start + ) + if shape != ppo_shape or shape != sr_shape: + raise ValueError("field shapes differ between cases") + + phase_match = select_joint_phase_match(target_trace, ppo_trace, sr_trace, start, stop, temporal_weight=args.temporal_weight) + selected_indices = phase_match["indices"] + candidate_sets = { + "Target": (target_candidates, target_candidate_indices), + "PPO": (ppo_candidates, ppo_candidate_indices), + "SR": (sr_candidates, sr_candidate_indices), + } + full: dict[str, np.ndarray] = {} + selected_candidate_indices: dict[str, int] = {} + for label, (candidates, trace_indices) in candidate_sets.items(): + field, candidate_index = select_exact_candidate_field( + candidates, trace_indices, selected_indices[label], expected_start=start, expected_stop=stop + ) + full[label] = field.copy() + selected_candidate_indices[label] = candidate_index + candidate_field_bytes = int(sum(candidates.nbytes for candidates, _ in candidate_sets.values())) + cropped: dict[str, np.ndarray] = {} + crop_meta = None + for label, field in full.items(): + cropped[label], meta = crop_yx(field, args.xlim, args.ylim) + crop_meta = crop_meta or meta + if meta != crop_meta: + raise AssertionError("inconsistent crop metadata") + finite = np.concatenate([np.abs(value[np.isfinite(value)]) for value in cropped.values()]) + if finite.size == 0: + raise FloatingPointError("captured fields contain no finite vorticity") + vmax = float(np.percentile(finite, 99.5)) + if not np.isfinite(vmax) or vmax <= 0: + raise FloatingPointError("invalid shared color normalization") + + package.mkdir(parents=True, exist_ok=True) + (package / "figures").mkdir(parents=True, exist_ok=True) + npz_path = outputs[0] + np.savez_compressed(npz_path, target_vorticity_yx=full["Target"], ppo_vorticity_yx=full["PPO"], sr_vorticity_yx=full["SR"], target_sensors=target_trace, ppo_sensors=ppo_trace, sr_sensors=sr_trace, selected_indices=np.asarray([selected_indices[x] for x in ("Target", "PPO", "SR")]), phase_angles_rad=np.asarray([phase_match["phase_angles_rad"][x] for x in ("Target", "PPO", "SR")]), wrapped_angle_errors_rad=np.asarray([0.0, phase_match["wrapped_angle_errors_rad"]["PPO"], phase_match["wrapped_angle_errors_rad"]["SR"]]), local_phase_direction=np.asarray([phase_match["local_phase_direction"][x] for x in ("Target", "PPO", "SR")]), selected_candidate_indices=np.asarray([selected_candidate_indices[x] for x in ("Target", "PPO", "SR")]), extent_xD_yD=np.asarray(crop_meta["extent_xD_yD"]), crop_x_slice=np.asarray(crop_meta["x_slice"]), crop_y_slice=np.asarray(crop_meta["y_slice"]), field_shape_xy=np.asarray(shape), sensor_layout=np.asarray(SENSOR_LAYOUT)) + _plot(cropped, crop_meta, cfg, package / "figures", vmax) + + dt = float(cfg["control_dt"]) + model_path = Path(ppo_plan.model_path) if ppo_plan.model_path else None + metadata = { + "schema_version": SCHEMA, "scene": args.scene, "description": "single deterministic downstream center-sensor limit-cycle phase snapshot; not full-field identity or an ensemble", + "phase_matching": {"method": "one run per condition with same-sample stable-window field capture; exhaustive joint phase search", "phase_definition": "theta=atan2(z(center_uy), z(center_ux)); each trajectory standardized separately over stable window", "objective_formula": "|wrap(theta_PPO-theta_Target)| + |wrap(theta_SR-theta_Target)| + w*(|i_PPO-i_Target|+|i_SR-i_Target|)", "target_window": [start, stop], "direction_check": "all selected local unwrapped-phase derivatives have the same sign", "figure06_alignment_context_only": alignment, **phase_match}, + "selected": {label: {"index": int(selected_indices[label]), "candidate_index": int(selected_candidate_indices[label]), "t_D_over_U0": float(selected_indices[label] * dt), "phase_angle_rad": float(phase_match["phase_angles_rad"][label]), "wrapped_phase_error_rad": 0.0 if label == "Target" else float(phase_match["wrapped_angle_errors_rad"][label])} for label in ("Target", "PPO", "SR")}, + "sample_coupling": {"contract": "for every condition, the selected field and phase-diagnostic sensor values come from the same CFD sample in the same run", "candidate_trace_index_mapping": "candidate_index = trace_index - stable_window_start; exact contiguous mapping asserted before selection", "candidate_window": [start, stop], "candidate_count_per_condition": stop-start, "candidate_dtype": CANDIDATE_DTYPE.name, "candidate_field_shape_yx": [int(shape[1]), int(shape[0])], "candidate_host_memory_bytes": candidate_field_bytes, "candidate_host_memory_mib": candidate_field_bytes / (1024**2), "disk_contract": "only the three selected full fields and complete sensor traces are stored; stable-window candidates are memory-only"}, + "field": {"quantity": "omega_z D/U0", "source": "vorticity_from_ddf", "source_shape_order": "(NX,NY)", "stored_shape_order": "(NY,NX)", "crop": crop_meta, "shared_symmetric_vmax_percentile": {"percentile": 99.5, "vmax": vmax}, "geometry_D": {"disturbance": [10.0, 0.0, 1.0], "pinball": [[cfg["pinball_front_x"], 0.0, 0.5], [cfg["pinball_rear_x"], 0.75, 0.5], [cfg["pinball_rear_x"], -0.75, 0.5]], "sensors": [[40.0, 2.0], [40.0, 0.0], [40.0, -2.0]]}}, + "contracts": {"target": "exact build_karman_cloak_env geometry, stabilization, and sample interval; target has no pinball", "controlled": "stage_3_validate.prepare_plan/build_karman_environment/build_policy", "ppo_model_device": args.model_device, "cfd_logical_device": args.device}, + "hashes": {"config": hash_json(cfg), "formula_front": hash_file(args.formula_front), "formula_rear": hash_file(args.formula_rear), "formula_pair": pair.pair_hash, "ppo_model": hash_file(model_path) if model_path and model_path.is_file() else None, "alignment_metadata": hash_file(args.alignment_metadata), "npz": hash_file(npz_path), "png": hash_file(outputs[2]), "pdf": hash_file(outputs[3]), "exporter": hash_file(Path(__file__))}, + "paths": {"npz": str(npz_path.relative_to(REPO_ROOT)), "formula_front": str(args.formula_front.resolve()), "formula_rear": str(args.formula_rear.resolve()), "ppo_model": str(model_path) if model_path else None}, + "provenance": {"git_sha": git_sha(), "command": " ".join(sys.argv), "created_utc": datetime.now(timezone.utc).isoformat(), "python": sys.version, "platform": platform.platform(), "numpy": np.__version__, "CUDA_VISIBLE_DEVICES": os.environ.get("CUDA_VISIBLE_DEVICES")}, + } + metadata["record_hash"] = hash_json(metadata) + atomic_write_json(outputs[1], metadata) + update_package_manifest(package) + print(json.dumps({"outputs": [str(p) for p in outputs], "selected": metadata["selected"], "vmax": vmax}, indent=2)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/SR_analysis/tools/plot_sr_diagnostics.py b/src/SR_analysis/tools/plot_sr_diagnostics.py index cf6ce5a..87e179e 100644 --- a/src/SR_analysis/tools/plot_sr_diagnostics.py +++ b/src/SR_analysis/tools/plot_sr_diagnostics.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 -"""Render compact diagnostic figures from the SR plotting CSV packages.""" +"""Render publication SR figures and tables from frozen article CSV/JSON artifacts.""" from __future__ import annotations import argparse import csv +import hashlib +import json from pathlib import Path -from typing import Sequence +from typing import Any, Mapping, Sequence import matplotlib.pyplot as plt import numpy as np @@ -15,6 +17,11 @@ TRAINING_SCENES = ( "illusion_0.75L", "illusion_1L", "illusion_1.5L", ) LABELS = ("K50", "K100", "K200", "K400", "I0.75L", "I1L", "I1.5L") +ACTIONS = ("front", "upper", "lower") +PACKAGE = "article2-plotting-package-20260721" +STANDARD = "article2-timeseries-csv-20260720" +LONG_SR = "article2-long-timeseries-csv-20260720" +COLORS = {"Target": "#222222", "PPO": "#2878B5", "SR": "#D95319"} def read_rows(path: Path) -> list[dict[str, str]]: @@ -22,187 +29,249 @@ def read_rows(path: Path) -> list[dict[str, str]]: return list(csv.DictReader(handle)) +def write_rows(path: Path, fieldnames: Sequence[str], rows: Sequence[Mapping[str, Any]]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("w", newline="", encoding="utf-8") as handle: + writer = csv.DictWriter(handle, fieldnames=fieldnames, lineterminator="\n") + writer.writeheader() + writer.writerows(rows) + + def final_similarity(root: Path, package: str, scene: str, source: str) -> float: - rows = read_rows(root / package / scene / f"{source}_dtw_convergence.csv") - return float(rows[-1]["similarity"]) + return float(read_rows(root / package / scene / f"{source}_dtw_convergence.csv")[-1]["similarity"]) def save(fig: plt.Figure, output: Path, stem: str) -> None: output.mkdir(parents=True, exist_ok=True) - fig.savefig(output / f"{stem}.png", dpi=220, bbox_inches="tight") + fig.savefig(output / f"{stem}.png", dpi=300, bbox_inches="tight") fig.savefig(output / f"{stem}.pdf", bbox_inches="tight") plt.close(fig) def plot_performance(root: Path, output: Path) -> None: - standard = "article2-timeseries-csv-20260720" - long_sr = "article2-long-timeseries-csv-20260720" - ppo200 = [final_similarity(root, standard, scene, "ppo") for scene in TRAINING_SCENES] - sr200 = [final_similarity(root, standard, scene, "sr") for scene in TRAINING_SCENES] - sr400 = [final_similarity(root, long_sr, scene, "sr") for scene in TRAINING_SCENES] - long_ppo_root = root / "article2-plotting-package-20260721/long_ppo" - ppo400 = [] - for scene in TRAINING_SCENES: - path = long_ppo_root / scene / "ppo_dtw_convergence.csv" - ppo400.append(float(read_rows(path)[-1]["similarity"]) if path.is_file() else np.nan) + ppo200 = np.array([final_similarity(root, STANDARD, s, "ppo") for s in TRAINING_SCENES]) + sr200 = np.array([final_similarity(root, STANDARD, s, "sr") for s in TRAINING_SCENES]) + ppo400 = np.array([final_similarity(root / PACKAGE / "long_ppo", "", s, "ppo") for s in TRAINING_SCENES]) + sr400 = np.array([final_similarity(root, LONG_SR, s, "sr") for s in TRAINING_SCENES]) + x = np.arange(len(LABELS)); width = 0.36 + fig, axes = plt.subplots(1, 2, figsize=(12.2, 4.7), gridspec_kw={"width_ratios": (1.15, 1)}) + axes[0].bar(x - width / 2, ppo400, width, label="PPO — 400 control steps", color=COLORS["PPO"]) + axes[0].bar(x + width / 2, sr400, width, label="SR — 400 control steps", color=COLORS["SR"]) + axes[0].set_ylabel("Legacy DTW similarity"); axes[0].set_ylim(0.75, 1.005) + axes[0].set_title("(a) Closed-loop performance at 400 control steps") + axes[0].legend(frameon=False, fontsize=9) + axes[1].axhline(0, color="0.25", linewidth=1) + axes[1].scatter(x - 0.08, ppo400 - ppo200, label="PPO: 400 − 200 control steps", marker="o", s=48, color=COLORS["PPO"]) + axes[1].scatter(x + 0.08, sr400 - sr200, label="SR: 400 − 200 control steps", marker="s", s=48, color=COLORS["SR"]) + axes[1].set_ylabel("Change in legacy DTW similarity") + axes[1].set_title("(b) Duration stability from 200 to 400 steps") + axes[1].legend(frameon=False, fontsize=9) + for ax in axes: + ax.set_xticks(x, LABELS); ax.set_xlabel("Training case"); ax.grid(axis="y", alpha=0.25) + fig.tight_layout(); save(fig, output, "01_training_case_performance") - x = np.arange(len(LABELS)) - width = 0.2 - fig, ax = plt.subplots(figsize=(11, 4.8)) - ax.bar(x - 1.5 * width, ppo200, width, label="PPO 200") - ax.bar(x - 0.5 * width, sr200, width, label="SR 200") - ax.bar(x + 0.5 * width, ppo400, width, label="PPO 400") - ax.bar(x + 1.5 * width, sr400, width, label="SR 400") - ax.set_ylabel("Legacy DTW similarity") - ax.set_xlabel("Training case") - ax.set_xticks(x, LABELS) - ax.set_ylim(0.65, 1.01) - ax.grid(axis="y", alpha=0.25) - ax.legend(ncol=4, frameon=False) - ax.set_title("Closed-loop PPO and symbolic-controller performance") - fig.tight_layout() - save(fig, output, "01_training_case_performance") + +def generalization_points(repo_root: Path) -> list[dict[str, Any]]: + path = repo_root / "src/SR_analysis/results/runs/article2-generalization-summary-20260720/generalization.csv" + points: list[dict[str, Any]] = [dict(row) for row in read_rows(path)] + run_root = repo_root / "src/SR_analysis/results/runs" + for scene in TRAINING_SCENES: + objective = "karman" if scene.startswith("karman") else "illusion" + parameter = float(scene.removeprefix("karman_re").removeprefix("illusion_").removesuffix("L")) + points.append({"objective": objective, "scene": scene, "parameter": parameter, + "classification": "training", "legacy_dtw": final_similarity(run_root, STANDARD, scene, "sr")}) + return points def plot_generalization(repo_root: Path, output: Path) -> None: - path = repo_root / "src/SR_analysis/results/runs/article2-generalization-summary-20260720/generalization.csv" - rows = read_rows(path) - fig, axes = plt.subplots(1, 2, figsize=(10.5, 4.2)) - for ax, objective, xlabel in zip(axes, ("karman", "illusion"), ("Code Reynolds label", "Target-size label L")): - subset = sorted((row for row in rows if row["objective"] == objective), key=lambda row: float(row["parameter"])) - interpolation = [row for row in subset if row["classification"] == "interpolation"] - extrapolation = [row for row in subset if row["classification"] == "extrapolation"] - ax.plot([float(r["parameter"]) for r in subset], [float(r["legacy_dtw"]) for r in subset], color="0.6", linewidth=1) - ax.scatter([float(r["parameter"]) for r in interpolation], [float(r["legacy_dtw"]) for r in interpolation], label="Interpolation", s=48) - ax.scatter([float(r["parameter"]) for r in extrapolation], [float(r["legacy_dtw"]) for r in extrapolation], label="Extrapolation", marker="s", s=48) - ax.set_xlabel(xlabel) - ax.set_ylabel("Legacy DTW similarity") - ax.set_ylim(0.7, 1.01) - ax.grid(alpha=0.25) - ax.legend(frameon=False) - ax.set_title("Kármán unseen Re" if objective == "karman" else "Illusion unseen target size") - fig.tight_layout() - save(fig, output, "02_pointwise_generalization") + rows = generalization_points(repo_root) + fig, axes = plt.subplots(1, 2, figsize=(11, 4.4)) + styles = {"training": ("o", "#222222", "Training condition"), + "interpolation": ("^", "#2E8B57", "Unseen interpolation"), + "extrapolation": ("s", "#A23B72", "Unseen extrapolation")} + for ax, objective, xlabel in zip(axes, ("karman", "illusion"), ("Reynolds-number label", "Target-size label, L")): + subset = [r for r in rows if r["objective"] == objective] + for category, (marker, color, label) in styles.items(): + selected = sorted((r for r in subset if r["classification"] == category), key=lambda r: float(r["parameter"])) + ax.scatter([float(r["parameter"]) for r in selected], [float(r["legacy_dtw"]) for r in selected], + marker=marker, color=color, edgecolor="white", linewidth=0.6, s=65, label=label, zorder=3) + ax.set_xlabel(xlabel); ax.set_ylabel("Legacy DTW similarity"); ax.set_ylim(0.7, 1.01) + ax.grid(alpha=0.25); ax.legend(frameon=False, fontsize=8.5) + ax.set_title("Kármán conditions" if objective == "karman" else "Illusion conditions") + fig.suptitle("Frozen SR controller: training and pointwise unseen 200-step realizations", y=1.01) + fig.tight_layout(); save(fig, output, "02_pointwise_generalization") def plot_steady(repo_root: Path, output: Path) -> None: - path = repo_root / "src/SR_analysis/results/runs/article2-steady-analysis-20260720/steady_rotation_sweep.csv" - rows = read_rows(path) - magnitude = np.asarray([float(row["rear_alpha_magnitude"]) for row in rows]) - similarity = np.asarray([float(row["legacy_dtw"]) for row in rows]) - error = np.asarray([float(row["mean_abs_sensor_error"]) for row in rows]) - fig, ax = plt.subplots(figsize=(7.2, 4.5)) - secondary = ax.twinx() + rows = read_rows(repo_root / "src/SR_analysis/results/runs/article2-steady-analysis-20260720/steady_rotation_sweep.csv") + magnitude = np.asarray([float(r["rear_alpha_magnitude"]) for r in rows]) + similarity = np.asarray([float(r["legacy_dtw"]) for r in rows]) + error = np.asarray([float(r["mean_abs_sensor_error"]) for r in rows]) + fig, ax = plt.subplots(figsize=(7.2, 4.5)); secondary = ax.twinx() ax.plot(magnitude, similarity, marker="o", label="DTW similarity") secondary.plot(magnitude, error, marker="s", linestyle="--", label="Mean sensor error", color="tab:orange") ax.axvline(3.4312086805, linestyle=":", color="0.35", label="SR rear constant 3.4312") - ax.set_xlabel("Rear counter-rotation magnitude |α|") - ax.set_ylabel("Legacy DTW similarity") - secondary.set_ylabel("Mean absolute sensor error") - ax.set_ylim(0.6, 1.01) - ax.grid(alpha=0.25) - lines = ax.get_lines() + secondary.get_lines() - ax.legend(lines, [line.get_label() for line in lines], frameon=False, loc="center right") - ax.set_title("Steady-cloak constant-rotation calibration") - fig.tight_layout() + ax.set_xlabel("Rear counter-rotation magnitude |α|"); ax.set_ylabel("Legacy DTW similarity") + secondary.set_ylabel("Mean absolute sensor error"); ax.set_ylim(0.6, 1.01); ax.grid(alpha=0.25) + lines = ax.get_lines() + secondary.get_lines(); ax.legend(lines, [line.get_label() for line in lines], frameon=False, loc="center right") + ax.set_title("Steady-cloak constant-rotation calibration"); fig.tight_layout() save(fig, output, "03_steady_rotation_calibration") -def plot_offline_residuals(root: Path, output: Path) -> None: - rows = read_rows(root / "article2-plotting-package-20260721/offline_predictions.csv") - rmse = {scene: [] for scene in TRAINING_SCENES} +def build_offline_table(root: Path, tables: Path) -> None: + source = root / PACKAGE / "offline_predictions.csv"; rows = read_rows(source); output = [] for scene in TRAINING_SCENES: - subset = [row for row in rows if row["scene"] == scene] - for action in ("front", "upper", "lower"): - residual = np.asarray([float(row[f"residual_alpha_{action}"]) for row in subset]) - rmse[scene].append(float(np.sqrt(np.mean(residual**2)))) - fig, ax = plt.subplots(figsize=(10.5, 4.5)) - x = np.arange(len(LABELS)) - width = 0.25 - for index, action in enumerate(("Front", "Upper", "Lower")): - ax.bar(x + (index - 1) * width, [rmse[scene][index] for scene in TRAINING_SCENES], width, label=action) - ax.set_xticks(x, LABELS) - ax.set_xlabel("Training case") - ax.set_ylabel("Offline action RMSE in α") - ax.set_title("SR imitation error on PPO-visited causal states") - ax.grid(axis="y", alpha=0.25) - ax.legend(frameon=False, ncol=3) - fig.tight_layout() - save(fig, output, "04_offline_action_rmse") + subset = [r for r in rows if r["scene"] == scene] + values = {a: float(np.sqrt(np.mean([float(r[f"residual_alpha_{a}"]) ** 2 for r in subset]))) for a in ACTIONS} + aggregate = float(np.sqrt(np.mean([float(r[f"residual_alpha_{a}"]) ** 2 for r in subset for a in ACTIONS]))) + output.append({"objective": subset[0]["objective"], "training_case": scene, "n_causally_aligned_states": len(subset), + **{f"rmse_alpha_{a}": f"{values[a]:.8f}" for a in ACTIONS}, "aggregate_rmse_alpha": f"{aggregate:.8f}"}) + fields = ("objective", "training_case", "n_causally_aligned_states", *(f"rmse_alpha_{a}" for a in ACTIONS), "aggregate_rmse_alpha") + write_rows(tables / "offline_action_rmse.csv", fields, output) + lines = ["# Offline SR-vs-PPO next-action RMSE", "", + "RMSE is evaluated on causally aligned PPO-visited states: each post-action state predicts the next PPO action. This is an offline imitation diagnostic, not closed-loop performance.", "", + "| Objective | Training case | States | Front RMSE | Upper RMSE | Lower RMSE | Aggregate RMSE |", + "|---|---|---:|---:|---:|---:|---:|"] + for r in output: + lines.append(f"| {r['objective']} | {r['training_case']} | {r['n_causally_aligned_states']} | {r['rmse_alpha_front']} | {r['rmse_alpha_upper']} | {r['rmse_alpha_lower']} | {r['aggregate_rmse_alpha']} |") + lines += ["", "All action errors are in dimensionless surface-speed α. Aggregate RMSE pools the three action channels and all aligned states within a case."] + (tables / "offline_action_rmse.md").write_text("\n".join(lines) + "\n", encoding="utf-8") -def plot_ablation(root: Path, output: Path) -> None: - rows = read_rows(root / "article2-plotting-package-20260721/ablation_summary.csv") - karman_variants = ("k_front0", "k_rear0", "k_rear1") - karman_scenes = ("karman_re50", "karman_re100", "karman_re200", "karman_re400") - illusion_variants = ("i_front0", "i_front1", "i_rear0", "i_rear1") - illusion_scenes = ("illusion_0.75L", "illusion_1L", "illusion_1.5L") - fig, axes = plt.subplots(1, 2, figsize=(11.5, 4.5)) - for ax, variants, scenes, title in ( - (axes[0], karman_variants, karman_scenes, "Kármán 40-step term deletion"), - (axes[1], illusion_variants, illusion_scenes, "Illusion 40-step term deletion"), - ): - x = np.arange(len(scenes)) - width = 0.8 / len(variants) - for index, variant in enumerate(variants): - values = [float(next(row["similarity"] for row in rows if row["variant"] == variant and row["scene"] == scene)) for scene in scenes] - ax.bar(x + (index - (len(variants) - 1) / 2) * width, values, width, label=variant) - ax.set_xticks(x, [scene.replace("karman_", "").replace("illusion_", "") for scene in scenes]) - ax.set_xlabel("Case") - ax.set_ylabel("Legacy DTW similarity") - ax.set_ylim(0.6, 1.0) - ax.grid(axis="y", alpha=0.25) - ax.legend(frameon=False, fontsize=8) - ax.set_title(title) - fig.tight_layout() - save(fig, output, "05_term_deletion") +def _load_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) -def plot_example_timeseries(root: Path, output: Path) -> None: - package = root / "article2-timeseries-csv-20260720/karman_re100" - sr = read_rows(package / "sr_wide.csv") - ppo = read_rows(package / "ppo_wide.csv") - target = read_rows(package / "target_wide.csv") - fig, axes = plt.subplots(2, 1, figsize=(10.5, 6.2), sharex=False) - axes[0].plot([float(r["t_D"]) for r in target], [float(r["sensors_center_ux"]) for r in target], label="Target reference") - axes[0].plot([float(r["t_D"]) for r in ppo], [float(r["sensors_center_ux"]) for r in ppo], label="PPO") - axes[0].plot([float(r["t_D"]) for r in sr], [float(r["sensors_center_ux"]) for r in sr], label="SR") - axes[0].set_ylabel("Center sensor ux / U0") - axes[0].set_xlabel("t U0 / D") - axes[0].legend(frameon=False, ncol=3) - axes[0].grid(alpha=0.25) - axes[0].set_title("Kármán Re100 observer signal") - for action, label in (("front", "Front"), ("upper", "Upper"), ("lower", "Lower")): - axes[1].plot([float(r["t_D"]) for r in sr], [float(r[f"actions_alpha_{action}"]) for r in sr], label=label) - axes[1].set_ylabel("SR action α") - axes[1].set_xlabel("t U0 / D") - axes[1].legend(frameon=False, ncol=3) - axes[1].grid(alpha=0.25) - fig.tight_layout() - save(fig, output, "06_example_timeseries_karman_re100") +def build_term_deletion_table(repo_root: Path, root: Path, tables: Path) -> None: + formula_root = repo_root / "src/SR_analysis/results/runs/article-ablation-formulas-v2-20260718/formulas" + parent_root = repo_root / "src/SR_analysis/results/runs" + specs = { + "k_front0": ("karman", "front odd projection", "karman_front__delete_t0.json", "article-refit-karman-topology-a-20260718/formulas/joint_front.json", "article-L2-karman-20260718"), + "k_rear0": ("karman", "upper/lower shared-symmetry rear", "karman_rear__delete_t0.json", "article-refit-karman-topology-a-20260718/formulas/joint_rear_shared_upper.json", "article-L2-karman-20260718"), + "k_rear1": ("karman", "upper/lower shared-symmetry rear", "karman_rear__delete_t1.json", "article-refit-karman-topology-a-20260718/formulas/joint_rear_shared_upper.json", "article-L2-karman-20260718"), + "i_front0": ("illusion", "front odd projection", "illusion_front__delete_t0.json", "article-refit-illusion-topology-a-20260718/formulas/joint_front.json", "article-L2-illusionA-20260718"), + "i_front1": ("illusion", "front odd projection", "illusion_front__delete_t1.json", "article-refit-illusion-topology-a-20260718/formulas/joint_front.json", "article-L2-illusionA-20260718"), + "i_rear0": ("illusion", "upper/lower shared-symmetry rear", "illusion_rear__delete_t0.json", "article-refit-illusion-topology-a-20260718/formulas/joint_rear_shared_upper.json", "article-L2-illusionA-20260718"), + "i_rear1": ("illusion", "upper/lower shared-symmetry rear", "illusion_rear__delete_t1.json", "article-refit-illusion-topology-a-20260718/formulas/joint_rear_shared_upper.json", "article-L2-illusionA-20260718"), + } + ablations = read_rows(root / PACKAGE / "ablation_summary.csv"); output = [] + for variant, (objective, head, variant_file, parent_file, parent_run) in specs.items(): + variant_json = _load_json(formula_root / variant_file); parent_json = _load_json(parent_root / parent_file) + deleted = variant_json["variant_metadata"]["term_identity"] + variant_rows = [r for r in ablations if r["variant"] == variant] + similarities = [float(r["similarity"]) for r in variant_rows] + parent_metrics = {} + for path in (parent_root / parent_run / "validations").glob("*.json"): + record = _load_json(path); parent_metrics[record["scene"]] = float(record["metrics"]["legacy_reference_cycle_vs_last_recorded_cycle"]["similarity"]) + for r in sorted(variant_rows, key=lambda x: TRAINING_SCENES.index(x["scene"])): + parent_value = parent_metrics[r["scene"]]; value = float(r["similarity"]) + output.append({"objective": objective, "head": head, "variant": variant, + "parent_formula": parent_json["deployment_expression"], "deleted_term": deleted, + "retained_formula": variant_json["deployment_expression"], "case": r["scene"], "steps": r["steps"], + "legacy_dtw": f"{value:.8f}", "parent_legacy_dtw_40_step": f"{parent_value:.8f}", + "delta_vs_parent_40_step": f"{value-parent_value:+.8f}", "variant_mean": f"{np.mean(similarities):.8f}", + "variant_min": f"{np.min(similarities):.8f}"}) + fields = tuple(output[0]); write_rows(tables / "term_deletion.csv", fields, output) + lines = ["# Forty-step closed-loop term deletion", "", + "Every comparison uses the same 40-control-step window and the legacy DTW metric. Parent values come from the scientifically comparable parent L2 runs; deltas are deletion minus parent. Formula identities are read from the canonical parent and deletion JSON artifacts.", "", + "Front formulas are scalar generators deployed through the odd projection `α_F(x) = ½[f(x) − f(Gx)]`. Rear formulas define the upper action; the lower action is mapped by `α_L(x) = −α_U(Gx)`.", "", + "| Objective | Head | Variant | Parent formula | Deleted term | Retained formula | Case | DTW | Parent DTW | Δ | Mean | Min |", + "|---|---|---|---|---|---|---|---:|---:|---:|---:|---:|"] + for r in output: + fmt = lambda s: f"`{s.replace('*', '·')}`" + lines.append(f"| {r['objective']} | {r['head']} | {r['variant']} | {fmt(r['parent_formula'])} | {fmt(r['deleted_term'])} | {fmt(r['retained_formula'])} | {r['case']} | {r['legacy_dtw']} | {r['parent_legacy_dtw_40_step']} | {r['delta_vs_parent_40_step']} | {r['variant_mean']} | {r['variant_min']} |") + lines += ["", "Interpretation: Kármán deletion effects are term-dependent, with deleting the rear constant producing the largest degradation; the tested front term is weak over this short window. Illusion deletions remain stable and comparatively close to their parent, so these runs do not establish uniqueness of every term. Means and minima summarize the listed training cases only."] + (tables / "term_deletion.md").write_text("\n".join(lines) + "\n", encoding="utf-8") -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[3]) - parser.add_argument("--output-dir", type=Path, required=True) - return parser +def circular_lag(reference: np.ndarray, signal: np.ndarray) -> int: + """Return deterministic roll applied to signal to maximize normalized circular correlation.""" + ref = np.asarray(reference, dtype=float) - np.mean(reference); sig = np.asarray(signal, dtype=float) - np.mean(signal) + if len(ref) != len(sig) or len(ref) == 0: raise ValueError("signals must have equal non-zero length") + scores = np.array([np.dot(ref, np.roll(sig, lag)) for lag in range(len(ref))]) / (np.linalg.norm(ref) * np.linalg.norm(sig)) + lag = int(np.flatnonzero(np.isclose(scores, scores.max(), rtol=0, atol=1e-12))[0]) + return lag if lag <= len(ref) // 2 else lag - len(ref) + + +def plot_example_timeseries(root: Path, output: Path, package_dir: Path) -> dict[str, Any]: + source = root / STANDARD / "karman_re100" + data = {name: read_rows(source / f"{name.lower()}_wide.csv") for name in ("Target", "PPO", "SR")} + start, stop = 96, 146 # 50 samples, approximately three target cycles, late in the 150-row target record. + phase = np.linspace(0, 6 * np.pi, stop - start, endpoint=False) + signal_name = "sensors_center_uy" + reference = np.array([float(r[signal_name]) for r in data["Target"][start:stop]]) + lags = {"Target": 0} + aligned: dict[str, list[dict[str, str]]] = {"Target": data["Target"][start:stop]} + for name in ("PPO", "SR"): + segment = data[name][start:stop] + lag = circular_lag(reference, np.array([float(r[signal_name]) for r in segment])) + lags[name] = lag; aligned[name] = list(np.roll(np.asarray(segment, dtype=object), lag)) + fig, axes = plt.subplots(2, 3, figsize=(12, 7.2)) + sensors = (("upper", "Upper sensor"), ("center", "Center sensor"), ("lower", "Lower sensor")) + styles = {"Target": "-", "PPO": "--", "SR": "-."} + for col, (sensor, title) in enumerate(sensors): + for name in ("Target", "PPO", "SR"): + rows = aligned[name] + axes[0, col].plot([float(r[f"sensors_{sensor}_ux"]) for r in rows], [float(r[f"sensors_{sensor}_uy"]) for r in rows], + color=COLORS[name], linestyle=styles[name], linewidth=1.7, label=name) + axes[0, col].set_xlabel(r"$u/U_0$"); axes[0, col].set_ylabel(r"$v/U_0$"); axes[0, col].set_title(title); axes[0, col].grid(alpha=0.22) + axes[0, col].scatter(float(aligned["Target"][0][f"sensors_{sensor}_ux"]), float(aligned["Target"][0][f"sensors_{sensor}_uy"]), color=COLORS["Target"], marker="o", s=20, zorder=4) + axes[0, 0].legend(frameon=False, ncol=3, fontsize=9) + for col, action in enumerate(ACTIONS): + for name in ("PPO", "SR"): + axes[1, col].plot(phase / (2 * np.pi), [float(r[f"actions_alpha_{action}"]) for r in aligned[name]], color=COLORS[name], linestyle=styles[name], linewidth=1.6, label=name) + axes[1, col].set_xlabel("Target phase / 2π"); axes[1, col].set_ylabel("Action α"); axes[1, col].set_title(f"{action.capitalize()} action"); axes[1, col].set_xticks((0, 1, 2, 3)); axes[1, col].grid(alpha=0.22) + axes[1, 0].legend(frameon=False, ncol=2, fontsize=9) + fig.suptitle("Kármán Re100: three stable target cycles with phase-aligned controllers", y=0.995) + fig.tight_layout(); save(fig, output, "06_example_timeseries_karman_re100") + metadata = {"scene": "karman_re100", "source_package": STANDARD, "target_index_start_inclusive": start, + "target_index_stop_exclusive": stop, "target_t_D_start": float(data["Target"][start]["t_D"]), + "target_t_D_end": float(data["Target"][stop-1]["t_D"]), "samples": stop-start, "displayed_target_cycles": 3, + "alignment_signal": signal_name, "alignment_method": "mean-centered normalized circular cross-correlation; smallest maximizing roll selected", + "roll_lags_samples": lags, "control_dt_D_over_U0": 0.4, "target_actions_plotted": False} + (package_dir / "phase_alignment.json").write_text(json.dumps(metadata, indent=2) + "\n", encoding="utf-8") + return metadata + + +def update_manifest(repo_root: Path, package_dir: Path) -> None: + old = _load_json(package_dir / "manifest.json"); summary = dict(old.get("summary", {})) + summary.pop("diagnostic_figures", None); summary.update({"publication_figures": 5, "publication_tables": 2, "presentation_pages": 2}) + suffixes = {".csv", ".png", ".pdf", ".md", ".json", ".npz"}; artifacts = [] + for path in sorted(p for p in package_dir.rglob("*") if p.is_file() and p.suffix in suffixes and p.name != "manifest.json"): + artifacts.append({"path": str(path.relative_to(repo_root)), "sha256": hashlib.sha256(path.read_bytes()).hexdigest()}) + record = {"schema_version": "sr-plotting-package-v3", "source_policy": old["source_policy"], "summary": summary, "artifacts": artifacts} + (package_dir / "manifest.json").write_text(json.dumps(record, indent=2) + "\n", encoding="utf-8") + + +def write_package_readme(package_dir: Path, metadata: Mapping[str, Any]) -> None: + text = f"""# SR publication plotting package + +This package contains five generated publication figures, two replacement tables, and two PPT-ready 16:9 summary pages. All values are derived from frozen canonical artifacts without formula refitting. + +- `01_training_case_performance`: panel (a) compares PPO and SR absolute closed-loop legacy DTW similarity at **400 control steps** for all seven training cases; panel (b) reports each controller's 400-minus-200 change. Here 200 and 400 are run durations in control steps, not Reynolds numbers. +- `02_pointwise_generalization`: 200-step SR results at training conditions and sampled unseen interpolation/extrapolation conditions. Each unseen point is one realization. Categories are unconnected because the evidence does not establish a continuous parameter law or statistical robustness. +- `03_steady_rotation_calibration`: the retained disturbance-free constant rear-rotation sweep diagnostic. +- `06_example_timeseries_karman_re100`: late-window phase portraits and PPO/SR actions over approximately three target cycles. Target indices {metadata['target_index_start_inclusive']}–{metadata['target_index_stop_exclusive'] - 1} (`t_D={metadata['target_t_D_start']:.1f}`–`{metadata['target_t_D_end']:.1f}`) are used. PPO and SR segments at the same indices are circularly aligned to target `sensors_center_uy` by mean-centered normalized cross-correlation; deterministic roll lags are PPO={metadata['roll_lags_samples']['PPO']} and SR={metadata['roll_lags_samples']['SR']} samples. No target actions exist or are plotted. +- `07_flow_field_comparison_karman_re100`: a deterministic stable-cycle snapshot trio. Over [96,146), downstream center-sensor `(ux,uy)` is standardized separately per trajectory; an exhaustive joint search minimizes wrapped phase errors plus a 0.001 rad/sample temporal-separation penalty with a same-direction branch check. This matches center-sensor limit-cycle phase, not six-sensor state equality or exact full-field identity. +- `tables/offline_action_rmse.*`: offline SR-vs-PPO next-action RMSE on causally aligned PPO-visited states. This is not closed-loop performance. +- `tables/term_deletion.*`: actual 40-step closed-loop deletion results, canonical formula identities, same-window parent comparisons, and aggregate mean/min summaries. + +The `presentation/` directory contains two white-background 16:9 PNG/PDF pages and a source-artifact README. PNG and PDF files share each retained figure stem. `phase_alignment.json` records the exact Figure 06 selection/alignment contract. `manifest.json` hashes every package CSV, Markdown, JSON/NPZ metadata or field artifact, and publication figure. +""" + (package_dir / "README.md").write_text(text, encoding="utf-8") def main(argv: Sequence[str] | None = None) -> int: - args = build_parser().parse_args(argv) - repo_root = args.repo_root.resolve() - run_root = repo_root / "src/SR_analysis/results/runs" - output = args.output_dir if args.output_dir.is_absolute() else repo_root / args.output_dir - plt.style.use("seaborn-v0_8-whitegrid") - plot_performance(run_root, output) - plot_generalization(repo_root, output) - plot_steady(repo_root, output) - plot_offline_residuals(run_root, output) - plot_ablation(run_root, output) - plot_example_timeseries(run_root, output) + parser = argparse.ArgumentParser(description=__doc__); parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[3]); parser.add_argument("--output-dir", type=Path, required=True) + args = parser.parse_args(argv); repo_root = args.repo_root.resolve(); root = repo_root / "src/SR_analysis/results/runs" + output = args.output_dir if args.output_dir.is_absolute() else repo_root / args.output_dir; package_dir = root / PACKAGE + plt.style.use("seaborn-v0_8-whitegrid"); plt.rcParams.update({"font.size": 10, "axes.titlesize": 11, "axes.labelsize": 10, "legend.fontsize": 9, "pdf.fonttype": 42}) + for obsolete in ("04_offline_action_rmse", "05_term_deletion"): + for suffix in (".png", ".pdf"): (output / f"{obsolete}{suffix}").unlink(missing_ok=True) + plot_performance(root, output); plot_generalization(repo_root, output); plot_steady(repo_root, output) + tables = package_dir / "tables"; build_offline_table(root, tables); build_term_deletion_table(repo_root, root, tables) + metadata = plot_example_timeseries(root, output, package_dir); write_package_readme(package_dir, metadata); update_manifest(repo_root, package_dir) return 0 -if __name__ == "__main__": - raise SystemExit(main()) +if __name__ == "__main__": raise SystemExit(main()) diff --git a/src/SR_analysis/tools/plot_sr_presentation.py b/src/SR_analysis/tools/plot_sr_presentation.py new file mode 100755 index 0000000..60b8fa4 --- /dev/null +++ b/src/SR_analysis/tools/plot_sr_presentation.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""Render two 16:9 presentation pages from frozen canonical SR artifacts.""" +from __future__ import annotations +import argparse, csv, json, re +from pathlib import Path +from typing import Any +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np + +BLUE, ORANGE, INK, MUTED, PALE = "#1769AA", "#D65F1E", "#17202A", "#52606D", "#F3F7FA" + +def load_json(path: Path) -> dict[str, Any]: return json.loads(path.read_text(encoding="utf-8")) +def rows(path: Path) -> list[dict[str,str]]: + with path.open(newline="", encoding="utf-8") as f: return list(csv.DictReader(f)) +def similarity(path: Path) -> float: return float(rows(path)[-1]["similarity"]) +def expression(root: Path, family: str, head: str) -> str: + p=root/f"article-refit-{family}-topology-a-20260718/formulas/{head}.json" + return load_json(p)["deployment_expression"] +def pretty(expr: str) -> str: + rounded = re.sub(r"(? None: + out.mkdir(parents=True, exist_ok=True); fig.savefig(out/f"{stem}.png", dpi=200, facecolor="white"); fig.savefig(out/f"{stem}.pdf", facecolor="white"); plt.close(fig) +def base(title: str, subtitle: str): + fig=plt.figure(figsize=(16,9), facecolor="white"); fig.text(.05,.94,title,fontsize=28,weight="bold",color=INK,va="top"); fig.text(.05,.885,subtitle,fontsize=13,color=MUTED,va="top"); return fig + +def page_method(root: Path, out: Path) -> None: + fig=base("From PPO trajectories to an executable symbolic controller", "Causal alignment, symmetry, all-case refitting, then closed-loop CFD—not offline fit alone") + steps=[("1","PPO trajectories","states • forces • actions"),("2","Causal dataset","post-action state → next action"),("3","Topology discovery","G-symmetry-aware symbolic search"),("4","Coefficient refit","fixed topology • all family cases"),("5","CFD validation","40 / 200 / 400 steps • deletion")] + for i,(n,h,d) in enumerate(steps): + x=.05+i*.19; fig.patches.append(plt.Rectangle((x,.67),.16,.13,transform=fig.transFigure,fc=PALE,ec=BLUE,lw=1.5)) + fig.text(x+.012,.765,n,fontsize=20,weight="bold",color=BLUE);fig.text(x+.042,.765,h,fontsize=14,weight="bold",color=INK);fig.text(x+.012,.705,d,fontsize=10.5,color=MUTED) + if i<4: fig.text(x+.17,.725,"→",fontsize=24,color=ORANGE,ha="center") + formulas={f:(pretty(expression(root,f,"joint_front")),pretty(expression(root,f,"joint_rear_shared_upper"))) for f in ("karman","illusion")} + for x,f,title in ((.05,"karman","Kármán shared family"),(.52,"illusion","Illusion shared numerical family")): + fig.patches.append(plt.Rectangle((x,.25),.43,.34,transform=fig.transFigure,fc="white",ec="#CAD5DF",lw=1.2)) + fig.text(x+.025,.545,title,fontsize=18,weight="bold",color=BLUE if f=="karman" else ORANGE) + front,rear=formulas[f] + fig.text(x+.025,.485,r"$\alpha_F(x)=\frac{1}{2}[f(x)-f(Gx)]$",fontsize=14,color=INK) + fig.text(x+.025,.435,rf"$f(x)={front}$",fontsize=14,color=INK) + fig.text(x+.025,.375,rf"$\alpha_U(x)={rear}$",fontsize=14,color=INK) + fig.text(x+.025,.325,r"$\alpha_L(x)=-\alpha_U(Gx)$",fontsize=14,color=INK) + note=("Persistent rear counter-rotation constant dominates;\nrear lift correction is secondary." if f=="karman" else "Deployable shared family; deletion evidence does not\nestablish term uniqueness.") + fig.text(x+.025,.285,note,fontsize=12,color=MUTED,linespacing=1.35) + fig.text(.05,.12,"G mirrors upper/lower quantities and applies the signed symmetry map. α is dimensionless cylinder surface speed.",fontsize=11.5,color=MUTED) + save(fig,out,"01_sr_method_and_formulas") + +def page_evidence(root: Path, out: Path) -> None: + fig=base("Quantitative evidence at a glance", "Legacy DTW similarity; deterministic frozen artifacts; control-step windows stated explicitly") + pkg=root/"article2-plotting-package-20260721"; standard=root/"article2-timeseries-csv-20260720"; longsr=root/"article2-long-timeseries-csv-20260720" + scenes={"Kármán":["karman_re50","karman_re100","karman_re200","karman_re400"],"Illusion":["illusion_0.75L","illusion_1L","illusion_1.5L"]} + vals={} + for fam,ss in scenes.items(): + sr=np.array([similarity(longsr/s/"sr_dtw_convergence.csv") for s in ss]); ppo=np.array([similarity(pkg/"long_ppo"/s/"ppo_dtw_convergence.csv") for s in ss]); vals[fam]=(sr,ppo) + gen=rows(root/"article2-generalization-summary-20260720/generalization.csv") + deletion=rows(pkg/"tables/term_deletion.csv") + cards=[(.05,.61,.42,.20,"400-step training cases",[(f"Kármán SR range",f"{vals['Kármán'][0].min():.3f}–{vals['Kármán'][0].max():.3f}"),("Kármán mean SR / PPO",f"{vals['Kármán'][0].mean():.3f} / {vals['Kármán'][1].mean():.3f}"),("Illusion SR range",f"{vals['Illusion'][0].min():.3f}–{vals['Illusion'][0].max():.3f}"),("Illusion mean SR / PPO",f"{vals['Illusion'][0].mean():.3f} / {vals['Illusion'][1].mean():.3f}")]), + (.52,.61,.43,.20,"200-step unseen conditions",[("Kármán: 4 points",f"{min(float(r['legacy_dtw']) for r in gen if r['objective']=='karman'):.3f}–{max(float(r['legacy_dtw']) for r in gen if r['objective']=='karman'):.3f}"),("Illusion: 5 points",f"{min(float(r['legacy_dtw']) for r in gen if r['objective']=='illusion'):.3f}–{max(float(r['legacy_dtw']) for r in gen if r['objective']=='illusion'):.3f}"),("Evidence level","one realization / condition"),("Scope","sampled interpolation + extrapolation")])] + for x,y,w,h,title,items in cards: + fig.patches.append(plt.Rectangle((x,y),w,h,transform=fig.transFigure,fc=PALE,ec="#CAD5DF"));fig.text(x+.02,y+h-.045,title,fontsize=17,weight="bold",color=INK) + for j,(k,v) in enumerate(items): fig.text(x+.02,y+h-.09-.032*j,k,fontsize=11,color=MUTED);fig.text(x+w-.02,y+h-.09-.032*j,v,fontsize=11.5,weight="bold",color=BLUE,ha="right") + variants=[("K front term","k_front0"),("K rear lift","k_rear0"),("K rear constant","k_rear1"),("I worst mean deletion","i_front1")] + fig.text(.05,.52,"40-step term deletion: mean parent-relative change",fontsize=18,weight="bold",color=INK) + for i,(label,var) in enumerate(variants): + sub=[r for r in deletion if r["variant"]==var]; delta=np.mean([float(r["delta_vs_parent_40_step"]) for r in sub]);x=.05+i*.225 + fig.patches.append(plt.Rectangle((x,.35),.20,.12,transform=fig.transFigure,fc="white",ec="#CAD5DF"));fig.text(x+.015,.43,label,fontsize=11.5,color=MUTED);fig.text(x+.015,.375,f"{delta:+.3f}",fontsize=22,weight="bold",color=ORANGE if delta<-.03 else BLUE) + fig.text(.05,.235,"Read with care",fontsize=17,weight="bold",color=INK) + cautions=["40-step deletion, 200-step generalization, and 400-step training summaries answer different questions.","Legacy DTW is an aligned trajectory-similarity metric—not causal proof or a physical delay estimate.","Unseen conditions are pointwise deterministic runs; no continuous parameter law or uncertainty band is claimed.","Offline action RMSE is intentionally omitted: imitation on PPO-visited states is not article performance."] + for i,t in enumerate(cautions): fig.text(.07,.195-.038*i,"• "+t,fontsize=11.5,color=MUTED) + save(fig,out,"02_sr_quantitative_evidence") + +def main() -> int: + ap=argparse.ArgumentParser();ap.add_argument("--repo-root",type=Path,default=Path(__file__).resolve().parents[3]);ap.add_argument("--output-dir",type=Path,default=None);a=ap.parse_args();root=a.repo_root.resolve()/"src/SR_analysis/results/runs";out=a.output_dir or root/"article2-plotting-package-20260721/presentation";page_method(root,out);page_evidence(root,out);return 0 +if __name__=="__main__": raise SystemExit(main()) diff --git a/src/SR_analysis/tools/prepare_plotting_data.py b/src/SR_analysis/tools/prepare_plotting_data.py index 64a82b2..53e67ca 100644 --- a/src/SR_analysis/tools/prepare_plotting_data.py +++ b/src/SR_analysis/tools/prepare_plotting_data.py @@ -321,8 +321,9 @@ def build_manifest(repo_root: Path, output_dir: Path, summary: Mapping[str, Any] artifacts = [] for path in sorted( candidate - for pattern in ("*.csv", "*.png", "*.pdf") + for pattern in ("*.csv", "*.png", "*.pdf", "*.md", "*.json", "*.npz") for candidate in output_dir.rglob(pattern) + if candidate.name != "manifest.json" ): artifacts.append({"path": str(path.relative_to(repo_root)), "sha256": hash_file(path)}) atomic_write_json(