# OID_analysis/li22b/phase_c_synthesis.py
"""Phase C: Three-framework synthesis — SR, Li22b, OID.

C.1: Unified POD (Li22b DB + PPO DB global POD)
C.2: Cross-framework comparison table + interpretation

Usage:
  PYTHONPATH="src:$PYTHONPATH" python3 src/OID_analysis/li22b/phase_c_synthesis.py
"""
import os, sys, json, glob, numpy as np

_REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
if _REPO not in sys.path:
    sys.path.insert(0, _REPO)

DATA_BASE = os.path.join(os.path.dirname(__file__), "..", "data_li22b")
DERIVED = os.path.join(DATA_BASE, "derived")
OID_DERIVED = os.path.join(os.path.dirname(__file__), "..", "data", "derived")


def load_li22b_lse():
    fp = os.path.join(DERIVED, "lse", "lse_results.json")
    if not os.path.isfile(fp): return None
    return json.load(open(fp))


def load_oid_results():
    """Load existing OID results from old PPO data."""
    fp = os.path.join(OID_DERIVED, "master", "master_table.json")
    if not os.path.isfile(fp): return None
    return json.load(open(fp))


def load_li22b_oid():
    fp = os.path.join(DERIVED, "oid_li22b", "crossmap.npz")
    if not os.path.isfile(fp): return None
    return np.load(fp)["overlap"]


def load_li22b_pod_energy():
    fp = os.path.join(DERIVED, "pod", "energy.json")
    if not os.path.isfile(fp): return None
    return json.load(open(fp))


def main():
    lse = load_li22b_lse()
    oid_res = load_oid_results()
    crossmap = load_li22b_oid()
    pod_en = load_li22b_pod_energy()

    print("=" * 70)
    print("Three-Framework Synthesis: SR + Li22b + OID")
    print("=" * 70)

    print("\n--- 1. Li22b LSE ---")
    if lse:
        print(f"  Overall error E: {lse['E']:.4f}")
        print(f"  Mean per-command eps_a: {lse['mean_eps_a']:.4f} +/- {lse['std_eps_a']:.4f}")
    else:
        print("  (not yet computed — run phase_a3_lse.py)")

    print("\n--- 2. Li22b POD Energy ---")
    if pod_en:
        print(f"  Commands: {pod_en['n_commands']}, Snapshots: {pod_en['n_snapshots']}")
        print(f"  E2={pod_en['e2']*100:.1f}%, E10={pod_en['e10']*100:.1f}%")
        print(f"  99% truncation: {pod_en['r99']} modes")
        print(f"  Li22b reference: E2=44.9%, E10=78.9%, r99=78")

    print("\n--- 3. Li22b OID ---")
    if crossmap is not None:
        r = crossmap.shape[0]
        # Top cross-mappings
        print(f"  Mode cross-mapping ({r}x{r}):")
        for i in range(min(5, r)):
            top_j = int(np.argmax(crossmap[i]))
            print(f"    Li22b mode {i} → OID mode {top_j} (overlap={crossmap[i,top_j]:.3f})")

    print("\n--- 4. PPO OID Results (from previous work) ---")
    if oid_res:
        print(f"  (loaded from {OID_DERIVED}/master/master_table.json)")
    else:
        print("  (not found)")

    print("\n--- 5. Synthesis ---")
    interpretations = []

    # Is Li22b POD energy distribution consistent with PPO POD?
    if pod_en and pod_en['e10'] > 0.7:
        interpretations.append(
            "Li22b POD confirms low-dimensionality of pinball flows (E10~{:.0f}%). "
            "Supports OID's default r=10 POD truncation.".format(pod_en['e10']*100))

    # Does LSE error explain why SR works?
    if lse and lse['E'] < 0.5:
        interpretations.append(
            "LSE achieves moderate estimation accuracy (E={:.3f}), suggesting "
            "a linear component EXISTS in [s,b]→field mapping. This explains why "
            "SR can find clean formulas: SR's obs→act chain operates on the same "
            "sensor channels, and act is lower-dimensional than full field.".format(lse['E']))

    # Cross-mapping interpretation
    if crossmap is not None:
        # Check if any Li22b mode strongly overlaps with OID modes
        max_overlap = float(np.max(crossmap))
        interpretations.append(
            "Max Li22b-OID mode overlap = {:.3f}. ".format(max_overlap) +
            ("The two POD bases share significant structure — steady-control "
             "and dynamic-control correction fields lie in overlapping subspaces."
             if max_overlap > 0.7 else
             "Steady and dynamic control engage notably different POD structures, "
             "confirming that control temporal dynamics fundamentally change the "
             "correction space."))

    # Key difference: Li22b uses steady open-loop, we use dynamic PPO
    interpretations.append(
        "CRITICAL DISTINCTION: Li22b estimates FULL fields from [s,b] under STEADY "
        "controls. Our OID diagnoses CORRECTION structures in Δq_ctl under DYNAMIC "
        "PPO. The bridge between them quantifies how much of the dynamic control "
        "maneuver can be captured by the steady-control POD basis.")

    for it in interpretations:
        print(f"  • {it}")

    # Save synthesis
    out = {
        "li22b_lse_E": lse['E'] if lse else None,
        "li22b_pod_e10": pod_en['e10'] if pod_en else None,
        "li22b_pod_r99": pod_en['r99'] if pod_en else None,
        "max_crossmap_overlap": float(np.max(crossmap)) if crossmap is not None else None,
        "interpretations": interpretations,
    }
    with open(os.path.join(DERIVED, "synthesis.json"), "w") as f:
        json.dump(out, f, indent=2)
    print(f"\nSaved synthesis to {DERIVED}/synthesis.json")


if __name__ == "__main__":
    main()
