第二轮:整理两个工作目录
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
"""CCD analysis pipeline: POD + force/action/signature CCD.
|
||||
|
||||
Usage:
|
||||
python ccd/run_ccd.py
|
||||
|
||||
Requires resampled data from scripts/resample.py.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
_ANALYSIS = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
if _ANALYSIS not in sys.path:
|
||||
sys.path.insert(0, _ANALYSIS)
|
||||
|
||||
from CCD_analysis.configs import DATA_DIR
|
||||
from CCD_analysis.utils.resampling import (
|
||||
compute_pod, cumulative_energy, e95_index,
|
||||
compute_reduced_ccd, stack_velocity_fields,
|
||||
)
|
||||
|
||||
R_CANDIDATES = [6, 8, 10]
|
||||
CCD_Q = 12
|
||||
|
||||
|
||||
def load_resampled(name: str):
|
||||
p = os.path.join(DATA_DIR, "resampled", name, "resampled.npz")
|
||||
if not os.path.isfile(p):
|
||||
return None
|
||||
return np.load(p)
|
||||
|
||||
|
||||
def main():
|
||||
print("=== CCD Pipeline ===\n")
|
||||
|
||||
# Identify which cases have resampled data
|
||||
resampled_dir = os.path.join(DATA_DIR, "resampled")
|
||||
if not os.path.isdir(resampled_dir):
|
||||
print("ERROR: run scripts/resample.py first")
|
||||
return 1
|
||||
|
||||
cases = sorted(os.listdir(resampled_dir))
|
||||
print(f"Resampled cases: {cases}")
|
||||
|
||||
# --- POD ---
|
||||
print("\n--- POD ---")
|
||||
snapshots = []
|
||||
case_ranges = {}
|
||||
idx = 0
|
||||
|
||||
for name in cases:
|
||||
d = load_resampled(name)
|
||||
if d is None:
|
||||
continue
|
||||
ux, uy = d.get("ux"), d.get("uy")
|
||||
if ux is None:
|
||||
print(f" {name}: no field data, skip POD")
|
||||
continue
|
||||
n_cyc, n_pt = ux.shape[0], ux.shape[1]
|
||||
for c in range(n_cyc):
|
||||
for p in range(n_pt):
|
||||
q = np.concatenate([ux[c, p].ravel(), uy[c, p].ravel()])
|
||||
snapshots.append(q)
|
||||
case_ranges[name] = (idx, idx + n_cyc * n_pt)
|
||||
idx += n_cyc * n_pt
|
||||
print(f" {name}: {n_cyc}x{n_pt} snapshots")
|
||||
|
||||
if not snapshots:
|
||||
print("No field data for POD")
|
||||
return 1
|
||||
|
||||
Q = np.column_stack(snapshots)
|
||||
mean_field, modes, s, coeffs = compute_pod(Q)
|
||||
energy = cumulative_energy(s)
|
||||
e95 = e95_index(energy)
|
||||
print(f" POD: {len(s)} modes, E95={e95}")
|
||||
for i in range(min(6, len(s))):
|
||||
print(f" mode {i+1}: energy={energy[i]:.4f}")
|
||||
|
||||
# --- CCD for each case ---
|
||||
print("\n--- CCD ---")
|
||||
all_results = {}
|
||||
W_dict = {}
|
||||
|
||||
for r in R_CANDIDATES:
|
||||
print(f"\n POD truncation r={r}")
|
||||
for name in cases:
|
||||
d = load_resampled(name)
|
||||
if d is None:
|
||||
continue
|
||||
|
||||
# POD coefficients for this case
|
||||
if name in case_ranges:
|
||||
start, end = case_ranges[name]
|
||||
a_r = coeffs[:r, start:end]
|
||||
else:
|
||||
# Projection case (not in POD basis)
|
||||
ux, uy = d.get("ux"), d.get("uy")
|
||||
if ux is None:
|
||||
continue
|
||||
proj_snapshots = []
|
||||
for c in range(ux.shape[0]):
|
||||
for p in range(ux.shape[1]):
|
||||
q = np.concatenate([ux[c, p].ravel(), uy[c, p].ravel()])
|
||||
proj_snapshots.append(q)
|
||||
Q_proj = np.column_stack(proj_snapshots)
|
||||
Q_centered = Q_proj - mean_field[:, None]
|
||||
a_r = (modes[:, :r].T @ Q_centered)
|
||||
|
||||
N = a_r.shape[1]
|
||||
if N < 24:
|
||||
print(f" {name}: too few samples ({N})")
|
||||
continue
|
||||
|
||||
# Force CCD
|
||||
forces = d.get("forces")
|
||||
if forces is not None:
|
||||
f = forces.reshape(-1, forces.shape[-1])
|
||||
Fx = f[:, 0] + f[:, 2] + f[:, 4]
|
||||
Fy = f[:, 1] + f[:, 3] + f[:, 5]
|
||||
y_force = np.vstack([Fx, Fy])
|
||||
|
||||
if y_force.shape[1] >= N:
|
||||
y_f = y_force[:, :N]
|
||||
else:
|
||||
y_f = y_force
|
||||
|
||||
W, sigma, z = compute_reduced_ccd(a_r[:, :y_f.shape[1]], y_f, Q_delay=CCD_Q)
|
||||
ccd_ene = cumulative_energy(sigma)
|
||||
m80 = int(np.searchsorted(ccd_ene, 0.80) + 1) if len(ccd_ene) > 0 else 0
|
||||
key = f"{name}_force_r{r}"
|
||||
W_dict[key] = W
|
||||
all_results[key] = {"case": name, "observable": "force", "r": r,
|
||||
"m80": m80, "sigma_top3": [float(sigma[i]) for i in range(min(3, len(sigma)))]}
|
||||
print(f" {key}: m80={m80}")
|
||||
|
||||
# Action CCD (for controlled cases)
|
||||
actions = d.get("actions")
|
||||
if actions is not None:
|
||||
y_act = actions.reshape(-1, actions.shape[-1]).T
|
||||
if y_act.shape[1] >= N:
|
||||
y_a = y_act[:, :N]
|
||||
else:
|
||||
y_a = y_act
|
||||
W, sigma, z = compute_reduced_ccd(a_r[:, :y_a.shape[1]], y_a, Q_delay=CCD_Q)
|
||||
ccd_ene = cumulative_energy(sigma)
|
||||
m80 = int(np.searchsorted(ccd_ene, 0.80) + 1) if len(ccd_ene) > 0 else 0
|
||||
key = f"{name}_action_r{r}"
|
||||
W_dict[key] = W
|
||||
all_results[key] = {"case": name, "observable": "action", "r": r,
|
||||
"m80": m80, "sigma_top3": [float(sigma[i]) for i in range(min(3, len(sigma)))]}
|
||||
print(f" {key}: m80={m80}")
|
||||
|
||||
# --- Modal overlap ---
|
||||
print("\n--- Modal Overlap ---")
|
||||
force_keys = [k for k in W_dict if "force" in k]
|
||||
for i, ka in enumerate(force_keys):
|
||||
for kb in force_keys[i+1:]:
|
||||
Wa, Wb = W_dict[ka], W_dict[kb]
|
||||
n = min(Wa.shape[1], Wb.shape[1], 5)
|
||||
ov = []
|
||||
for k in range(n):
|
||||
ak = Wa[:, k] / (np.linalg.norm(Wa[:, k]) + 1e-12)
|
||||
bk = Wb[:, k] / (np.linalg.norm(Wb[:, k]) + 1e-12)
|
||||
ov.append(float(abs(ak @ bk)))
|
||||
print(f" O({ka}, {kb}): O1={ov[0]:.4f}, O2={ov[1]:.4f}")
|
||||
|
||||
# Save
|
||||
out_dir = os.path.join(DATA_DIR, "ccd")
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
with open(os.path.join(out_dir, "ccd_results.json"), "w") as f:
|
||||
json.dump(all_results, f, indent=2)
|
||||
print(f"\nSaved to {out_dir}/ccd_results.json")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user