File size: 6,592 Bytes
4462d29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
"""Reference outputs of the three release models, computed with the ORIGINAL code.

Runs the frozen code in ../source_snapshot on the frozen weights in ../originals, on the
first devices of the internal test split 0, and writes ../reference/<model>.npz. The new
boss_bench code must reproduce these outputs on the same devices (matched by uuid in the
published files).

Usage: python make_reference.py {loss_fraction,heat_flux_iid,inverse_design}

Everything runs on CPU in float32 with fixed noise, so the outputs are deterministic.
"""

import os
import sys
import types

import h5py
import numpy as np
import torch

ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SNAP = os.path.join(ROOT, "source_snapshot")
ORIG = os.path.join(ROOT, "originals")
OUT = os.path.join(ROOT, "reference")
DATA = "/path/to/internal/processed/confinement/confinement_data_test_split_00000.hdf5"  # pre-release file
SEED = 0


def uuids(rows):
    with h5py.File(DATA, "r") as f:
        return f["uuid"][:][rows].astype(str)


def loss_fraction(n=64):
    sys.path.insert(0, os.path.join(SNAP, "artemiss_benchmarks"))
    from benchmarks.surrogates.models_vmec.train import Predictor, load_files

    d = os.path.join(ORIG, "loss_fraction")
    pred = Predictor(os.path.join(d, "model_loss_frac_conv_tmax_100ms_biggg_sigmoid.pt"),
                     os.path.join(d, "model_config_loss_frac_conv_tmax_100ms_biggg_sigmoid.json"))
    # t_max = 0.1 s and s0 = 0.1, as in the training run (the "tmax_100ms" in its name)
    data = load_files(DATA, "loss_frac", s0=0.1, t_max_override=0.1)

    # load_files drops rows with NaN inputs; rebuild its mask to recover the rows' uuids
    with h5py.File(DATA, "r") as f:
        valid = ~np.isnan(f["surf_xyz"][:]).any(axis=1)
        for k in ("pressure", "current"):
            valid &= ~np.isnan(f[k][:]).any(axis=1)
        valid &= ~np.isnan(f["volavgB"][:])
    rows = np.flatnonzero(valid)
    assert len(rows) == len(data["target"])
    rows = rows[:n]

    inputs = {k: data[k][:n] for k in ("surf_r", "surf_z", "pressure", "current", "volavgB", "nfp")}
    y = pred.predict_batch(**inputs)
    np.savez(os.path.join(OUT, "loss_fraction.npz"), uuid=uuids(rows), rows=rows,
             prediction=np.asarray(y), target=data["target"][:n], **inputs)
    mae = np.abs(np.asarray(y) - data["target"][:n]).mean()
    print(f"loss_fraction: {n} devices, MAE vs traced = {mae:.4f}")


