yushuang88's picture
Upload folder using huggingface_hub
eac1b4e verified
Raw History Blame Contribute Delete
6.24 kB
from __future__ import annotations
import json
import os
import random
import sys
from pathlib import Path
from typing import Any
import numpy as np
import torch
import yaml
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from model.ho_fno_pipe import HOFNOPipe, count_parameters # noqa: E402
class UnitTransformer:
def __init__(self, tensor: torch.Tensor, axes: tuple[int, ...] = (0, 1), eps: float = 1e-8) -> None:
self.axes = axes
self.eps = eps
self.mean = tensor.mean(dim=axes, keepdim=True)
self.std = tensor.std(dim=axes, keepdim=True) + eps
def encode(self, tensor: torch.Tensor) -> torch.Tensor:
return (tensor - self.mean) / self.std
def decode(self, tensor: torch.Tensor) -> torch.Tensor:
return tensor * self.std + self.mean
def to(self, device: torch.device) -> "UnitTransformer":
self.mean = self.mean.to(device)
self.std = self.std.to(device)
return self
def state_dict(self) -> dict[str, Any]:
return {"mean": self.mean.cpu(), "std": self.std.cpu(), "axes": self.axes, "eps": self.eps}
@classmethod
def from_state_dict(cls, state: dict[str, Any]) -> "UnitTransformer":
obj = cls.__new__(cls)
obj.mean = state["mean"]
obj.std = state["std"]
obj.axes = tuple(state.get("axes", (0, 1)))
obj.eps = float(state.get("eps", 1e-8))
return obj
def load_config(path: str | os.PathLike[str] | None = None) -> dict[str, Any]:
cfg_path = Path(path) if path else ROOT / "config" / "config.yaml"
with cfg_path.open("r", encoding="utf-8") as handle:
cfg = yaml.safe_load(handle)
cfg["_config_path"] = str(cfg_path)
return cfg
def set_seed(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 resolve(path: str | os.PathLike[str], base: Path | None = None) -> Path:
p = Path(path)
if p.is_absolute():
return p
return (base or ROOT) / p
def select_device(require_accelerator: bool = False) -> torch.device:
if torch.cuda.is_available():
return torch.device("cuda")
if require_accelerator:
raise RuntimeError("accelerator is required, but torch.cuda.is_available() is False")
return torch.device("cpu")
def load_pipe_tensors(cfg: dict[str, Any]) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
data_cfg = cfg["data"]
data_dir = Path(cfg["paths"]["data_dir"])
train_size = int(data_cfg["train_size"])
test_size = int(data_cfg["test_size"])
total_used = int(data_cfg["total_used"])
r1, r2 = (int(v) for v in data_cfg.get("downsample", [1, 1]))
s1 = int(((int(data_cfg["resolution"][0]) - 1) / r1) + 1)
s2 = int(((int(data_cfg["resolution"][1]) - 1) / r2) + 1)
x_raw = np.load(data_dir / data_cfg["x_file"], mmap_mode="r")[:total_used, ::r1, ::r2][:, :s1, :s2]
y_raw = np.load(data_dir / data_cfg["y_file"], mmap_mode="r")[:total_used, ::r1, ::r2][:, :s1, :s2]
q_raw = np.load(data_dir / data_cfg["q_file"], mmap_mode="r")[
:total_used, int(data_cfg["output_channel_index"]), ::r1, ::r2
][:, :s1, :s2]
coords = np.stack((np.asarray(x_raw, dtype=np.float32), np.asarray(y_raw, dtype=np.float32)), axis=-1)
target = np.asarray(q_raw, dtype=np.float32)[..., None]
x_all = torch.from_numpy(coords.copy())
y_all = torch.from_numpy(target.copy())
return x_all[:train_size], y_all[:train_size], x_all[-test_size:], y_all[-test_size:]
def make_normalized_loaders(cfg: dict[str, Any]):
x_train, y_train, x_test, y_test = load_pipe_tensors(cfg)
axes = tuple(int(v) for v in cfg["data"].get("normalizer_axes", [0, 1]))
x_normalizer = UnitTransformer(x_train, axes=axes)
y_normalizer = UnitTransformer(y_train, axes=axes)
x_train_n = x_normalizer.encode(x_train)
x_test_n = x_normalizer.encode(x_test)
y_train_n = y_normalizer.encode(y_train)
y_test_n = y_normalizer.encode(y_test)
batch_size = int(cfg["training"]["batch_size"])
loader_kwargs = {
"batch_size": batch_size,
"num_workers": int(cfg["data"].get("num_workers", 0)),
"pin_memory": bool(cfg["data"].get("pin_memory", False)),
}
train_ds = torch.utils.data.TensorDataset(x_train_n, y_train_n)
test_ds = torch.utils.data.TensorDataset(x_test_n, y_test_n)
train_loader = torch.utils.data.DataLoader(train_ds, shuffle=True, **loader_kwargs)
test_loader = torch.utils.data.DataLoader(test_ds, shuffle=False, **loader_kwargs)
return train_loader, test_loader, x_normalizer, y_normalizer
def build_model(cfg: dict[str, Any]) -> HOFNOPipe:
m = cfg["model"]
return HOFNOPipe(
input_channels=int(m["input_channels"]),
output_channels=int(m["output_channels"]),
order=int(m["order"]),
layers=int(m["layers"]),
width=int(m["width"]),
modes=tuple(int(v) for v in m["modes"]),
add_unit_grid=bool(m.get("add_unit_grid", True)),
mlp_expansion=int(m.get("mlp_expansion", 4)),
mlp_dropout=float(m.get("mlp_dropout", 0.0)),
drop_path_rate=float(m.get("drop_path_rate", 0.0)),
)
def relative_l2_per_sample(pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
pred_f = pred.reshape(pred.shape[0], -1)
target_f = target.reshape(target.shape[0], -1)
denom = torch.linalg.vector_norm(target_f, dim=1).clamp_min(1e-12)
return torch.linalg.vector_norm(pred_f - target_f, dim=1) / denom
def relative_l2_sum(pred: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
return relative_l2_per_sample(pred, target).sum()
def save_json(path: Path, payload: dict[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("w", encoding="utf-8") as handle:
json.dump(payload, handle, indent=2)
__all__ = [
"ROOT",
"UnitTransformer",
"build_model",
"count_parameters",
"load_config",
"make_normalized_loaders",
"relative_l2_per_sample",
"relative_l2_sum",
"resolve",
"save_json",
"select_device",
"set_seed",
]