Download scripts/inference.py from OneScience-Group/FAST-DeepONet-DarcyFlow: direct link, hf CLI and curl.
- Browser
- Download file 7.38 kB
-
https://huggingface.co/OneScience-Group/FAST-DeepONet-DarcyFlow/resolve/main/scripts/inference.py
- Command line
-
hf download hf://OneScience-Group/FAST-DeepONet-DarcyFlow/scripts/inference.py
-
curl -L -o inference.py https://huggingface.co/OneScience-Group/FAST-DeepONet-DarcyFlow/resolve/main/scripts/inference.py
7.38 kB
| #!/usr/bin/env python3 | |
| """Evaluate the best FAST-DeepONet checkpoint on all ten test fields.""" | |
| from __future__ import annotations | |
| import argparse | |
| import csv | |
| import json | |
| import math | |
| import os | |
| import sys | |
| from pathlib import Path | |
| from typing import Any | |
| import numpy as np | |
| import torch | |
| PROJECT_ROOT = Path(__file__).resolve().parents[1] | |
| if str(PROJECT_ROOT) not in sys.path: | |
| sys.path.insert(0, str(PROJECT_ROOT)) | |
| from model.FASTDeepONet import FASTDeepONet | |
| from scripts.common import ( | |
| atomic_write_json, | |
| load_config, | |
| load_split, | |
| make_coordinate_features, | |
| relative_l2, | |
| select_device, | |
| ) | |
| def load_checkpoint(path: Path, device: torch.device) -> dict[str, Any]: | |
| try: | |
| checkpoint = torch.load(path, map_location=device, weights_only=False) | |
| except TypeError: | |
| checkpoint = torch.load(path, map_location=device) | |
| if not isinstance(checkpoint, dict) or "model_state_dict" not in checkpoint: | |
| raise ValueError(f"Invalid checkpoint payload: {path}") | |
| return checkpoint | |
| def build_from_checkpoint(checkpoint: dict[str, Any], device: torch.device) -> FASTDeepONet: | |
| arguments = dict(checkpoint["model_arguments"]) | |
| recorded_effective_rank = int(arguments.pop("effective_factor_rank")) | |
| basis = checkpoint["spectral_basis"] | |
| model = FASTDeepONet(spectral_basis=basis, **arguments).to(device) | |
| if model.effective_factor_rank != recorded_effective_rank: | |
| raise ValueError("Checkpoint effective rank is inconsistent with its spectral basis") | |
| model.load_state_dict(checkpoint["model_state_dict"], strict=True) | |
| model.eval() | |
| return model | |
| def atomic_save_predictions(path: Path, **arrays: np.ndarray) -> None: | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| temporary = path.with_suffix(path.suffix + ".tmp") | |
| with temporary.open("wb") as handle: | |
| np.savez_compressed(handle, **arrays) | |
| os.replace(temporary, path) | |
| def write_metrics_csv(path: Path, filenames: list[str], metrics: list[float], mean_metric: float) -> None: | |
| temporary = path.with_suffix(path.suffix + ".tmp") | |
| with temporary.open("w", newline="", encoding="utf-8") as handle: | |
| writer = csv.writer(handle) | |
| writer.writerow(["sample_index", "filename", "relative_l2"]) | |
| for index, (filename, metric) in enumerate(zip(filenames, metrics)): | |
| writer.writerow([index, filename, f"{metric:.12e}"]) | |
| writer.writerow(["mean", "", f"{mean_metric:.12e}"]) | |
| os.replace(temporary, path) | |
| def run_inference(config: dict[str, Any], force_cpu: bool) -> None: | |
| paths = config["paths"] | |
| data_config = config["data"] | |
| model_config = config["model"] | |
| training = config["training"] | |
| device = torch.device("cpu") if force_cpu else select_device(require_accelerator=False) | |
| checkpoint_path = Path(paths["checkpoint"]) | |
| if not checkpoint_path.is_file(): | |
| raise FileNotFoundError(f"Checkpoint not found: {checkpoint_path}") | |
| checkpoint = load_checkpoint(checkpoint_path, device) | |
| model = build_from_checkpoint(checkpoint, device) | |
| filenames, inputs_np, targets_np, masks_np = load_split( | |
| paths["data_root"], | |
| "test", | |
| data_config["split_counts"]["test"], | |
| data_config["points_per_field"], | |
| ) | |
| coordinates_np = make_coordinate_features( | |
| data_config["grid_size"], model_config["fourier_order"] | |
| ) | |
| coordinates = torch.from_numpy(coordinates_np).to(device) | |
| target_mean = np.asarray(checkpoint["target_mean"].cpu(), dtype=np.float32) | |
| target_std = float(checkpoint["target_std"]) | |
| predictions: list[np.ndarray] = [] | |
| metrics: list[float] = [] | |
| batch_size = int(training["batch_size"]) | |
| print( | |
| f"inference_start checkpoint={checkpoint_path} best_epoch={checkpoint['best_epoch']} " | |
| f"best_tune_loss={float(checkpoint['best_tune_mse']):.8e} device={device}", | |
| flush=True, | |
| ) | |
| with torch.inference_mode(): | |
| for sample_index, (filename, input_field, target_field) in enumerate( | |
| zip(filenames, inputs_np, targets_np) | |
| ): | |
| branch = torch.from_numpy(input_field[None, :]).to(device) | |
| branch_latent = model.encode_branch(branch) | |
| normalized_chunks: list[np.ndarray] = [] | |
| for start in range(0, coordinates.shape[0], batch_size): | |
| coordinate_batch = coordinates[start : start + batch_size] | |
| expanded_branch = branch_latent.expand(coordinate_batch.shape[0], -1) | |
| normalized = model.predict_from_branch_encoding(expanded_branch, coordinate_batch) | |
| normalized_chunks.append(normalized.cpu().numpy().astype(np.float32, copy=False)) | |
| normalized_prediction = np.concatenate(normalized_chunks) | |
| prediction = normalized_prediction * target_std + target_mean | |
| metric = relative_l2(target_field, prediction) | |
| if not math.isfinite(metric): | |
| raise FloatingPointError(f"Non-finite relative L2 for {filename}") | |
| predictions.append(prediction.astype(np.float32, copy=False)) | |
| metrics.append(metric) | |
| print( | |
| f"test_sample={sample_index:02d} file={filename} relative_l2={metric:.10e}", | |
| flush=True, | |
| ) | |
| mean_metric = float(np.mean(metrics)) | |
| print(f"test_mean_relative_l2={mean_metric:.10e} samples={len(metrics)}", flush=True) | |
| results_dir = Path(paths["results_dir"]) | |
| results_dir.mkdir(parents=True, exist_ok=True) | |
| metrics_payload = { | |
| "metric": "relative_l2_per_field", | |
| "equation": "||y-y_hat||_2 / (||y||_2 + 1e-12)", | |
| "checkpoint": str(checkpoint_path), | |
| "best_epoch": int(checkpoint["best_epoch"]), | |
| "best_tune_mse": float(checkpoint["best_tune_mse"]), | |
| "sample_count": len(metrics), | |
| "samples": [ | |
| {"sample_index": index, "filename": filename, "relative_l2": metric} | |
| for index, (filename, metric) in enumerate(zip(filenames, metrics)) | |
| ], | |
| "mean_relative_l2": mean_metric, | |
| "paper_comparison_note": ( | |
| "This fixed run uses 10 test fields and one model seed; the paper's Table 2 " | |
| "uses 100 independent test fields and summarizes five fits." | |
| ), | |
| } | |
| atomic_write_json(results_dir / "test_metrics.json", metrics_payload) | |
| write_metrics_csv(results_dir / "test_metrics.csv", filenames, metrics, mean_metric) | |
| atomic_save_predictions( | |
| results_dir / "test_predictions.npz", | |
| filenames=np.asarray(filenames), | |
| inputs=inputs_np.astype(np.float32, copy=False), | |
| masks=masks_np.astype(np.float32, copy=False), | |
| targets=targets_np.astype(np.float32, copy=False), | |
| predictions=np.stack(predictions).astype(np.float32, copy=False), | |
| relative_l2=np.asarray(metrics, dtype=np.float64), | |
| ) | |
| print(f"inference_complete results_dir={results_dir}", flush=True) | |
| def parse_args() -> argparse.Namespace: | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument("--config", type=Path, default=PROJECT_ROOT / "config" / "config.yaml") | |
| parser.add_argument("--cpu", action="store_true", help="Force CPU inference") | |
| return parser.parse_args() | |
| def main() -> None: | |
| args = parse_args() | |
| run_inference(load_config(args.config), force_cpu=args.cpu) | |
| if __name__ == "__main__": | |
| main() | |