def heat_flux_iid(n_configs=16, n_points=512, n_steps=50):
    sys.path.insert(0, os.path.join(SNAP, "fusion", "heat_flux"))
    import train_final as tf
    from flow_model_final import FlowMatchingModel

    d = os.path.join(ORIG, "heat_flux_iid")
    cfg = torch.load(os.path.join(d, "model_config.pt"), weights_only=True)
    model = FlowMatchingModel(
        d_cond=cfg["d_cond"], x_dim=cfg.get("x_dim", tf.X_DIM),
        embed_dim=cfg["embed_dim"], hidden=cfg["hidden_dim"],
        full_cloud=cfg.get("full_cloud", False), pool=cfg.get("pool", "max"),
        gate_global=cfg.get("gate_global", False),
        n_vel_blocks=cfg.get("n_vel_blocks", 4))
    model.load_state_dict(torch.load(os.path.join(d, "model_best.pt"), weights_only=True))
    model.eval()
    stats = dict(np.load(os.path.join(d, "cond_stats.npz")))

    # min_lost=0 keeps every (config, s_init) pair, so unique index == HDF5 row
    scal, surf, v2u, sinit, points, lf, ni, _ = tf.load_single_file(DATA, min_lost=0)
    keep = v2u < n_configs
    scal, surf = scal[:n_configs], surf[:n_configs]
    v2u, sinit = v2u[keep], sinit[keep]
    points = [p for p, k in zip(points, keep) if k]
    lf, ni = lf[keep], ni[keep]
    raw = dict(cond_scalars=scal.copy(), surf_xyz=surf.copy(), s_init=sinit.copy())

    tf.apply_stats(scal, sinit, surf, points, stats)
    c_emb, f_pred = tf.encode_and_predict_all(
        model, scal, sinit, torch.from_numpy(surf), v2u, torch.device("cpu"))

    gen = torch.Generator().manual_seed(SEED)
    x0 = torch.randn(len(v2u), n_points, tf.X_DIM, generator=gen)
    with torch.no_grad():
        c_pt = c_emb.repeat_interleave(n_points, dim=0)
        x1 = tf._ode_integrate(model, x0.reshape(-1, tf.X_DIM), c_pt, n_steps, "euler", amp=False)
    x1 = x1.reshape(len(v2u), n_points, tf.X_DIM)

    np.savez(os.path.join(OUT, "heat_flux_iid.npz"), uuid=uuids(np.arange(n_configs)),
             config_index=v2u, loss_fraction_pred=f_pred, loss_fraction=lf, n_inner=ni,
             cond_embedding=c_emb.numpy(), x0=x0.numpy(), x1=x1.numpy(),
             n_steps=n_steps, sampler="euler", **raw)
    print(f"heat_flux_iid: {len(v2u)} samples, loss-fraction MAE = {np.abs(f_pred - lf).mean():.4f}")


def inverse_design(n=8):
    here = os.path.join(SNAP, "fusion", "shape_generation", "inverse_design")
    sys.path.insert(0, here)
    sys.modules.setdefault("wandb", types.ModuleType("wandb"))  # imported by train.py, unused here
    import dataloading as dl
    from evaluate_samples import build_model, to_rz
    from flow_settings import load_flow_settings
    from sample_loss_sweep import sample_from_noise

    ck = torch.load(os.path.join(ORIG, "inverse_design", "boss_diffusion_surf_rz_unet_ep4000_ema.pt"),
                    map_location="cpu", weights_only=False)
    model, d_design = build_model(ck, "cpu")
    model.load_state_dict(ck["ema"])
    model.eval()
    sp = load_flow_settings()["sampling"]

    ds = dl.StellaratorDataset([DATA], stats=ck["stats"], normalize=True)
    # the dataset drops rows (NaN, non-stellsym); find each kept row by its boundary
    with h5py.File(DATA, "r") as f:
        xyz = f["surf_xyz"][:].astype(np.float32)
    rows = np.array([np.flatnonzero((xyz == ds.surf_xyz[i]).all(axis=1))[0] for i in range(n)])

    props = ds.props[:n]
    x0 = torch.randn(n, d_design, generator=torch.Generator().manual_seed(SEED))
    with torch.no_grad():
        x1 = sample_from_noise(model, props, x0, sp["n_steps"], sp["method"], sp["t_min"], sp["t_max"])
    R, Z = to_rz(x1, ck["stats"], "cpu")

    np.savez(os.path.join(OUT, "inverse_design.npz"), uuid=uuids(rows), rows=rows,
             props_raw=ds.props_raw[:n], props=props.numpy(), x0=x0.numpy(), x1=x1.numpy(),
             R=R, Z=Z, R_true=ds.design_raw[:n].reshape(n, 41, 41, 2)[..., 0],
             Z_true=ds.design_raw[:n].reshape(n, 41, 41, 2)[..., 1], **{f"sampling_{k}": v for k, v in sp.items()})
    print(f"inverse_design: {n} designs, sampler {sp}")


if __name__ == "__main__":
    torch.manual_seed(SEED)
    {"loss_fraction": loss_fraction, "heat_flux_iid": heat_flux_iid,
     "inverse_design": inverse_design}[sys.argv[1]]()