Download scripts/train.py from OneScience-Group/XPINNs: direct link, hf CLI and curl.
- Browser
- Download file 7.2 kB
-
https://huggingface.co/OneScience-Group/XPINNs/resolve/main/scripts/train.py
- Command line
-
hf download hf://OneScience-Group/XPINNs/scripts/train.py
-
curl -L -o train.py https://huggingface.co/OneScience-Group/XPINNs/resolve/main/scripts/train.py
7.2 kB
| from __future__ import annotations | |
| import random | |
| import sys | |
| import time | |
| from pathlib import Path | |
| import numpy as np | |
| import torch | |
| import yaml | |
| PROJECT_ROOT = Path(__file__).resolve().parents[1] | |
| sys.path.insert(0, str(PROJECT_ROOT)) | |
| sys.path.insert(0, str(Path(__file__).resolve().parent)) | |
| from data_utils import ( # noqa: E402 | |
| build_evaluation_points, | |
| build_training_batch, | |
| exact_subdomain_values, | |
| load_mat_data, | |
| ) | |
| from model.xpinn import XPINNPoisson2D # noqa: E402 | |
| DEFAULT_CONFIG = PROJECT_ROOT / "conf" / "config.yaml" | |
| 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 seed_everything(seed: int) -> None: | |
| random.seed(seed) | |
| np.random.seed(seed) | |
| torch.manual_seed(seed) | |
| if torch.cuda.is_available(): | |
| torch.cuda.manual_seed_all(seed) | |
| def compute_loss( | |
| outputs: dict[str, torch.Tensor], | |
| batch: dict[str, torch.Tensor], | |
| weights: dict, | |
| ) -> tuple[torch.Tensor, dict[str, float]]: | |
| boundary_loss = torch.mean( | |
| (batch["ub"] - outputs["boundary_prediction"]).square() | |
| ) | |
| pde_loss = sum( | |
| torch.mean(outputs[f"residual{domain}"].square()) for domain in (1, 2, 3) | |
| ) | |
| interface_residual_loss = torch.mean( | |
| outputs["interface1_residual"].square() | |
| ) + torch.mean(outputs["interface2_residual"].square()) | |
| interface_value_loss = ( | |
| torch.mean( | |
| (outputs["interface1_domain1"] - outputs["interface1_average"]).square() | |
| ) | |
| + torch.mean( | |
| (outputs["interface1_domain2"] - outputs["interface1_average"]).square() | |
| ) | |
| + torch.mean( | |
| (outputs["interface2_domain1"] - outputs["interface2_average"]).square() | |
| ) | |
| + torch.mean( | |
| (outputs["interface2_domain3"] - outputs["interface2_average"]).square() | |
| ) | |
| ) | |
| total = ( | |
| float(weights["boundary"]) * boundary_loss | |
| + float(weights["pde"]) * pde_loss | |
| + float(weights["interface_residual"]) * interface_residual_loss | |
| + float(weights["interface_value"]) * interface_value_loss | |
| ) | |
| return total, { | |
| "boundary": boundary_loss.item(), | |
| "pde": pde_loss.item(), | |
| "interface_residual": interface_residual_loss.item(), | |
| "interface_value": interface_value_loss.item(), | |
| } | |
| def clear_coordinate_grads(batch: dict[str, torch.Tensor]) -> None: | |
| for value in batch.values(): | |
| if value.requires_grad and value.grad is not None: | |
| value.grad = None | |
| def evaluate( | |
| model: XPINNPoisson2D, | |
| points: dict[str, torch.Tensor], | |
| references: tuple[torch.Tensor, torch.Tensor, torch.Tensor], | |
| ) -> tuple[float, float, float, float]: | |
| with torch.no_grad(): | |
| predictions = model.predict(points["xy1"], points["xy2"], points["xy3"]) | |
| errors = tuple( | |
| ( | |
| torch.linalg.vector_norm(prediction - reference) | |
| / torch.linalg.vector_norm(reference) | |
| ).item() | |
| for prediction, reference in zip(predictions, references, strict=True) | |
| ) | |
| combined_prediction = torch.cat(predictions) | |
| combined_reference = torch.cat(references) | |
| combined_error = ( | |
| torch.linalg.vector_norm(combined_prediction - combined_reference) | |
| / torch.linalg.vector_norm(combined_reference) | |
| ).item() | |
| return errors[0], errors[1], errors[2], combined_error | |
| 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"])) | |
| seed = int(common["seed"]) | |
| data_path = project_path(config["data"]["mat_file"]) | |
| weight_dir = project_path(common["weight_dir"]) | |
| checkpoint_path = weight_dir / config["training"]["checkpoint_name"] | |
| steps = int(config["training"]["steps"]) | |
| if steps <= 0: | |
| raise ValueError("training steps must be positive") | |
| counts = {key: int(value) for key, value in config["data"]["samples"].items()} | |
| seed_everything(seed) | |
| print(f"Config: {config_path}") | |
| print(f"Data: {data_path}") | |
| print(f"Device: {device}") | |
| print(f"Samples: {counts}") | |
| data = load_mat_data(data_path) | |
| batch = build_training_batch(data, counts, seed, device, dtype) | |
| evaluation_points = build_evaluation_points(data, device, dtype) | |
| references = exact_subdomain_values(data, device, dtype) | |
| model = XPINNPoisson2D(config["model"], dtype=dtype).to( | |
| device=device, dtype=dtype | |
| ) | |
| optimizer = torch.optim.Adam( | |
| model.parameters(), lr=float(config["training"]["lr"]) | |
| ) | |
| log_interval = int(config["training"]["log_interval"]) | |
| started = time.time() | |
| for step in range(1, steps + 1): | |
| clear_coordinate_grads(batch) | |
| outputs = model.training_outputs(batch) | |
| loss, parts = compute_loss(outputs, batch, config["loss"]) | |
| if not torch.isfinite(loss): | |
| raise FloatingPointError(f"XPINN loss became non-finite at step {step}") | |
| optimizer.zero_grad(set_to_none=True) | |
| loss.backward() | |
| optimizer.step() | |
| if step == 1 or step % log_interval == 0 or step == steps: | |
| error1, error2, error3, combined_error = evaluate( | |
| model, evaluation_points, references | |
| ) | |
| print( | |
| f"step={step:4d} loss={loss.item():.3e} " | |
| f"boundary={parts['boundary']:.3e} pde={parts['pde']:.3e} " | |
| f"interface_r={parts['interface_residual']:.3e} " | |
| f"interface_u={parts['interface_value']:.3e} " | |
| f"l2=({error1:.3e}, {error2:.3e}, {error3:.3e}) " | |
| f"combined={combined_error:.3e}" | |
| ) | |
| weight_dir.mkdir(parents=True, exist_ok=True) | |
| checkpoint = { | |
| "case": "poisson2d", | |
| "architecture": "xpinn_poisson2d", | |
| "model_state": model.state_dict(), | |
| "model_config": config["model"], | |
| "sample_counts": counts, | |
| "step": steps, | |
| } | |
| torch.save(checkpoint, checkpoint_path) | |
| print(f"Training finished in {time.time() - started:.1f}s") | |
| print(f"Saved checkpoint: {checkpoint_path}") | |
| if __name__ == "__main__": | |
| main() | |