Efradeca's picture
Upload folder using huggingface_hub
2c93889 verified
Raw
History Blame Contribute Delete
6.15 kB
"""Tensile2d (PLAID-datasets/Tensile2d, HF, CC-BY-SA-4.0) adapter — SECOND benchmark.
Why: unlike Geo-FNO Elasticity (scalar von Mises target -> our tensor is latent), Tensile2d directly
supervises the full 2D Cauchy stress tensor (sig11, sig22, sig12) per node on an irregular mesh, so our
equilibrium regularizer acts on a SUPERVISED tensor and we can validate the predicted tensor itself.
VERIFIED facts (golden rule, by reading real samples):
- Each sample is a pickled CGNS tree (no PLAID lib needed): GridCoordinates {CoordinateX, CoordinateY}
and PointData {sig11, sig22, sig12, U1, U2, q}; plus input scalars {P, p1..p5} and output scalars.
- 702 rows: samples 0-499 are the labeled `train_500` set (full stress); samples 500-701 (test 200 +
OOD 2) have stress fields WITHHELD (competition) -> only coords + input scalars. So we use the 500
labeled samples and hold out a local test split (no official test ground truth is public).
- Node count varies per sample (6143-11801, mean ~9400). Static, plane-strain, quasistatic.
`build_cache()` writes data/tensile2d/samples/*.npz once. `build_tensile_splits()` loads them, fits
z-score normalizers on the train split, and returns per-sample lists (variable N -> no stacking).
"""
from __future__ import annotations
import glob
import os
from dataclasses import dataclass
from typing import List
import numpy as np
import torch
INPUT_SCALARS = ["P", "p1", "p2", "p3", "p4", "p5"]
def _field(tree, name):
stack = [tree]
while stack:
n = stack.pop()
if isinstance(n, list) and len(n) == 4:
if n[0] == name and isinstance(n[1], np.ndarray):
return n[1]
stack.extend(n[2] or [])
return None
def build_cache(out_dir: str = "data/tensile2d/samples") -> int:
"""Download Tensile2d parquet shards and cache the 500 labeled samples as .npz. Returns count."""
from huggingface_hub import hf_hub_download
import pyarrow.parquet as pq
import pickle
os.makedirs(out_dir, exist_ok=True)
shards = [hf_hub_download("PLAID-datasets/Tensile2d",
f"data/all_samples-0000{i}-of-00002.parquet", repo_type="dataset")
for i in (0, 1)]
idx = 0
saved = 0
for sh in shards:
col = pq.read_table(sh).column("sample")
for i in range(len(col)):
try:
obj = pickle.loads(col[i].as_py())
tree = list(obj["meshes"].values())[0]
x, y = _field(tree, "CoordinateX"), _field(tree, "CoordinateY")
s11, s22, s12 = _field(tree, "sig11"), _field(tree, "sig22"), _field(tree, "sig12")
if any(v is None for v in (x, y, s11, s22, s12)):
idx += 1
continue # withheld test/OOD sample
coords = np.stack([x, y], 1).astype(np.float32)
sigma = np.stack([s11, s22, s12], 1).astype(np.float32)
vm = np.sqrt(s11 ** 2 - s11 * s22 + s22 ** 2 + 3 * s12 ** 2).astype(np.float32)
sc = obj.get("scalars", {})
scin = np.array([float(sc[k]) for k in INPUT_SCALARS], np.float32)
np.savez(os.path.join(out_dir, f"{saved:04d}.npz"),
coords=coords, sigma=sigma, vm=vm, scalars_in=scin)
saved += 1
except Exception:
pass
idx += 1
return saved
@dataclass
class TensileSplits:
train_coords: List[torch.Tensor]
train_sigma: List[torch.Tensor] # scaled by 1/S (N,3)
train_scalars: List[torch.Tensor] # z-scored (6,)
test_coords: List[torch.Tensor]
test_sigma: List[torch.Tensor]
test_scalars: List[torch.Tensor]
scale_S: float # SINGLE global stress scale; de-normalize: sigma*S
scalar_mean: torch.Tensor # (6,)
scalar_std: torch.Tensor
def build_tensile_splits(samples_dir: str = "data/tensile2d/samples",
ntrain: int = 400, ntest: int = 100, seed: int = 0) -> TensileSplits:
"""Load cached samples; deterministic train/test split; z-score normalizers fit on TRAIN only.
Coords are kept RAW (physical units) so the MLS divergence operator measures true distances; the
model conditions on raw coords + normalized input scalars (the encoder MLP handles the coord scale).
Stress is z-scored per component; input scalars z-scored per dim.
"""
files = sorted(glob.glob(os.path.join(samples_dir, "*.npz")))
if not files:
raise FileNotFoundError(f"no cached Tensile2d samples in {samples_dir} — run build_cache() first")
rng = np.random.default_rng(seed)
order = rng.permutation(len(files))
tr_idx, te_idx = order[:ntrain], order[ntrain:ntrain + ntest]
def load(i):
d = np.load(files[i])
return d["coords"], d["sigma"], d["scalars_in"]
tr = [load(i) for i in tr_idx]
te = [load(i) for i in te_idx]
# SINGLE global stress scale S (RMS of all train stress components) so the divergence regularizer
# de-normalizes with one scalar: sigma_phys = S * sigma_norm => div(sigma_phys) = S * div(sigma_norm).
# (Per-component z-score would scale each channel differently and corrupt the physical divergence.)
sig_all = np.concatenate([s for _, s, _ in tr], 0) # (sum N, 3)
S = float(np.sqrt((sig_all ** 2).mean())) # global RMS scale
sc_all = np.stack([sc for _, _, sc in tr], 0) # (ntrain, 6)
sc_mean, sc_std = sc_all.mean(0), sc_all.std(0) + 1e-8
def pack(rows):
cs, ss, scs = [], [], []
for c, s, sc in rows:
cs.append(torch.tensor(c))
ss.append(torch.tensor(s / S))
scs.append(torch.tensor((sc - sc_mean) / sc_std))
return cs, ss, scs
trc, trs, trsc = pack(tr)
tec, tes, tesc = pack(te)
return TensileSplits(
train_coords=trc, train_sigma=trs, train_scalars=trsc,
test_coords=tec, test_sigma=tes, test_scalars=tesc,
scale_S=S,
scalar_mean=torch.tensor(sc_mean), scalar_std=torch.tensor(sc_std),
)