"""Run chunked inference on the full 251 x 251 x 44 x 3 paper-resolution domain.""" import importlib.util from pathlib import Path 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 coordinates_for_indices(indices, y_axis, x_axis, times, pressures): x_count, p_count, t_count = len(x_axis), len(pressures), len(times) y_index = indices // (x_count * p_count * t_count) remainder = indices % (x_count * p_count * t_count) x_index = remainder // (p_count * t_count) remainder %= p_count * t_count p_index = remainder // t_count t_index = remainder % t_count return np.column_stack((y_axis[y_index], x_axis[x_index], times[t_index], pressures[p_index])).astype(np.float32) def main(): config = yaml.safe_load((ROOT / "conf/config.yaml").read_text()) module = load_model_module() requested = config["runtime"]["device"] device = torch.device("cuda" if requested == "auto" and torch.cuda.is_available() else ("cpu" if requested == "auto" else requested)) checkpoint = torch.load(ROOT / config["paths"]["checkpoint"], map_location=device, weights_only=False) if checkpoint.get("format_version") != config["data"]["format_version"]: raise ValueError("checkpoint format_version mismatch") if checkpoint["input_order"] != list(module.INPUT_ORDER) or checkpoint["output_order"] != list(module.OUTPUT_ORDER): raise ValueError("checkpoint coordinate or variable protocol mismatch") model = module.PINNTC(**checkpoint["model_config"]).to(device) model.load_state_dict(checkpoint["model_state_dict"]); model.eval() data = config["data"] y_axis = np.linspace(-data["horizontal_extent_m"], data["horizontal_extent_m"], data["grid_points"], dtype=np.float32) x_axis = y_axis.copy() times = np.asarray(data["observation_times_s"], dtype=np.float32) pressures = np.linspace(data["pressure_min_pa"], data["pressure_max_pa"], data["pressure_levels"], dtype=np.float32) shape = (len(y_axis), len(x_axis), len(pressures), len(times), 4) expected = (251, 251, 44, 3, 4) if shape != expected: raise ValueError(f"dense grid must remain {expected}, got {shape}") output = ROOT / config["paths"]["prediction"] output.parent.mkdir(parents=True, exist_ok=True) temporary = output.with_suffix(".work.npy") prediction = np.lib.format.open_memmap(temporary, mode="w+", dtype=np.float32, shape=shape) flat = prediction.reshape(-1, 4) chunk_size = int(config["runtime"]["inference_chunk_size"]) with torch.inference_mode(): for start in range(0, len(flat), chunk_size): stop = min(start + chunk_size, len(flat)) coordinates = coordinates_for_indices(np.arange(start, stop, dtype=np.int64), y_axis, x_axis, times, pressures) values = model(torch.from_numpy(coordinates).to(device)).cpu().numpy().astype(np.float32) if not np.isfinite(values).all(): raise FloatingPointError(f"non-finite prediction in rows {start}:{stop}") flat[start:stop] = values prediction.flush() np.savez(output, predictions=prediction, y=y_axis, x=x_axis, time=times, pressure=pressures, input_order=np.asarray(module.INPUT_ORDER), output_order=np.asarray(module.OUTPUT_ORDER), format_version=np.asarray(config["data"]["format_version"]), layout=np.asarray("YXPTV")) del prediction temporary.unlink() print(f"predictions={output.relative_to(ROOT)} shape={shape} bytes={output.stat().st_size}") if __name__ == "__main__": main()