PINN-TC / scripts /result.py
zhangrenchao's picture
Upload folder using huggingface_hub
2f3c9e4 verified
Raw
History Blame Contribute Delete
6.97 kB
"""Evaluate dense PINN-TC predictions and create sparse/target/prediction/error panels."""
import importlib.util
import json
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import torch
import yaml
ROOT = Path(__file__).resolve().parents[1]
def load_model_module():
spec = importlib.util.spec_from_file_location("pinn_tc_model", ROOT / "model/pinn-tc.py")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def main():
config = yaml.safe_load((ROOT / "conf/config.yaml").read_text())
module = load_model_module()
archive = np.load(ROOT / config["paths"]["prediction"])
if str(archive["format_version"]) != config["data"]["format_version"]:
raise ValueError("prediction format_version mismatch")
prediction = archive["predictions"]
expected = (251, 251, 44, 3, 4)
if prediction.shape != expected or prediction.dtype != np.float32:
raise ValueError(f"prediction must be float32 with shape {expected}")
y_axis, x_axis, pressures, times = archive["y"], archive["x"], archive["pressure"], archive["time"]
sum_speed_error = sum_pred = sum_true = sum_pred2 = sum_true2 = sum_cross = 0.0
count = 0
radial_edges = np.linspace(0.0, np.sqrt(2.0) * config["data"]["horizontal_extent_m"], 13)
radial_sse = np.zeros(len(radial_edges) - 1); radial_count = np.zeros(len(radial_edges) - 1, dtype=np.int64)
yy, xx = np.meshgrid(y_axis, x_axis, indexing="ij")
radius = np.sqrt(xx * xx + yy * yy)
for pressure_index, pressure in enumerate(pressures):
for time_index, time in enumerate(times):
coordinates = np.column_stack((yy.ravel(), xx.ravel(), np.full(yy.size, time), np.full(yy.size, pressure))).astype(np.float32)
target = module.analytic_vortex_numpy(coordinates).reshape(251, 251, 4)
predicted_speed = np.linalg.norm(prediction[:, :, pressure_index, time_index, :2].astype(np.float64), axis=-1)
true_speed = np.linalg.norm(target[:, :, :2].astype(np.float64), axis=-1)
difference = predicted_speed - true_speed
sum_speed_error += np.square(difference).sum(); count += difference.size
sum_pred += predicted_speed.sum(); sum_true += true_speed.sum()
sum_pred2 += np.square(predicted_speed).sum(); sum_true2 += np.square(true_speed).sum()
sum_cross += (predicted_speed * true_speed).sum()
bins = np.clip(np.digitize(radius.ravel(), radial_edges) - 1, 0, len(radial_sse) - 1)
radial_sse += np.bincount(bins, weights=np.square(difference).ravel(), minlength=len(radial_sse))
radial_count += np.bincount(bins, minlength=len(radial_count))
covariance = sum_cross - sum_pred * sum_true / count
variance_pred = sum_pred2 - sum_pred * sum_pred / count
variance_true = sum_true2 - sum_true * sum_true / count
pearson = covariance / np.sqrt(max(variance_pred * variance_true, 1e-30))
checkpoint = torch.load(ROOT / config["paths"]["checkpoint"], map_location="cpu", weights_only=False)
if checkpoint.get("format_version") != config["data"]["format_version"]:
raise ValueError("checkpoint format_version mismatch")
model = module.PINNTC(**checkpoint["model_config"]); model.load_state_dict(checkpoint["model"]); model.eval()
rng = np.random.default_rng(int(config["seed"]) + 99)
residual_count = int(config["evaluation"]["pde_points"])
residual_coordinates = np.column_stack((rng.uniform(-400_000, 400_000, (residual_count, 2)),
rng.uniform(-21_600, 21_600, residual_count),
rng.uniform(15_000, 90_000, residual_count))).astype(np.float32)
tensor = torch.from_numpy(residual_coordinates).requires_grad_(True)
ru, rv, rc = module.pde_residuals(model, tensor, **config["physics"])
report = {"dense_shape": list(expected), "dense_points": int(np.prod(expected[:-1])),
"wind_speed_rmse_m_s": float(np.sqrt(sum_speed_error / count)), "wind_speed_pearson": float(pearson),
"pde_residual_rms": {"ru_m_s2": float(torch.sqrt(torch.mean(ru.square())).detach()),
"rv_m_s2": float(torch.sqrt(torch.mean(rv.square())).detach()),
"rc_s-1": float(torch.sqrt(torch.mean(rc.square())).detach())},
"radial_rmse": [{"radius_mid_km": float((left + right) / 2000.0), "rmse_m_s": float(np.sqrt(error / max(samples, 1))), "count": int(samples)}
for left, right, error, samples in zip(radial_edges[:-1], radial_edges[1:], radial_sse, radial_count)],
"scope": "Structured analytic engineering validation, not a paper-performance claim."}
values = [report["wind_speed_rmse_m_s"], report["wind_speed_pearson"], *report["pde_residual_rms"].values()]
if not np.isfinite(values).all():
raise FloatingPointError("evaluation produced a non-finite metric")
output = ROOT / config["paths"]["evaluation_dir"]
output.mkdir(parents=True, exist_ok=True)
(output / "metrics.json").write_text(json.dumps(report, indent=2) + "\n")
pressure_index, time_index = int(np.argmin(abs(pressures - 50_000))), int(np.argmin(abs(times)))
coordinates = np.column_stack((yy.ravel(), xx.ravel(), np.full(yy.size, times[time_index]),
np.full(yy.size, pressures[pressure_index]))).astype(np.float32)
target_speed = np.linalg.norm(module.analytic_vortex_numpy(coordinates)[:, :2], axis=1).reshape(251, 251)
predicted_speed = np.linalg.norm(prediction[:, :, pressure_index, time_index, :2], axis=-1)
sparse = np.load(ROOT / config["data"]["root"] / "training_points.npz")["observation_coordinates"]
selected = sparse[(abs(sparse[:, 2] - times[time_index]) < 1) & (abs(sparse[:, 3] - pressures[pressure_index]) < 1000)]
figure, axes_plot = plt.subplots(1, 4, figsize=(16, 4), constrained_layout=True)
axes_plot[0].scatter(selected[:, 1] / 1000, selected[:, 0] / 1000, s=5, c="black"); axes_plot[0].set_title("Sparse observations")
panels = ((target_speed, "Target wind speed", "viridis"), (predicted_speed, "Predicted wind speed", "viridis"),
(predicted_speed - target_speed, "Prediction error", "coolwarm"))
for axis, (field, title, color_map) in zip(axes_plot[1:], panels):
image = axis.imshow(field, origin="lower", extent=(-400, 400, -400, 400), cmap=color_map)
axis.set_title(title); figure.colorbar(image, ax=axis, shrink=0.75)
for axis in axes_plot:
axis.set_xlabel("x (km)"); axis.set_ylabel("y (km)")
figure.savefig(output / "comparison.png", dpi=140); plt.close(figure)
print(f"evaluation={output.relative_to(ROOT)} wind_rmse={report['wind_speed_rmse_m_s']:.6g} pearson={pearson:.6g}")
if __name__ == "__main__":
main()