yushuang88's picture
Upload folder using huggingface_hub
0f0d1f4 verified
Raw History Blame Contribute Delete
9.28 kB
#!/usr/bin/env python3
"""Reload best checkpoint; evaluate exact DNS and DNS-interpolated 200x200 grids."""
from __future__ import annotations
import argparse
import csv
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import numpy as np
import torch
from scipy.interpolate import RectBivariateSpline
from scripts.common import (ROOT, gather, load_checkpoint, load_dns, predict, read_config,
relative_l2, resolve, save_json, setup, tensor)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--config", default=str(ROOT / "config/config.yaml"))
parser.add_argument("--checkpoint")
parser.add_argument("--device")
args = parser.parse_args()
cfg = read_config(args.config)
device, dtype = setup(cfg, args.device)
model, checkpoint = load_checkpoint(cfg, device, args.checkpoint)
dns = load_dns(cfg)
if checkpoint["data_sha256"] != dns["sha256"]:
raise ValueError("Checkpoint was trained on different DNS bytes")
out = resolve(cfg["outputs"]["results"])
sampling = np.load(out / "sampling_indices.npz")
xy, times = dns["xy"], dns["t"]
nx, ny = cfg["evaluation"]["grid_size"]
gx = np.linspace(xy[:, 0].min(), xy[:, 0].max(), nx)
gy = np.linspace(xy[:, 1].min(), xy[:, 1].max(), ny)
xx, yy = np.meshgrid(gx, gy)
grid_xy = np.column_stack((xx.ravel(), yy.ravel()))
native_x, native_y = np.unique(xy[:, 0]), np.unique(xy[:, 1])
ix, iy = np.searchsorted(native_x, xy[:, 0]), np.searchsorted(native_y, xy[:, 1])
dns_grid_coincidence = np.any(np.isclose(gx[:, None], native_x[None, :], atol=1e-10, rtol=0), 1)[None, :] & np.any(np.isclose(gy[:, None], native_y[None, :], atol=1e-10, rtol=0), 1)[:, None]
grid_unseen = ~dns_grid_coincidence.ravel()
native_pred = np.empty((len(times), len(xy), 3), dtype=np.float64)
per_time, grid_sse, grid_denom = [], np.zeros(2), np.zeros(2)
unseen_sse, unseen_denom = np.zeros(2), np.zeros(2)
held_sse, held_denom = np.zeros(2), np.zeros(2)
snapshot = int(np.argmin(abs(times - cfg["evaluation"]["snapshot_time"])))
batch_size = cfg["evaluation"]["batch_size"]
native_heldout_count = 0
for k, t in enumerate(times):
q = np.column_stack((xy, np.full(len(xy), t)))
pred = predict(model, q, device, dtype, batch_size)
native_pred[k] = pred
errors = relative_l2(pred[:, :2], dns["uv"][k])
unused = np.ones(len(xy), dtype=bool)
unused[sampling["boundary"][k]] = False
unused[sampling["residual"][k]] = False
native_heldout_count += int(unused.sum())
held_sse += np.sum((pred[unused, :2] - dns["uv"][k, unused])**2, 0)
held_denom += np.sum(dns["uv"][k, unused]**2, 0)
gq = np.column_stack((grid_xy, np.full(len(grid_xy), t)))
gp = predict(model, gq, device, dtype, batch_size)[:, :2]
reference = np.empty((ny, nx, 2), dtype=np.float64)
for channel in range(2):
field = np.empty((len(native_y), len(native_x)))
field[iy, ix] = dns["uv"][k, :, channel]
reference[:, :, channel] = RectBivariateSpline(native_y, native_x, field, kx=3, ky=3)(gy, gx)
ref = reference.reshape(-1, 2)
ge = relative_l2(gp, ref)
grid_sse += np.sum((gp - ref)**2, 0)
grid_denom += np.sum(ref**2, 0)
unseen_sse += np.sum((gp[grid_unseen] - ref[grid_unseen])**2, 0)
unseen_denom += np.sum(ref[grid_unseen]**2, 0)
row = {"time_index": k, "time": float(t), "dns_u": errors[0], "dns_v": errors[1], "grid_u": ge[0], "grid_v": ge[1]}
per_time.append(row)
if k == snapshot:
np.savez_compressed(out / "snapshot_grid.npz", x=gx, y=gy, reference=reference,
prediction=gp.reshape(ny, nx, 2), time=t,
unseen_mask=grid_unseen.reshape(ny, nx))
if k % 10 == 0 or k == len(times) - 1:
print(f"INFERENCE time_index={k:3d}/199 t={t:.2f} DNS_relL2(u,v)=({errors[0]:.8e},{errors[1]:.8e}) grid_relL2(u,v)=({ge[0]:.8e},{ge[1]:.8e})", flush=True)
np.savez_compressed(out / "dns_predictions.npz", X_star=xy, t=times, uvp_prediction=native_pred,
uv_reference=dns["uv"], p_reference=dns["p"])
with (out / "per_time_metrics.csv").open("w", newline="") as handle:
writer = csv.DictWriter(handle, fieldnames=list(per_time[0]))
writer.writeheader()
writer.writerows(per_time)
primary = relative_l2(native_pred[:, :, :2].reshape(-1, 2), dns["uv"].reshape(-1, 2))
snap_error = np.array([per_time[snapshot]["dns_u"], per_time[snapshot]["dns_v"]])
grid_aggregate = np.sqrt(grid_sse / grid_denom)
grid_snapshot = np.array([per_time[snapshot]["grid_u"], per_time[snapshot]["grid_v"]])
table = cfg["evaluation"]["table3"]
table_values = np.array([table["tsa_u"], table["tsa_v"]])
nearest = int(np.argmin(np.sum((xy - np.array(cfg["evaluation"]["probe"]))**2, 1)))
probe_q = np.column_stack((np.tile(xy[nearest], (len(times), 1)), times))
probe_prediction = predict(model, probe_q, device, dtype, batch_size)
np.savez_compressed(out / "probe_series.npz", xy=xy[nearest], requested_xy=cfg["evaluation"]["probe"],
t=times, reference=dns["uv"][:, nearest], prediction=probe_prediction[:, :2])
diagnostics_q, _ = gather(dns, sampling["validation"])
diagnostic_count = min(len(diagnostics_q), 1000)
diagnostics_q = diagnostics_q[np.linspace(0, len(diagnostics_q) - 1, diagnostic_count, dtype=int)]
momentum_sse, div_sse = np.zeros(2), 0.0
ad_max_abs = {"flow": 0.0, "momentum": 0.0}
# Use the independent reverse-mode implementation for physical diagnostics.
for start in range(0, diagnostic_count, 250):
q = tensor(diagnostics_q[start:start + 250], device, dtype)
flow_values, residual, div = model.residual_autograd(q)
with torch.no_grad():
taylor_values, taylor_residual, _ = model.residual_taylor(q)
torch.testing.assert_close(taylor_values, flow_values, atol=1e-8, rtol=1e-7)
torch.testing.assert_close(taylor_residual, residual, atol=1e-8, rtol=1e-7)
ad_max_abs["flow"] = max(ad_max_abs["flow"], float((taylor_values-flow_values).abs().max().detach()))
ad_max_abs["momentum"] = max(ad_max_abs["momentum"], float((taylor_residual-residual).abs().max().detach()))
momentum_sse += (residual.detach().cpu().numpy()**2).sum(0)
div_sse += float((div.detach()**2).sum())
def pair(array):
return {"u": float(array[0]), "v": float(array[1])}
metrics = {"checkpoint_completed_updates": checkpoint["completed_updates"], "data_path": dns["path"],
"data_sha256": dns["sha256"], "metric": "||prediction-reference||_2 / ||reference||_2 (dimensionless ratio)",
"dns_all_spacetime": pair(primary), "dns_all_spacetime_percent": pair(primary * 100),
"dns_unused_spacetime": pair(np.sqrt(held_sse / held_denom)), "dns_unused_count": native_heldout_count,
"dns_snapshot": {"time": float(times[snapshot]), **pair(snap_error)},
"grid200_all_spacetime": pair(grid_aggregate), "grid200_snapshot": {"time": float(times[snapshot]), **pair(grid_snapshot)},
"grid200_strictly_unseen_spacetime": pair(np.sqrt(unseen_sse / unseen_denom)),
"grid200_unseen_spatial_count": int(grid_unseen.sum()),
"grid_reference": "Tensor-product cubic spline interpolation of this exact DNS; not additional DNS ground truth",
"table3_tsa_pinn": pair(table_values), "table3_pinn": {"u": table["pinn_u"], "v": table["pinn_v"]},
"snapshot_over_table3_ratio": pair(snap_error / table_values),
"grid_snapshot_over_table3_ratio": pair(grid_snapshot / table_values),
"all_spacetime_over_table3_ratio": pair(primary / table_values),
"probe": {"requested": cfg["evaluation"]["probe"], "actual_dns_point": xy[nearest].tolist(),
"relative_l2": pair(relative_l2(probe_prediction[:, :2], dns["uv"][:, nearest]))},
"physics_diagnostics": {"count": diagnostic_count, "sampling": "uniformly spaced across held-out locations over all 200 frames", "momentum_rmse": pair(np.sqrt(momentum_sse / diagnostic_count)),
"divergence_rmse": float(np.sqrt(div_sse / diagnostic_count)),
"trained_checkpoint_ad_equivalence": {"status": "passed", "max_absolute_difference": ad_max_abs}},
"comparison_limits": ["Table3 exact seed/checkpoint/evaluation aggregation not specified in paper", "Paper Eq42 includes x100, but Table3 numerical entries are reported without a percent marker; both ratio and percent provided", "Native DNS covers t=0..19.9 at dt=0.1; no synthetic temporal resampling", "PyTorch port of paper TensorFlow method; coordinate AD checked against reverse-mode PyTorch"]}
save_json(out / "metrics.json", metrics)
print("FINAL METRICS " + json.dumps(metrics, ensure_ascii=False), flush=True)
if __name__ == "__main__":
main()