VPINNs / scripts /inference.py
OneScience's picture
Upload folder using huggingface_hub
4a96652 verified
Raw
History Blame Contribute Delete
6.36 kB
from __future__ import annotations
import sys
from collections.abc import Mapping
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import torch
import yaml
PROJECT_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(PROJECT_ROOT))
from model.vpinn import VPINN, infer_layers, unpack_checkpoint # noqa: E402
DEFAULT_CONFIG = PROJECT_ROOT / "conf" / "config.yaml"
CASES = ("1d", "2d")
def load_config(path: Path) -> dict:
with path.open("r", encoding="utf-8") as stream:
config = yaml.safe_load(stream)
if not isinstance(config, dict) or "root" not in config:
raise ValueError(f"config must contain a 'root' mapping: {path}")
return config["root"]
def project_path(value: str | Path) -> Path:
path = Path(value).expanduser()
return path if path.is_absolute() else PROJECT_ROOT / path
def resolve_device(requested: str) -> torch.device:
if requested == "auto":
return torch.device("cuda" if torch.cuda.is_available() else "cpu")
device = torch.device(requested)
if device.type == "cuda" and not torch.cuda.is_available():
raise RuntimeError("CUDA/DCU was requested but torch.cuda.is_available() is false")
return device
def resolve_dtype(name: str) -> torch.dtype:
try:
return {"float32": torch.float32, "float64": torch.float64}[name]
except KeyError as error:
raise ValueError(f"unsupported dtype: {name}") from error
def load_model(
checkpoint_path: Path, device: torch.device, dtype: torch.dtype
) -> tuple[VPINN, dict]:
if not checkpoint_path.is_file():
raise FileNotFoundError(
f"checkpoint not found: {checkpoint_path}. Run scripts/train.py first."
)
checkpoint = torch.load(checkpoint_path, map_location="cpu", weights_only=True)
if not isinstance(checkpoint, Mapping):
raise ValueError(f"invalid VPINN checkpoint: {checkpoint_path}")
state, metadata = unpack_checkpoint(checkpoint)
layers = metadata.get("layers") or infer_layers(state)
model = VPINN(layers, dtype=dtype).to(device=device, dtype=dtype)
model.load_state_dict(state, strict=True)
model.eval()
return model, metadata
def exact_poisson1d(x: np.ndarray) -> np.ndarray:
return 0.1 * np.sin(8.0 * np.pi * x) + np.tanh(80.0 * x)
def infer_poisson1d(
weight_dir: Path,
result_dir: Path,
device: torch.device,
dtype: torch.dtype,
) -> float:
checkpoint_path = weight_dir / "hpvpinn_poisson1d.pt"
model, _ = load_model(checkpoint_path, device, dtype)
points = torch.linspace(-1.0, 1.0, 2001, dtype=dtype, device=device).unsqueeze(-1)
exact = exact_poisson1d(points.cpu().numpy().reshape(-1))
with torch.no_grad():
prediction = model(points).cpu().numpy().reshape(-1)
absolute_error = np.abs(prediction - exact)
relative_l2 = float(np.linalg.norm(prediction - exact) / np.linalg.norm(exact))
figure, axes = plt.subplots(1, 2, figsize=(12, 4))
coordinates = points.cpu().numpy().reshape(-1)
axes[0].plot(coordinates, exact, "k-", linewidth=1.5, label="Exact")
axes[0].plot(coordinates, prediction, "r--", linewidth=1.0, label="hp-VPINN")
axes[0].set_xlabel("x")
axes[0].set_ylabel("u")
axes[0].legend()
axes[1].semilogy(coordinates, np.maximum(absolute_error, 1.0e-16), "r-")
axes[1].set_xlabel("x")
axes[1].set_ylabel("Absolute error")
figure.tight_layout()
output_path = result_dir / "hpvpinn_poisson1d.png"
figure.savefig(output_path, dpi=150)
plt.close(figure)
print(f"[1D] relative L2={relative_l2:.6e}, plot={output_path}")
return relative_l2
def exact_poisson2d(x: np.ndarray, y: np.ndarray) -> np.ndarray:
return (0.1 * np.sin(2.0 * np.pi * x) + np.tanh(10.0 * x)) * np.sin(
2.0 * np.pi * y
)
def infer_poisson2d(
weight_dir: Path,
result_dir: Path,
device: torch.device,
dtype: torch.dtype,
) -> float:
checkpoint_path = weight_dir / "hpvpinn_poisson2d.pt"
model, _ = load_model(checkpoint_path, device, dtype)
axis = np.linspace(-1.0, 1.0, 100)
mesh_x, mesh_y = np.meshgrid(axis, axis, indexing="ij")
points = torch.as_tensor(
np.column_stack((mesh_x.ravel(), mesh_y.ravel())), dtype=dtype, device=device
)
with torch.no_grad():
prediction = model(points).cpu().numpy().reshape(mesh_x.shape)
exact = exact_poisson2d(mesh_x, mesh_y)
absolute_error = np.abs(prediction - exact)
relative_l2 = float(
np.linalg.norm(prediction.ravel() - exact.ravel()) / np.linalg.norm(exact.ravel())
)
figure, axes = plt.subplots(1, 3, figsize=(15, 4))
for plot_axis, title, field in zip(
axes,
("Exact", "hp-VPINN", "Absolute error"),
(exact, prediction, absolute_error),
strict=True,
):
image = plot_axis.imshow(
field.T,
extent=(-1, 1, -1, 1),
origin="lower",
aspect="auto",
cmap="jet",
)
plot_axis.set_title(title)
plot_axis.set_xlabel("x")
plot_axis.set_ylabel("y")
figure.colorbar(image, ax=plot_axis)
figure.tight_layout()
output_path = result_dir / "hpvpinn_poisson2d.png"
figure.savefig(output_path, dpi=150)
plt.close(figure)
print(f"[2D] relative L2={relative_l2:.6e}, plot={output_path}")
return relative_l2
def main() -> None:
config_path = DEFAULT_CONFIG.resolve()
config = load_config(config_path)
common = config["common"]
device = resolve_device(str(common["device"]))
dtype = resolve_dtype(str(common["dtype"]))
weight_dir = project_path(common["weight_dir"])
result_dir = project_path(common["result_dir"])
result_dir.mkdir(parents=True, exist_ok=True)
print(f"Config: {config_path}")
print(f"Device: {device}")
selected_case = str(common["case"]).lower()
if selected_case not in (*CASES, "all"):
raise ValueError("common.case must be one of: 1d, 2d, all")
selected_cases = CASES if selected_case == "all" else (selected_case,)
if "1d" in selected_cases:
infer_poisson1d(weight_dir, result_dir, device, dtype)
if "2d" in selected_cases:
infer_poisson2d(weight_dir, result_dir, device, dtype)
if __name__ == "__main__":
main()