Upload folder using huggingface_hub
Browse files- .gitattributes +7 -0
- README.md +37 -0
- config.json +10 -0
- config/config.yaml +42 -0
- model/__init__.py +1 -0
- model/ronom.py +165 -0
- results/best_validation_metrics.json +23 -0
- results/data_provenance.json +42 -0
- results/figures/test_case_800_fields.pdf +0 -0
- results/figures/test_case_800_fields.png +0 -0
- results/figures/test_case_800_profiles.pdf +0 -0
- results/figures/test_case_800_profiles.png +3 -0
- results/figures/test_case_880_fields.pdf +0 -0
- results/figures/test_case_880_fields.png +0 -0
- results/figures/test_case_880_profiles.pdf +0 -0
- results/figures/test_case_880_profiles.png +3 -0
- results/figures/test_case_960_fields.pdf +0 -0
- results/figures/test_case_960_fields.png +0 -0
- results/figures/test_case_960_profiles.pdf +0 -0
- results/figures/test_case_960_profiles.png +3 -0
- results/figures/test_case_994_fields.pdf +0 -0
- results/figures/test_case_994_fields.png +3 -0
- results/figures/test_case_994_profiles.pdf +0 -0
- results/figures/test_case_994_profiles.png +3 -0
- results/figures/test_error_summary.pdf +0 -0
- results/figures/test_error_summary.png +3 -0
- results/figures/training_curve.pdf +0 -0
- results/figures/training_curve.png +3 -0
- results/reproduction_summary.json +95 -0
- results/runtime_result.json +28 -0
- results/table1a_comparison.csv +3 -0
- results/test_metrics.json +34 -0
- results/test_per_sample.csv +201 -0
- results/test_predictions.npz +3 -0
- results/training_history.csv +0 -0
- results/training_summary.json +14 -0
- results/verification.json +41 -0
- results/visualization_manifest.json +42 -0
- scripts/common.py +193 -0
- scripts/inference.py +53 -0
- scripts/result.py +102 -0
- scripts/train.py +152 -0
- weight/best_model.pth +3 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,10 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
results/figures/test_case_800_profiles.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
results/figures/test_case_880_profiles.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
results/figures/test_case_960_profiles.png filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
results/figures/test_case_994_fields.png filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
results/figures/test_case_994_profiles.png filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
results/figures/test_error_summary.png filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
results/figures/training_curve.png filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
tags:
|
| 6 |
+
- OneScience
|
| 7 |
+
- fluid-dynamics
|
| 8 |
+
library_name: pytorch
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# RONOM
|
| 12 |
+
|
| 13 |
+
## Model
|
| 14 |
+
|
| 15 |
+
This package reproduces the one-dimensional viscous Burgers input generalization experiment. The trained checkpoint is `weight/best_model.pth`; model code, configuration, training, inference, and visualization scripts are included. The package was trained once; reported numbers are not guarantees for another run.
|
| 16 |
+
|
| 17 |
+
## Data
|
| 18 |
+
|
| 19 |
+
Download [OneScience/RONOM-Burgers](https://www.modelscope.cn/datasets/OneScience/RONOM-Burgers) with `modelscope download --dataset OneScience/RONOM-Burgers --local_dir ./data`. Burgers.mat contains 1,000 trajectories: input [1000,1025], output [1000,101,1025], tspan [1,101], float64. Split: 600 train, 200 validation, 200 test.
|
| 20 |
+
|
| 21 |
+
Data provenance: [https://zenodo.org/records/21451819](https://zenodo.org/records/21451819).
|
| 22 |
+
|
| 23 |
+
## Usage
|
| 24 |
+
|
| 25 |
+
```bash
|
| 26 |
+
hf download OneScience-Group/RONOM --local-dir ./RONOM
|
| 27 |
+
cd RONOM
|
| 28 |
+
pip install torch numpy scipy h5py pyyaml matplotlib
|
| 29 |
+
python scripts/inference.py
|
| 30 |
+
python scripts/result.py
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
For training, run `python scripts/train.py` after installing the appropriate accelerator-enabled PyTorch environment and downloading the data above. See `config/config.yaml` for experiment parameters. Preserve the included checkpoint before retraining.
|
| 34 |
+
|
| 35 |
+
## Result and citation
|
| 36 |
+
|
| 37 |
+
test trajectory RMSE: 0.00170540; paper Table 1(a): 0.00134. See `results/` for the recorded outputs and the [original paper or data page](https://zenodo.org/records/21451819) for the research context. The dataset license, where not separately stated by its author, must not be inferred from the code license.
|
config.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "ronom_burgers",
|
| 3 |
+
"architectures": [
|
| 4 |
+
"RONOM"
|
| 5 |
+
],
|
| 6 |
+
"framework": "pytorch",
|
| 7 |
+
"experiment": "one-dimensional viscous Burgers input generalization",
|
| 8 |
+
"checkpoint": "weight/best_model.pth",
|
| 9 |
+
"dataset": "OneScience/RONOM-Burgers"
|
| 10 |
+
}
|
config/config.yaml
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
seed: 0
|
| 2 |
+
data:
|
| 3 |
+
directory: data
|
| 4 |
+
filename: Burgers.mat
|
| 5 |
+
train_samples: 600
|
| 6 |
+
validation_samples: 200
|
| 7 |
+
test_samples: 200
|
| 8 |
+
space_stride: 32
|
| 9 |
+
time_stride: 10
|
| 10 |
+
model:
|
| 11 |
+
grid_size: 33
|
| 12 |
+
features: [64, 128, 128, 256]
|
| 13 |
+
latent_channels: 4
|
| 14 |
+
fourier_features: 32
|
| 15 |
+
velocity_widths: [64, 64, 64]
|
| 16 |
+
padding_size: 2
|
| 17 |
+
alpha: 1.0e-5
|
| 18 |
+
sobolev_reg: 1.0e-8
|
| 19 |
+
bc_reg: 10.0
|
| 20 |
+
step_size: 0.1
|
| 21 |
+
training:
|
| 22 |
+
epochs: 5000
|
| 23 |
+
batch_size: 32
|
| 24 |
+
learning_rate: 0.001
|
| 25 |
+
weight_decay: 0.0
|
| 26 |
+
scheduler_frequency: 250
|
| 27 |
+
scheduler_gamma: 0.9
|
| 28 |
+
validation_frequency: 1000
|
| 29 |
+
checkpoint_frequency: 100
|
| 30 |
+
jit_flow: true
|
| 31 |
+
device: cuda
|
| 32 |
+
num_threads: 8
|
| 33 |
+
paths:
|
| 34 |
+
results: results
|
| 35 |
+
checkpoint: weight/best_model.pth
|
| 36 |
+
last_checkpoint: weight/last_model.pth
|
| 37 |
+
inference:
|
| 38 |
+
batch_size: 32
|
| 39 |
+
device: cuda
|
| 40 |
+
visualization:
|
| 41 |
+
sample_indices: [0, 80, 160]
|
| 42 |
+
dpi: 200
|
model/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from .ronom import RONOM
|
model/ronom.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Independent implementation of RONOM, arXiv:2507.12814v2, Sec. 2 and SM3.
|
| 2 |
+
|
| 3 |
+
The projection solves weighted regularized least squares in a Gaussian basis;
|
| 4 |
+
it is not a learned Fourier operator. All geometric matrices are path independent.
|
| 5 |
+
"""
|
| 6 |
+
import math
|
| 7 |
+
from typing import List
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
from scipy.special import erf
|
| 11 |
+
import torch
|
| 12 |
+
from torch import nn
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def gaussian(x, centers, sigma):
|
| 16 |
+
return np.exp(-0.5 * ((x[:, None] - centers[None, :]) / sigma) ** 2)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def laplacian_gram(centers, sigma):
|
| 20 |
+
"""Closed-form integral of phi_i'' phi_j'' on [0,1], in float64.
|
| 21 |
+
|
| 22 |
+
Complete the square at the midpoint and integrate Gaussian moments 0,2,4.
|
| 23 |
+
The recurrence follows integration by parts, without numerical quadrature.
|
| 24 |
+
"""
|
| 25 |
+
mid = (centers[:, None] + centers[None, :]) / 2
|
| 26 |
+
dist = centers[:, None] - centers[None, :]
|
| 27 |
+
lo, hi = -mid, 1 - mid
|
| 28 |
+
ea, eb = np.exp(-(lo / sigma)**2), np.exp(-(hi / sigma)**2)
|
| 29 |
+
j0 = sigma * math.sqrt(math.pi) / 2 * (erf(hi / sigma) - erf(lo / sigma))
|
| 30 |
+
j2 = sigma**2 / 2 * (j0 - hi * eb + lo * ea)
|
| 31 |
+
j4 = sigma**2 / 2 * (3 * j2 - hi**3 * eb + lo**3 * ea)
|
| 32 |
+
value = np.exp(-dist**2 / (4 * sigma**2)) / sigma**8 * (
|
| 33 |
+
j4 - (dist**2 / 2 + 2 * sigma**2) * j2
|
| 34 |
+
+ (dist**2 / 4 - sigma**2)**2 * j0)
|
| 35 |
+
return (value + value.T) / 2
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def projection_matrix(x_in, x_measure, centers, sigma, alpha, sobolev_reg, bc_reg):
|
| 39 |
+
basis = gaussian(x_in, centers, sigma)
|
| 40 |
+
# Trapezoidal integral weights; all input-generalization points are uniform.
|
| 41 |
+
w = np.empty(len(x_in), dtype=np.float64)
|
| 42 |
+
w[0], w[-1] = (x_in[1] - x_in[0]) / 2, (x_in[-1] - x_in[-2]) / 2
|
| 43 |
+
w[1:-1] = (x_in[2:] - x_in[:-2]) / 2
|
| 44 |
+
edge = gaussian(np.array([0., 1.]), centers, sigma)
|
| 45 |
+
# Official residual convention scales the derivative residual by sigma.
|
| 46 |
+
deriv = -(np.array([0., 1.])[:, None] - centers) / sigma * edge
|
| 47 |
+
residual = np.stack([edge[0] - edge[1], deriv[0] - deriv[1]])
|
| 48 |
+
rhs = basis.T * w
|
| 49 |
+
normal = (rhs @ basis + sobolev_reg * laplacian_gram(centers, sigma)
|
| 50 |
+
+ bc_reg * (residual.T @ residual) + alpha * np.eye(len(centers)))
|
| 51 |
+
return gaussian(x_measure, centers, sigma) @ np.linalg.solve(normal, rhs)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
class LatentFlow(nn.Module):
|
| 55 |
+
"""Autonomous neural ODE with the explicit fourth order 3/8 tableau."""
|
| 56 |
+
def __init__(self, dimension, widths, step_size):
|
| 57 |
+
super().__init__()
|
| 58 |
+
layers = []
|
| 59 |
+
prev = dimension
|
| 60 |
+
for width in widths:
|
| 61 |
+
layers += [nn.Linear(prev, width), nn.GELU()]
|
| 62 |
+
prev = width
|
| 63 |
+
layers.append(nn.Linear(prev, dimension))
|
| 64 |
+
self.velocity = nn.Sequential(*layers)
|
| 65 |
+
self.step_size = float(step_size)
|
| 66 |
+
|
| 67 |
+
def forward(self, initial: torch.Tensor, times: torch.Tensor) -> torch.Tensor:
|
| 68 |
+
state = initial
|
| 69 |
+
states = torch.jit.annotate(List[torch.Tensor], [])
|
| 70 |
+
states.append(state)
|
| 71 |
+
for i in range(times.numel() - 1):
|
| 72 |
+
# Sampling times are 0,.1,...,1. Substeps also allow a coarser query.
|
| 73 |
+
interval = float(times[i + 1] - times[i])
|
| 74 |
+
count = max(1, int(math.ceil(interval / self.step_size - 1e-6)))
|
| 75 |
+
h = interval / count
|
| 76 |
+
for _ in range(count):
|
| 77 |
+
a = self.velocity(state)
|
| 78 |
+
b = self.velocity(state + (h / 3.) * a)
|
| 79 |
+
c = self.velocity(state + h * (b - a / 3.))
|
| 80 |
+
d = self.velocity(state + h * (a - b + c))
|
| 81 |
+
state = state + (h / 8.) * (a + 3. * b + 3. * c + d)
|
| 82 |
+
states.append(state)
|
| 83 |
+
return torch.stack(states, dim=1)
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
class RONOM(nn.Module):
|
| 87 |
+
def __init__(self, grid_size=33, features=(64, 128, 128, 256),
|
| 88 |
+
latent_channels=4, fourier_features=32, velocity_widths=(64, 64, 64),
|
| 89 |
+
padding_size=2, alpha=1e-5, sobolev_reg=1e-8, bc_reg=10.,
|
| 90 |
+
step_size=0.1):
|
| 91 |
+
super().__init__()
|
| 92 |
+
self.grid_size = int(grid_size)
|
| 93 |
+
self.latent_channels = int(latent_channels)
|
| 94 |
+
self.alpha, self.sobolev_reg, self.bc_reg = alpha, sobolev_reg, bc_reg
|
| 95 |
+
self.sigma = 1 / ((grid_size - 1) * math.sqrt(-2 * math.log(0.5)))
|
| 96 |
+
self.register_buffer('frequencies', torch.randn(fourier_features, 1))
|
| 97 |
+
f = list(features)
|
| 98 |
+
self.lift = nn.Sequential(nn.Conv1d(1 + 2 * fourier_features, 64, 3, padding=1),
|
| 99 |
+
nn.ReLU(), nn.Conv1d(64, f[0] // 2, 3, padding=1))
|
| 100 |
+
self.output_project = nn.Sequential(
|
| 101 |
+
nn.ConvTranspose1d(f[0] // 2, 64, 3, padding=1), nn.ReLU(),
|
| 102 |
+
nn.ConvTranspose1d(64, 1, 3, padding=1))
|
| 103 |
+
enc = []
|
| 104 |
+
prev = f[0] // 2
|
| 105 |
+
for width in f:
|
| 106 |
+
enc += [nn.Conv1d(prev, width, 3, padding=1), nn.ReLU()]
|
| 107 |
+
prev = width
|
| 108 |
+
self.encoder = nn.Sequential(*enc)
|
| 109 |
+
self.to_latent = nn.Conv1d(f[-1], latent_channels, 3, padding=1)
|
| 110 |
+
dec = []
|
| 111 |
+
back = list(reversed(f)) + [f[0] // 2]
|
| 112 |
+
for left, right in zip(back[:-1], back[1:]):
|
| 113 |
+
dec += [nn.ConvTranspose1d(left, right, 3, padding=1), nn.ReLU()]
|
| 114 |
+
self.decoder = nn.Sequential(*dec)
|
| 115 |
+
self.from_latent = nn.ConvTranspose1d(latent_channels, f[-1], 3, padding=1)
|
| 116 |
+
x = np.linspace(0., 1., grid_size)
|
| 117 |
+
centers = np.arange(-padding_size, grid_size + padding_size) / (grid_size - 1)
|
| 118 |
+
proj = projection_matrix(x, x, centers, self.sigma, alpha, sobolev_reg, bc_reg)
|
| 119 |
+
kernel = gaussian(x, x, self.sigma)
|
| 120 |
+
recovery = np.linalg.solve(kernel + alpha * np.eye(grid_size), kernel.T).T
|
| 121 |
+
self.register_buffer('grid', torch.tensor(x, dtype=torch.float32))
|
| 122 |
+
self.register_buffer('centers', torch.tensor(centers, dtype=torch.float64))
|
| 123 |
+
self.register_buffer('projection', torch.tensor(proj, dtype=torch.float32))
|
| 124 |
+
self.register_buffer('recovery', torch.tensor(recovery, dtype=torch.float32))
|
| 125 |
+
self.latent_flow = LatentFlow(grid_size * latent_channels, velocity_widths, step_size)
|
| 126 |
+
|
| 127 |
+
def encode(self, initial, x_input=None):
|
| 128 |
+
if initial.ndim == 3:
|
| 129 |
+
initial = initial.squeeze(-1)
|
| 130 |
+
if x_input is None:
|
| 131 |
+
projected = initial @ self.projection.T
|
| 132 |
+
else:
|
| 133 |
+
matrix = projection_matrix(x_input.detach().cpu().double().numpy(),
|
| 134 |
+
self.grid.detach().cpu().double().numpy(), self.centers.cpu().numpy(),
|
| 135 |
+
self.sigma, self.alpha, self.sobolev_reg, self.bc_reg)
|
| 136 |
+
projected = initial @ torch.as_tensor(matrix.T, device=initial.device, dtype=initial.dtype)
|
| 137 |
+
phase = 2 * math.pi * self.grid[:, None] @ self.frequencies.T
|
| 138 |
+
positional = torch.cat([phase.cos(), phase.sin()], dim=-1).T
|
| 139 |
+
lifted_input = torch.cat([projected[:, None, :],
|
| 140 |
+
positional[None].expand(initial.shape[0], -1, -1)], dim=1)
|
| 141 |
+
return self.to_latent(self.encoder(self.lift(lifted_input)))
|
| 142 |
+
|
| 143 |
+
def decode(self, latent, x_output=None):
|
| 144 |
+
b, nt = latent.shape[:2]
|
| 145 |
+
features = latent.reshape(b * nt, self.latent_channels, self.grid_size)
|
| 146 |
+
values = self.output_project(self.decoder(self.from_latent(features)))
|
| 147 |
+
values = values.reshape(b, nt, self.grid_size)
|
| 148 |
+
if x_output is None:
|
| 149 |
+
return values @ self.recovery.T
|
| 150 |
+
train_x = self.grid.detach().cpu().double().numpy()
|
| 151 |
+
target_x = x_output.detach().cpu().double().numpy()
|
| 152 |
+
kernel = gaussian(train_x, train_x, self.sigma)
|
| 153 |
+
matrix = np.linalg.solve(kernel + self.alpha * np.eye(self.grid_size),
|
| 154 |
+
gaussian(target_x, train_x, self.sigma).T).T
|
| 155 |
+
return values @ torch.as_tensor(matrix.T, device=values.device, dtype=values.dtype)
|
| 156 |
+
|
| 157 |
+
def forward(self, initial, times, return_latent=False):
|
| 158 |
+
# CPU time coordinates avoid a host/device synchronization at every RK stage.
|
| 159 |
+
z0 = self.encode(initial)
|
| 160 |
+
zt = self.latent_flow(z0.flatten(1), times)
|
| 161 |
+
zt = zt.reshape(initial.shape[0], times.numel(), self.latent_channels, self.grid_size)
|
| 162 |
+
prediction = self.decode(zt)
|
| 163 |
+
if return_latent:
|
| 164 |
+
return prediction, zt
|
| 165 |
+
return prediction
|
results/best_validation_metrics.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"epoch": 5000,
|
| 3 |
+
"split": "validation",
|
| 4 |
+
"samples": 200,
|
| 5 |
+
"mse": 5.125773963908943e-06,
|
| 6 |
+
"global_rmse": 0.002264017218112297,
|
| 7 |
+
"trajectory_rmse_mean": 0.0016242767846566335,
|
| 8 |
+
"trajectory_rmse_std_ddof1": 0.0015811384775408664,
|
| 9 |
+
"relative_l2_mean": 0.009477731318529135,
|
| 10 |
+
"rmse_by_time": [
|
| 11 |
+
0.0026190899833518078,
|
| 12 |
+
0.0022809309520447386,
|
| 13 |
+
0.002138092286151645,
|
| 14 |
+
0.002365773666200291,
|
| 15 |
+
0.0027355348428027863,
|
| 16 |
+
0.002744344484310206,
|
| 17 |
+
0.0023092583850567467,
|
| 18 |
+
0.0019961579303643937,
|
| 19 |
+
0.0018891513302002588,
|
| 20 |
+
0.001776062215740342,
|
| 21 |
+
0.0017600350993885276
|
| 22 |
+
]
|
| 23 |
+
}
|
results/data_provenance.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"file": "/public/home/liuyushuang/zhaomingsi/onescience-cfd-data/RONOM/Burgers.mat",
|
| 3 |
+
"sha256": "5c4f361d8318d4a00c61d4d57f3d49eeeb6643096b7ec356a059f0f95d014c71",
|
| 4 |
+
"metadata": {
|
| 5 |
+
"gamma": [
|
| 6 |
+
[
|
| 7 |
+
4
|
| 8 |
+
]
|
| 9 |
+
],
|
| 10 |
+
"sigma": [
|
| 11 |
+
[
|
| 12 |
+
62500
|
| 13 |
+
]
|
| 14 |
+
],
|
| 15 |
+
"tau": [
|
| 16 |
+
[
|
| 17 |
+
25
|
| 18 |
+
]
|
| 19 |
+
],
|
| 20 |
+
"visc": [
|
| 21 |
+
[
|
| 22 |
+
0.01
|
| 23 |
+
]
|
| 24 |
+
]
|
| 25 |
+
},
|
| 26 |
+
"provenance": "User-prepared original Zenodo record 21451819; no redownload",
|
| 27 |
+
"shape": {
|
| 28 |
+
"input": [
|
| 29 |
+
1000,
|
| 30 |
+
1025
|
| 31 |
+
],
|
| 32 |
+
"output": [
|
| 33 |
+
1000,
|
| 34 |
+
101,
|
| 35 |
+
1025
|
| 36 |
+
],
|
| 37 |
+
"tspan": [
|
| 38 |
+
1,
|
| 39 |
+
101
|
| 40 |
+
]
|
| 41 |
+
}
|
| 42 |
+
}
|
results/figures/test_case_800_fields.pdf
ADDED
|
Binary file (45 kB). View file
|
|
|
results/figures/test_case_800_fields.png
ADDED
|
results/figures/test_case_800_profiles.pdf
ADDED
|
Binary file (18 kB). View file
|
|
|
results/figures/test_case_800_profiles.png
ADDED
|
Git LFS Details
|
results/figures/test_case_880_fields.pdf
ADDED
|
Binary file (46.7 kB). View file
|
|
|
results/figures/test_case_880_fields.png
ADDED
|
results/figures/test_case_880_profiles.pdf
ADDED
|
Binary file (17.9 kB). View file
|
|
|
results/figures/test_case_880_profiles.png
ADDED
|
Git LFS Details
|
results/figures/test_case_960_fields.pdf
ADDED
|
Binary file (45.2 kB). View file
|
|
|
results/figures/test_case_960_fields.png
ADDED
|
results/figures/test_case_960_profiles.pdf
ADDED
|
Binary file (18.3 kB). View file
|
|
|
results/figures/test_case_960_profiles.png
ADDED
|
Git LFS Details
|
results/figures/test_case_994_fields.pdf
ADDED
|
Binary file (47.8 kB). View file
|
|
|
results/figures/test_case_994_fields.png
ADDED
|
Git LFS Details
|
results/figures/test_case_994_profiles.pdf
ADDED
|
Binary file (19.1 kB). View file
|
|
|
results/figures/test_case_994_profiles.png
ADDED
|
Git LFS Details
|
results/figures/test_error_summary.pdf
ADDED
|
Binary file (16.9 kB). View file
|
|
|
results/figures/test_error_summary.png
ADDED
|
Git LFS Details
|
results/figures/training_curve.pdf
ADDED
|
Binary file (42.8 kB). View file
|
|
|
results/figures/training_curve.png
ADDED
|
Git LFS Details
|
results/reproduction_summary.json
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"status": "completed",
|
| 3 |
+
"training": {
|
| 4 |
+
"status": "completed",
|
| 5 |
+
"completed_epochs": 5000,
|
| 6 |
+
"batches_per_epoch": 19,
|
| 7 |
+
"training_samples_per_epoch": 600,
|
| 8 |
+
"best_epoch": 5000,
|
| 9 |
+
"best_validation_mse": 5.125773963908943e-06,
|
| 10 |
+
"selection_split": "validation[600:800]",
|
| 11 |
+
"test_used_for_selection": false,
|
| 12 |
+
"checkpoint": "weight/best_model.pth",
|
| 13 |
+
"data_sha256": "5c4f361d8318d4a00c61d4d57f3d49eeeb6643096b7ec356a059f0f95d014c71",
|
| 14 |
+
"elapsed_seconds": 8878.240034841001,
|
| 15 |
+
"slurm_job_id": "1060058"
|
| 16 |
+
},
|
| 17 |
+
"independent_test": {
|
| 18 |
+
"samples": 200,
|
| 19 |
+
"mse": 4.673988774995294e-06,
|
| 20 |
+
"global_rmse": 0.002161940973985019,
|
| 21 |
+
"trajectory_rmse_mean": 0.001705397173261511,
|
| 22 |
+
"trajectory_rmse_std_ddof1": 0.0013320967174911497,
|
| 23 |
+
"relative_l2_mean": 0.00986708805065414,
|
| 24 |
+
"rmse_by_time": [
|
| 25 |
+
0.002461057978856684,
|
| 26 |
+
0.002075854227337168,
|
| 27 |
+
0.001999032913809955,
|
| 28 |
+
0.0020728556299004206,
|
| 29 |
+
0.0018694716441612323,
|
| 30 |
+
0.0022200476157488527,
|
| 31 |
+
0.0024226956088001946,
|
| 32 |
+
0.002467867249023815,
|
| 33 |
+
0.002239861862973637,
|
| 34 |
+
0.001978805748854005,
|
| 35 |
+
0.0018544607168863218
|
| 36 |
+
],
|
| 37 |
+
"split": "independent_test",
|
| 38 |
+
"indices": [
|
| 39 |
+
800,
|
| 40 |
+
999
|
| 41 |
+
],
|
| 42 |
+
"checkpoint_epoch": 5000,
|
| 43 |
+
"checkpoint_sha256": "fd6917b63e596ad919bf7ad8504794039d303a3910925acac75f3fccd4ac08a8",
|
| 44 |
+
"checkpoint_validation_mse": 5.125773963908943e-06,
|
| 45 |
+
"selection_split": "validation[600:800]",
|
| 46 |
+
"paper_rmse_mean": 0.00134,
|
| 47 |
+
"paper_rmse_std": 0.00162,
|
| 48 |
+
"elapsed_seconds": 9.619060612050816,
|
| 49 |
+
"slurm_job_id": "1060058"
|
| 50 |
+
},
|
| 51 |
+
"verification": {
|
| 52 |
+
"status": "passed",
|
| 53 |
+
"history_epochs": 5000,
|
| 54 |
+
"training_samples_per_epoch": 600,
|
| 55 |
+
"batches_per_epoch": 19,
|
| 56 |
+
"validation_epochs": [
|
| 57 |
+
1,
|
| 58 |
+
1001,
|
| 59 |
+
2001,
|
| 60 |
+
3001,
|
| 61 |
+
4001,
|
| 62 |
+
5000
|
| 63 |
+
],
|
| 64 |
+
"best_epoch": 5000,
|
| 65 |
+
"best_validation_mse": 5.125773963908943e-06,
|
| 66 |
+
"test_indices": [
|
| 67 |
+
800,
|
| 68 |
+
999
|
| 69 |
+
],
|
| 70 |
+
"test_samples": 200,
|
| 71 |
+
"test_shape": [
|
| 72 |
+
200,
|
| 73 |
+
11,
|
| 74 |
+
33
|
| 75 |
+
],
|
| 76 |
+
"latent_shape": [
|
| 77 |
+
200,
|
| 78 |
+
11,
|
| 79 |
+
4,
|
| 80 |
+
33
|
| 81 |
+
],
|
| 82 |
+
"recomputed_rmse_mean": 0.0017053971732615108,
|
| 83 |
+
"recomputed_rmse_std_ddof1": 0.0013320967174911497,
|
| 84 |
+
"global_rmse": 0.002161940973985019,
|
| 85 |
+
"checkpoint_sha256": "fd6917b63e596ad919bf7ad8504794039d303a3910925acac75f3fccd4ac08a8",
|
| 86 |
+
"figures_verified": 20,
|
| 87 |
+
"paper_rmse_mean": 0.00134,
|
| 88 |
+
"mean_rmse_gap_percent": 27.268445765784378,
|
| 89 |
+
"test_used_for_selection": false,
|
| 90 |
+
"source_data_sha256": "5c4f361d8318d4a00c61d4d57f3d49eeeb6643096b7ec356a059f0f95d014c71"
|
| 91 |
+
},
|
| 92 |
+
"figures": 20,
|
| 93 |
+
"paper": "https://arxiv.org/pdf/2507.12814v2",
|
| 94 |
+
"numeric_match_to_paper": false
|
| 95 |
+
}
|
results/runtime_result.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"execution_channel": "local_slurm",
|
| 3 |
+
"config_source": "onescience.json",
|
| 4 |
+
"preflight_passed": true,
|
| 5 |
+
"submission_state": "submitted",
|
| 6 |
+
"execution_state": "COMPLETED",
|
| 7 |
+
"exit_code": "0:0",
|
| 8 |
+
"job_id": "1060058",
|
| 9 |
+
"smoke_job_id": "1059370",
|
| 10 |
+
"node": "a01r3n02",
|
| 11 |
+
"resources": {
|
| 12 |
+
"nodes": 1,
|
| 13 |
+
"dcu": 1,
|
| 14 |
+
"cpus": 8,
|
| 15 |
+
"memory": "30G"
|
| 16 |
+
},
|
| 17 |
+
"slurm_elapsed": "02:29:24",
|
| 18 |
+
"local_log_dir": "/public/home/liuyushuang/zhaomingsi/onescience-cfd-repro-packages/RONOM/logs",
|
| 19 |
+
"logs": [
|
| 20 |
+
"/public/home/liuyushuang/zhaomingsi/onescience-cfd-repro-packages/RONOM/results/train.log",
|
| 21 |
+
"/public/home/liuyushuang/zhaomingsi/onescience-cfd-repro-packages/RONOM/results/inference.log",
|
| 22 |
+
"/public/home/liuyushuang/zhaomingsi/onescience-cfd-repro-packages/RONOM/results/visualization.log"
|
| 23 |
+
],
|
| 24 |
+
"completed_epochs": 5000,
|
| 25 |
+
"test_samples": 200,
|
| 26 |
+
"figures": 20,
|
| 27 |
+
"blocking_reason": null
|
| 28 |
+
}
|
results/table1a_comparison.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
source,split,samples,rmse_mean,rmse_std_ddof1,units
|
| 2 |
+
paper_Table1a_RONOM,paper_test,200,0.00134,0.00162,raw
|
| 3 |
+
this_run,independent_test_800_999,200,0.001705397173261511,0.0013320967174911497,raw
|
results/test_metrics.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"samples": 200,
|
| 3 |
+
"mse": 4.673988774995294e-06,
|
| 4 |
+
"global_rmse": 0.002161940973985019,
|
| 5 |
+
"trajectory_rmse_mean": 0.001705397173261511,
|
| 6 |
+
"trajectory_rmse_std_ddof1": 0.0013320967174911497,
|
| 7 |
+
"relative_l2_mean": 0.00986708805065414,
|
| 8 |
+
"rmse_by_time": [
|
| 9 |
+
0.002461057978856684,
|
| 10 |
+
0.002075854227337168,
|
| 11 |
+
0.001999032913809955,
|
| 12 |
+
0.0020728556299004206,
|
| 13 |
+
0.0018694716441612323,
|
| 14 |
+
0.0022200476157488527,
|
| 15 |
+
0.0024226956088001946,
|
| 16 |
+
0.002467867249023815,
|
| 17 |
+
0.002239861862973637,
|
| 18 |
+
0.001978805748854005,
|
| 19 |
+
0.0018544607168863218
|
| 20 |
+
],
|
| 21 |
+
"split": "independent_test",
|
| 22 |
+
"indices": [
|
| 23 |
+
800,
|
| 24 |
+
999
|
| 25 |
+
],
|
| 26 |
+
"checkpoint_epoch": 5000,
|
| 27 |
+
"checkpoint_sha256": "fd6917b63e596ad919bf7ad8504794039d303a3910925acac75f3fccd4ac08a8",
|
| 28 |
+
"checkpoint_validation_mse": 5.125773963908943e-06,
|
| 29 |
+
"selection_split": "validation[600:800]",
|
| 30 |
+
"paper_rmse_mean": 0.00134,
|
| 31 |
+
"paper_rmse_std": 0.00162,
|
| 32 |
+
"elapsed_seconds": 9.619060612050816,
|
| 33 |
+
"slurm_job_id": "1060058"
|
| 34 |
+
}
|
results/test_per_sample.csv
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
sample_id,rmse,relative_l2
|
| 2 |
+
800,0.0014441642533977318,0.00729123012996783
|
| 3 |
+
801,0.002415821063563245,0.00960939631888287
|
| 4 |
+
802,0.0011914687871388155,0.008451511382333349
|
| 5 |
+
803,0.0012626003363246977,0.007894797343731797
|
| 6 |
+
804,0.001998250550575508,0.008627655792169167
|
| 7 |
+
805,0.0008154827869567817,0.010417249737812542
|
| 8 |
+
806,0.0011013256291670644,0.00666085619823366
|
| 9 |
+
807,0.0015790514604787243,0.008515506502624587
|
| 10 |
+
808,0.003363841125022772,0.015478086942061958
|
| 11 |
+
809,0.001860558779885756,0.010520708433998343
|
| 12 |
+
810,0.0015722720141994162,0.009038333846308927
|
| 13 |
+
811,0.0013418697131035065,0.008836094380108874
|
| 14 |
+
812,0.0035894301379888758,0.01612087508704082
|
| 15 |
+
813,0.0012646054778170076,0.010149752156164118
|
| 16 |
+
814,0.002119074684764565,0.009492618292748448
|
| 17 |
+
815,0.0007998953236767689,0.01666992334013468
|
| 18 |
+
816,0.0011044745489972795,0.0067535312444658045
|
| 19 |
+
817,0.0012831633204547967,0.0080612579188031
|
| 20 |
+
818,0.000712199600738031,0.010070663533787555
|
| 21 |
+
819,0.0011953804786144532,0.007086889618089626
|
| 22 |
+
820,0.0011368105585113162,0.010047049930414158
|
| 23 |
+
821,0.0014393966085775218,0.007766688394612785
|
| 24 |
+
822,0.0028297305790365543,0.014422616433333901
|
| 25 |
+
823,0.0008518209117680049,0.010362130183934495
|
| 26 |
+
824,0.0015096009527889858,0.010858929407126289
|
| 27 |
+
825,0.0009111668439799336,0.006464505910483904
|
| 28 |
+
826,0.0011160574425272873,0.007794441801974531
|
| 29 |
+
827,0.0011383052123295698,0.008206011598699653
|
| 30 |
+
828,0.0008093319939719636,0.005389986818021365
|
| 31 |
+
829,0.0015325703497033512,0.008833484364188529
|
| 32 |
+
830,0.0011792855042533938,0.006164376190559706
|
| 33 |
+
831,0.0013148246876960113,0.00604776096694483
|
| 34 |
+
832,0.001455539736817489,0.007089218935786096
|
| 35 |
+
833,0.001300337276554276,0.01375691214333825
|
| 36 |
+
834,0.0018383855676432533,0.00802335054490402
|
| 37 |
+
835,0.006325736936347732,0.03315410707726788
|
| 38 |
+
836,0.0008055008257981556,0.014882097065569967
|
| 39 |
+
837,0.0025166074208316557,0.011857703471058157
|
| 40 |
+
838,0.0015345968253095863,0.010198831702372176
|
| 41 |
+
839,0.0007412714997609998,0.007310470754295764
|
| 42 |
+
840,0.0008701879555373449,0.005872218593710851
|
| 43 |
+
841,0.0012929898835578905,0.008509449938652292
|
| 44 |
+
842,0.0012409216815030717,0.0074217976140607276
|
| 45 |
+
843,0.001135883691994004,0.009481252722396994
|
| 46 |
+
844,0.0012506143762502397,0.005568246185989062
|
| 47 |
+
845,0.0011502996472816083,0.009546127334200056
|
| 48 |
+
846,0.0008205328328738069,0.013193053100485566
|
| 49 |
+
847,0.0024833228468586752,0.010898839482961392
|
| 50 |
+
848,0.0012799428387503683,0.007132848003360383
|
| 51 |
+
849,0.0009348639236117146,0.008923647952120545
|
| 52 |
+
850,0.007537085395672275,0.02642515778598448
|
| 53 |
+
851,0.000959899735035825,0.008902070500208876
|
| 54 |
+
852,0.000986228428168412,0.005933525267133642
|
| 55 |
+
853,0.0009762482221984804,0.009761204137796731
|
| 56 |
+
854,0.0013718022317925273,0.010185100781627281
|
| 57 |
+
855,0.0010658512589603404,0.00919749785675796
|
| 58 |
+
856,0.0017357300505692697,0.011288073938221118
|
| 59 |
+
857,0.0014464055179929172,0.00831458797149399
|
| 60 |
+
858,0.0010702700699737973,0.008537243183456014
|
| 61 |
+
859,0.0010050917335225926,0.006474680376006104
|
| 62 |
+
860,0.00306822761824498,0.014621531613193026
|
| 63 |
+
861,0.0033928099370821195,0.0185655581334803
|
| 64 |
+
862,0.0013527266189997836,0.006640214040641837
|
| 65 |
+
863,0.0015559520463899752,0.010385949127214997
|
| 66 |
+
864,0.001105718633584989,0.005764066935622933
|
| 67 |
+
865,0.0010621524921265507,0.007696815553640195
|
| 68 |
+
866,0.001261436300985645,0.00645613708700552
|
| 69 |
+
867,0.0017576913752563894,0.010991553427473917
|
| 70 |
+
868,0.0013099092578256833,0.009590739739171884
|
| 71 |
+
869,0.0007613496377322205,0.006311841112983999
|
| 72 |
+
870,0.0017574812372593408,0.009835706150111607
|
| 73 |
+
871,0.004579392292460886,0.014371718283453802
|
| 74 |
+
872,0.0015475964335534154,0.006230175452350933
|
| 75 |
+
873,0.00148630639030871,0.009307520959803396
|
| 76 |
+
874,0.0016762723633592241,0.006335640785602478
|
| 77 |
+
875,0.0008748569414768277,0.0055368537345673935
|
| 78 |
+
876,0.0017752065839310906,0.009570745165451207
|
| 79 |
+
877,0.0009693422221720172,0.008563731232211389
|
| 80 |
+
878,0.0007937653405507869,0.005892526467193541
|
| 81 |
+
879,0.001039564762835509,0.00665225038016452
|
| 82 |
+
880,0.001457881787085484,0.006899389637768518
|
| 83 |
+
881,0.0018651977924340215,0.008489585871742623
|
| 84 |
+
882,0.0010858070845518957,0.005558571478030946
|
| 85 |
+
883,0.0012538390100052196,0.007565115823016464
|
| 86 |
+
884,0.0008817052410440639,0.0046771205813646004
|
| 87 |
+
885,0.0009310306394476048,0.006235420755048469
|
| 88 |
+
886,0.0013793459972799656,0.007405686863358377
|
| 89 |
+
887,0.0020846975641691766,0.013736941560302536
|
| 90 |
+
888,0.0011948592085205228,0.0075800670034093754
|
| 91 |
+
889,0.0011697477719223286,0.005507243210246042
|
| 92 |
+
890,0.004551107853771669,0.020819895890820953
|
| 93 |
+
891,0.003637871440803657,0.0163985772348673
|
| 94 |
+
892,0.0007941509008577267,0.005470727708808298
|
| 95 |
+
893,0.0012703569185660787,0.007230502272474798
|
| 96 |
+
894,0.0030672970438073593,0.01151415953579286
|
| 97 |
+
895,0.0011810650603748742,0.006550353576006067
|
| 98 |
+
896,0.0012441613372044002,0.014603132185172781
|
| 99 |
+
897,0.0029938960498603505,0.014752847710556592
|
| 100 |
+
898,0.0008727711794136182,0.005515131326416716
|
| 101 |
+
899,0.0011201524781288253,0.007810446284357195
|
| 102 |
+
900,0.001233991416089354,0.007057580632594449
|
| 103 |
+
901,0.0011650017881304974,0.007650767564492308
|
| 104 |
+
902,0.0011996249695272127,0.006720864212309036
|
| 105 |
+
903,0.0012370310054518714,0.0074587668715494675
|
| 106 |
+
904,0.0009220205593254075,0.007096993832622519
|
| 107 |
+
905,0.0009633853193826409,0.008508680908907603
|
| 108 |
+
906,0.0007135869913291987,0.011259870770668952
|
| 109 |
+
907,0.0010521268966999114,0.008066662355631086
|
| 110 |
+
908,0.0009190429554308218,0.010030657046073343
|
| 111 |
+
909,0.0013659371418535157,0.012432603051923527
|
| 112 |
+
910,0.0009393835036039246,0.010479185025272513
|
| 113 |
+
911,0.0009575362081858569,0.009120206511523378
|
| 114 |
+
912,0.002702246207780349,0.010972459690995962
|
| 115 |
+
913,0.0011266866886340261,0.0072892865526466
|
| 116 |
+
914,0.0016682201969247475,0.008558695096768583
|
| 117 |
+
915,0.0015066277308996962,0.008812798822178306
|
| 118 |
+
916,0.0015375844064554301,0.008466948786086675
|
| 119 |
+
917,0.0018666939150836967,0.008781869201336296
|
| 120 |
+
918,0.004188875524084949,0.01215421534930819
|
| 121 |
+
919,0.0031559402816445405,0.011026492850696725
|
| 122 |
+
920,0.0009635181897632188,0.00826463296361614
|
| 123 |
+
921,0.0035347368818962905,0.015725360016018052
|
| 124 |
+
922,0.0014358204245286086,0.012940127641777835
|
| 125 |
+
923,0.0009964901053022947,0.010119682500378042
|
| 126 |
+
924,0.0009148418169323768,0.007392940254705161
|
| 127 |
+
925,0.0016691999941050087,0.011411197426739681
|
| 128 |
+
926,0.002387696487705466,0.012382249128682523
|
| 129 |
+
927,0.0014521756816838005,0.00765643279091908
|
| 130 |
+
928,0.0009902341380583805,0.01413048801942241
|
| 131 |
+
929,0.002825700257807701,0.015226357917784709
|
| 132 |
+
930,0.005160293489520457,0.016374975027971365
|
| 133 |
+
931,0.0014003052210915922,0.006057548485411315
|
| 134 |
+
932,0.0022625319342623688,0.009827343709087302
|
| 135 |
+
933,0.0015242896197079703,0.00821868161145458
|
| 136 |
+
934,0.001033083962603733,0.007102402629403483
|
| 137 |
+
935,0.001420422999209446,0.0060663995460390014
|
| 138 |
+
936,0.0009671579441707368,0.011546146828978348
|
| 139 |
+
937,0.0012568418093653058,0.00954057253659006
|
| 140 |
+
938,0.0019279405865385943,0.008715780498962775
|
| 141 |
+
939,0.004221112616735485,0.018773587824957756
|
| 142 |
+
940,0.0009647876962190149,0.007381329255001716
|
| 143 |
+
941,0.0007270810859020719,0.008070871146743775
|
| 144 |
+
942,0.0015694417270356253,0.01053232539653554
|
| 145 |
+
943,0.0012707776046804089,0.01294442406153623
|
| 146 |
+
944,0.004469268902409168,0.01791724149430663
|
| 147 |
+
945,0.0009714431061166003,0.009614952188550357
|
| 148 |
+
946,0.0014078634321375632,0.00938219391916491
|
| 149 |
+
947,0.0009141998010265314,0.009410290158824807
|
| 150 |
+
948,0.002605327225377241,0.01058111719026333
|
| 151 |
+
949,0.0009009842271187858,0.006988902258785145
|
| 152 |
+
950,0.0019418328237304457,0.01089337040143236
|
| 153 |
+
951,0.001839604033349563,0.008286063786727929
|
| 154 |
+
952,0.0012464553272033385,0.005839170559141925
|
| 155 |
+
953,0.0016172507856935163,0.007244302616848377
|
| 156 |
+
954,0.0013819677906637625,0.005714478430086377
|
| 157 |
+
955,0.0013170377048367344,0.009037444084165236
|
| 158 |
+
956,0.0010936816214972879,0.008607176858602072
|
| 159 |
+
957,0.0012824458019790733,0.009049715234738824
|
| 160 |
+
958,0.0007623849167848237,0.007785652530933292
|
| 161 |
+
959,0.0012166669716038524,0.007187985134346414
|
| 162 |
+
960,0.0015404606598713488,0.010387211792061974
|
| 163 |
+
961,0.0009880663253630198,0.008116852182480316
|
| 164 |
+
962,0.0012553970757991468,0.00845053892089981
|
| 165 |
+
963,0.000702529926807744,0.00587484272041473
|
| 166 |
+
964,0.0032501025434846158,0.011024587248372082
|
| 167 |
+
965,0.0019096188518064033,0.012373153384138191
|
| 168 |
+
966,0.0009021126397589328,0.009761469355660276
|
| 169 |
+
967,0.0013885713963696115,0.006857308533615614
|
| 170 |
+
968,0.0020933809454012015,0.00818737999326205
|
| 171 |
+
969,0.0015117437447880135,0.012136279960406511
|
| 172 |
+
970,0.0010510918757149453,0.004821037008264643
|
| 173 |
+
971,0.0025011940432743177,0.015027695090694041
|
| 174 |
+
972,0.003646446780417154,0.013519626547469004
|
| 175 |
+
973,0.0011198295822278475,0.007990426050346788
|
| 176 |
+
974,0.0014245268649068067,0.006864751006748974
|
| 177 |
+
975,0.0012871312827115028,0.007565817128583534
|
| 178 |
+
976,0.000886059705367791,0.005678057728736154
|
| 179 |
+
977,0.000955530971755569,0.009408212237748423
|
| 180 |
+
978,0.001069682752971588,0.0064699506263734
|
| 181 |
+
979,0.0010199505489099584,0.005575725699605437
|
| 182 |
+
980,0.0024029757571904214,0.009653973749049586
|
| 183 |
+
981,0.0007437867902824578,0.01704335584185775
|
| 184 |
+
982,0.004265423452123864,0.01586745992820094
|
| 185 |
+
983,0.002167071679798764,0.012244394124590935
|
| 186 |
+
984,0.000929962437772895,0.00893554650136493
|
| 187 |
+
985,0.002525234246333844,0.013478586959083477
|
| 188 |
+
986,0.0022838601217988913,0.011788063646633392
|
| 189 |
+
987,0.003402670753787411,0.013849056177897001
|
| 190 |
+
988,0.0010771493054250135,0.00789478518348717
|
| 191 |
+
989,0.001083582156428052,0.006593746009821497
|
| 192 |
+
990,0.0007881397517817494,0.011011966524056654
|
| 193 |
+
991,0.006703697063184707,0.023413969354371778
|
| 194 |
+
992,0.0012929017590327214,0.006987158099561493
|
| 195 |
+
993,0.0011427798511789333,0.008311275758676951
|
| 196 |
+
994,0.012773607533505092,0.035389758709408387
|
| 197 |
+
995,0.0009239440952716762,0.0157285345242972
|
| 198 |
+
996,0.0014329196350219976,0.012676394059059157
|
| 199 |
+
997,0.001202171970288141,0.006952197076749028
|
| 200 |
+
998,0.0017470733328887329,0.011172317814995052
|
| 201 |
+
999,0.001339933014064422,0.006860201160684589
|
results/test_predictions.npz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8c38551cc091d60d90c59f83bba370a1c525fcee717cfe7e10c29bdb9c306a20
|
| 3 |
+
size 1638857
|
results/training_history.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
results/training_summary.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"status": "completed",
|
| 3 |
+
"completed_epochs": 5000,
|
| 4 |
+
"batches_per_epoch": 19,
|
| 5 |
+
"training_samples_per_epoch": 600,
|
| 6 |
+
"best_epoch": 5000,
|
| 7 |
+
"best_validation_mse": 5.125773963908943e-06,
|
| 8 |
+
"selection_split": "validation[600:800]",
|
| 9 |
+
"test_used_for_selection": false,
|
| 10 |
+
"checkpoint": "weight/best_model.pth",
|
| 11 |
+
"data_sha256": "5c4f361d8318d4a00c61d4d57f3d49eeeb6643096b7ec356a059f0f95d014c71",
|
| 12 |
+
"elapsed_seconds": 8878.240034841001,
|
| 13 |
+
"slurm_job_id": "1060058"
|
| 14 |
+
}
|
results/verification.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"status": "passed",
|
| 3 |
+
"history_epochs": 5000,
|
| 4 |
+
"training_samples_per_epoch": 600,
|
| 5 |
+
"batches_per_epoch": 19,
|
| 6 |
+
"validation_epochs": [
|
| 7 |
+
1,
|
| 8 |
+
1001,
|
| 9 |
+
2001,
|
| 10 |
+
3001,
|
| 11 |
+
4001,
|
| 12 |
+
5000
|
| 13 |
+
],
|
| 14 |
+
"best_epoch": 5000,
|
| 15 |
+
"best_validation_mse": 5.125773963908943e-06,
|
| 16 |
+
"test_indices": [
|
| 17 |
+
800,
|
| 18 |
+
999
|
| 19 |
+
],
|
| 20 |
+
"test_samples": 200,
|
| 21 |
+
"test_shape": [
|
| 22 |
+
200,
|
| 23 |
+
11,
|
| 24 |
+
33
|
| 25 |
+
],
|
| 26 |
+
"latent_shape": [
|
| 27 |
+
200,
|
| 28 |
+
11,
|
| 29 |
+
4,
|
| 30 |
+
33
|
| 31 |
+
],
|
| 32 |
+
"recomputed_rmse_mean": 0.0017053971732615108,
|
| 33 |
+
"recomputed_rmse_std_ddof1": 0.0013320967174911497,
|
| 34 |
+
"global_rmse": 0.002161940973985019,
|
| 35 |
+
"checkpoint_sha256": "fd6917b63e596ad919bf7ad8504794039d303a3910925acac75f3fccd4ac08a8",
|
| 36 |
+
"figures_verified": 20,
|
| 37 |
+
"paper_rmse_mean": 0.00134,
|
| 38 |
+
"mean_rmse_gap_percent": 27.268445765784378,
|
| 39 |
+
"test_used_for_selection": false,
|
| 40 |
+
"source_data_sha256": "5c4f361d8318d4a00c61d4d57f3d49eeeb6643096b7ec356a059f0f95d014c71"
|
| 41 |
+
}
|
results/visualization_manifest.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"figures": [
|
| 3 |
+
"figures/test_case_800_fields.png",
|
| 4 |
+
"figures/test_case_800_fields.pdf",
|
| 5 |
+
"figures/test_case_800_profiles.png",
|
| 6 |
+
"figures/test_case_800_profiles.pdf",
|
| 7 |
+
"figures/test_case_880_fields.png",
|
| 8 |
+
"figures/test_case_880_fields.pdf",
|
| 9 |
+
"figures/test_case_880_profiles.png",
|
| 10 |
+
"figures/test_case_880_profiles.pdf",
|
| 11 |
+
"figures/test_case_960_fields.png",
|
| 12 |
+
"figures/test_case_960_fields.pdf",
|
| 13 |
+
"figures/test_case_960_profiles.png",
|
| 14 |
+
"figures/test_case_960_profiles.pdf",
|
| 15 |
+
"figures/test_case_994_fields.png",
|
| 16 |
+
"figures/test_case_994_fields.pdf",
|
| 17 |
+
"figures/test_case_994_profiles.png",
|
| 18 |
+
"figures/test_case_994_profiles.pdf",
|
| 19 |
+
"figures/training_curve.png",
|
| 20 |
+
"figures/training_curve.pdf",
|
| 21 |
+
"figures/test_error_summary.png",
|
| 22 |
+
"figures/test_error_summary.pdf"
|
| 23 |
+
],
|
| 24 |
+
"sample_ids": [
|
| 25 |
+
800,
|
| 26 |
+
880,
|
| 27 |
+
960,
|
| 28 |
+
994
|
| 29 |
+
],
|
| 30 |
+
"dpi": 200,
|
| 31 |
+
"test_shape": [
|
| 32 |
+
200,
|
| 33 |
+
11,
|
| 34 |
+
33
|
| 35 |
+
],
|
| 36 |
+
"training_epochs": 5000,
|
| 37 |
+
"colormaps": {
|
| 38 |
+
"field": "RdBu_r",
|
| 39 |
+
"absolute_error": "magma"
|
| 40 |
+
},
|
| 41 |
+
"note": "Validation selected weights. Test figures and metrics are post-training only."
|
| 42 |
+
}
|
scripts/common.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Shared configuration, raw Burgers data and metric contracts."""
|
| 2 |
+
import argparse
|
| 3 |
+
import hashlib
|
| 4 |
+
import json
|
| 5 |
+
import os
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
import random
|
| 8 |
+
import socket
|
| 9 |
+
import sys
|
| 10 |
+
|
| 11 |
+
import numpy as np
|
| 12 |
+
from scipy.io import loadmat
|
| 13 |
+
import torch
|
| 14 |
+
import yaml
|
| 15 |
+
|
| 16 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 17 |
+
if str(ROOT) not in sys.path:
|
| 18 |
+
sys.path.insert(0, str(ROOT))
|
| 19 |
+
from model.ronom import RONOM
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def parser(description):
|
| 23 |
+
p = argparse.ArgumentParser(description=description)
|
| 24 |
+
p.add_argument('--config', default='config/config.yaml')
|
| 25 |
+
return p
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def load_config(filename):
|
| 29 |
+
with open(filename, encoding='utf-8') as handle:
|
| 30 |
+
cfg = yaml.safe_load(handle)
|
| 31 |
+
return cfg
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def resolve(path):
|
| 35 |
+
path = Path(path).expanduser()
|
| 36 |
+
return path if path.is_absolute() else ROOT / path
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def save_json(path, value):
|
| 40 |
+
path = Path(path)
|
| 41 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 42 |
+
tmp = path.with_suffix(path.suffix + '.tmp')
|
| 43 |
+
tmp.write_text(json.dumps(value, indent=2, ensure_ascii=False, allow_nan=False) + '\n')
|
| 44 |
+
tmp.replace(path)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def update_run_manifest(cfg, **changes):
|
| 48 |
+
path = resolve(cfg['paths']['results']) / 'run_manifest.json'
|
| 49 |
+
if path.exists():
|
| 50 |
+
value = json.loads(path.read_text())
|
| 51 |
+
value.update(changes)
|
| 52 |
+
save_json(path, value)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def seed_everything(seed, threads=8):
|
| 56 |
+
random.seed(seed)
|
| 57 |
+
np.random.seed(seed)
|
| 58 |
+
torch.manual_seed(seed)
|
| 59 |
+
torch.set_num_threads(threads)
|
| 60 |
+
if torch.cuda.is_available():
|
| 61 |
+
torch.cuda.manual_seed_all(seed)
|
| 62 |
+
torch.backends.cudnn.benchmark = False
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def device_for(name):
|
| 66 |
+
if name in ('cuda', 'dcu'):
|
| 67 |
+
if not os.environ.get('SLURM_JOB_ID'):
|
| 68 |
+
raise RuntimeError('Accelerator execution must run inside a Slurm allocation.')
|
| 69 |
+
if not torch.cuda.is_available():
|
| 70 |
+
raise RuntimeError('The allocated compute node has no accessible accelerator.')
|
| 71 |
+
if torch.cuda.device_count() != 1:
|
| 72 |
+
raise RuntimeError('Expected one Slurm-visible DCU; check allocation device visibility. '
|
| 73 |
+
f'CUDA_VISIBLE_DEVICES={os.environ.get("CUDA_VISIBLE_DEVICES")} '
|
| 74 |
+
f'HIP_VISIBLE_DEVICES={os.environ.get("HIP_VISIBLE_DEVICES")} '
|
| 75 |
+
f'ROCR_VISIBLE_DEVICES={os.environ.get("ROCR_VISIBLE_DEVICES")} '
|
| 76 |
+
f'SLURM_JOB_GPUS={os.environ.get("SLURM_JOB_GPUS")}')
|
| 77 |
+
print(f'Slurm job={os.environ["SLURM_JOB_ID"]} host={socket.gethostname()} '
|
| 78 |
+
f'accelerator={torch.cuda.get_device_name()} count={torch.cuda.device_count()}', flush=True)
|
| 79 |
+
return torch.device('cuda')
|
| 80 |
+
return torch.device(name)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def load_splits(cfg, names):
|
| 84 |
+
"""Read only the supplied .mat. No downloads, augmentation, or normalization.
|
| 85 |
+
|
| 86 |
+
scipy decompresses the raw MATLAB array; only requested split tensors survive
|
| 87 |
+
this function. train.py requests train/validation and never constructs test.
|
| 88 |
+
"""
|
| 89 |
+
d = cfg['data']
|
| 90 |
+
path = resolve(d['directory']) / d['filename']
|
| 91 |
+
raw = loadmat(path, variable_names=['input', 'output', 'tspan', 'visc'])
|
| 92 |
+
if 'visc' not in raw or not np.isclose(float(raw['visc'].squeeze()), 0.01):
|
| 93 |
+
raise ValueError('Raw Burgers viscosity must equal 0.01')
|
| 94 |
+
initial, target = raw['input'], raw['output']
|
| 95 |
+
times = raw['tspan'].reshape(-1)
|
| 96 |
+
if initial.shape != (1000, 1025) or target.shape != (1000, 101, 1025):
|
| 97 |
+
raise ValueError(f'Unexpected raw Burgers shapes: {initial.shape}, {target.shape}')
|
| 98 |
+
if times.shape != (101,) or not np.allclose(times, np.linspace(0, 1, 101), atol=1e-12):
|
| 99 |
+
raise ValueError('Raw tspan does not match the paper interval [0,1].')
|
| 100 |
+
counts = (d['train_samples'], d['validation_samples'], d['test_samples'])
|
| 101 |
+
if counts != (600, 200, 200):
|
| 102 |
+
raise ValueError('Formal Burgers protocol requires exactly 600/200/200 samples.')
|
| 103 |
+
if (d['space_stride'], d['time_stride']) != (32, 10):
|
| 104 |
+
raise ValueError('This experiment requires strides 32 in space and 10 in time.')
|
| 105 |
+
spans = {'train': (0, 600), 'validation': (600, 800), 'test': (800, 1000)}
|
| 106 |
+
splits = {}
|
| 107 |
+
for name in names:
|
| 108 |
+
begin, end = spans[name]
|
| 109 |
+
u = np.ascontiguousarray(initial[begin:end, ::32], dtype=np.float32)
|
| 110 |
+
y = np.ascontiguousarray(target[begin:end, ::10, ::32], dtype=np.float32)
|
| 111 |
+
if not np.isfinite(u).all() or not np.isfinite(y).all():
|
| 112 |
+
raise ValueError(f'Nonfinite data in {name}')
|
| 113 |
+
mismatch = float(np.max(np.abs(u - y[:, 0])))
|
| 114 |
+
if mismatch > 1e-5:
|
| 115 |
+
raise ValueError(f'input and output(t=0) disagree in {name}: {mismatch}')
|
| 116 |
+
splits[name] = (torch.from_numpy(u), torch.from_numpy(y), np.arange(begin, end))
|
| 117 |
+
print(f'Data split={name} indices=[{begin},{end}) input={u.shape} '
|
| 118 |
+
f'target={y.shape} initial_consistency_max={mismatch:.3e}', flush=True)
|
| 119 |
+
return splits, torch.tensor(times[::10], dtype=torch.float32), path
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def digest(path):
|
| 123 |
+
sha = hashlib.sha256()
|
| 124 |
+
with open(path, 'rb') as stream:
|
| 125 |
+
for block in iter(lambda: stream.read(8 * 1024 * 1024), b''):
|
| 126 |
+
sha.update(block)
|
| 127 |
+
return sha.hexdigest()
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def build_model(cfg, device):
|
| 131 |
+
model = RONOM(**cfg['model']).to(device)
|
| 132 |
+
if cfg['training'].get('jit_flow', False):
|
| 133 |
+
model.latent_flow = torch.jit.script(model.latent_flow)
|
| 134 |
+
count = sum(p.numel() for p in model.parameters())
|
| 135 |
+
if count != 414217:
|
| 136 |
+
raise ValueError(f'Model parameter count {count} differs from paper Appendix B: 414217')
|
| 137 |
+
print(f'RONOM parameters={count}, latent=4x33=132', flush=True)
|
| 138 |
+
return model
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def checkpoint_save(path, value):
|
| 142 |
+
path = resolve(path)
|
| 143 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 144 |
+
tmp = path.with_suffix(path.suffix + '.tmp')
|
| 145 |
+
torch.save(value, tmp)
|
| 146 |
+
tmp.replace(path)
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def load_best(cfg, device):
|
| 150 |
+
path = resolve(cfg['paths']['checkpoint'])
|
| 151 |
+
checkpoint = torch.load(path, map_location='cpu', weights_only=False)
|
| 152 |
+
# Only architecture is compared: data/results paths never gate loading.
|
| 153 |
+
if checkpoint['model_config'] != cfg['model']:
|
| 154 |
+
raise ValueError('Model architecture in config differs from checkpoint.')
|
| 155 |
+
model = build_model(cfg, device)
|
| 156 |
+
model.load_state_dict(checkpoint['model_state_dict'], strict=True)
|
| 157 |
+
model.eval()
|
| 158 |
+
print(f'Loaded best checkpoint epoch={checkpoint["epoch"]} '
|
| 159 |
+
f'validation_mse={checkpoint["validation_mse"]:.9e}', flush=True)
|
| 160 |
+
return model, checkpoint, path
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def metrics(prediction, target):
|
| 164 |
+
err = prediction.double() - target.double()
|
| 165 |
+
mse_i = err.square().flatten(1).mean(1)
|
| 166 |
+
rmse_i = mse_i.sqrt()
|
| 167 |
+
relative = err.flatten(1).norm(dim=1) / target.double().flatten(1).norm(dim=1).clamp_min(1e-30)
|
| 168 |
+
return {
|
| 169 |
+
'samples': len(rmse_i),
|
| 170 |
+
'mse': mse_i.mean().item(),
|
| 171 |
+
'global_rmse': mse_i.mean().sqrt().item(),
|
| 172 |
+
'trajectory_rmse_mean': rmse_i.mean().item(),
|
| 173 |
+
'trajectory_rmse_std_ddof1': rmse_i.std(correction=1).item() if len(rmse_i) > 1 else 0.,
|
| 174 |
+
'relative_l2_mean': relative.mean().item(),
|
| 175 |
+
'rmse_by_time': err.square().mean(dim=(0, 2)).sqrt().cpu().tolist(),
|
| 176 |
+
}, rmse_i.cpu().numpy(), relative.cpu().numpy()
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
@torch.no_grad()
|
| 180 |
+
def evaluate(model, initial, target, times, batch_size, collect_latent=False):
|
| 181 |
+
predictions, latents = [], []
|
| 182 |
+
model.eval()
|
| 183 |
+
for begin in range(0, len(initial), batch_size):
|
| 184 |
+
output = model(initial[begin:begin + batch_size], times, return_latent=collect_latent)
|
| 185 |
+
if collect_latent:
|
| 186 |
+
output, zt = output
|
| 187 |
+
latents.append(zt.cpu())
|
| 188 |
+
predictions.append(output.cpu())
|
| 189 |
+
predicted = torch.cat(predictions)
|
| 190 |
+
if not torch.isfinite(predicted).all():
|
| 191 |
+
raise RuntimeError('Evaluation produced nonfinite predictions')
|
| 192 |
+
summary, per_sample, relative = metrics(predicted, target.cpu())
|
| 193 |
+
return summary, predicted, per_sample, relative, torch.cat(latents) if latents else None
|
scripts/inference.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Load validation-selected weights and evaluate the last 200 independent cases."""
|
| 2 |
+
import csv
|
| 3 |
+
import os
|
| 4 |
+
import time
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
from common import (parser, load_config, seed_everything, device_for, resolve,
|
| 8 |
+
load_splits, load_best, evaluate, save_json, digest, update_run_manifest)
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def main():
|
| 12 |
+
args = parser(__doc__).parse_args()
|
| 13 |
+
cfg = load_config(args.config)
|
| 14 |
+
seed_everything(cfg['seed'], cfg['training']['num_threads'])
|
| 15 |
+
device = device_for(cfg['inference']['device'])
|
| 16 |
+
model, checkpoint, checkpoint_path = load_best(cfg, device)
|
| 17 |
+
splits, times, data_path = load_splits(cfg, ['test'])
|
| 18 |
+
if checkpoint['data_sha256'] != digest(data_path):
|
| 19 |
+
raise ValueError('Raw test data checksum differs from the training source')
|
| 20 |
+
u, y, ids = splits['test']
|
| 21 |
+
start = time.monotonic()
|
| 22 |
+
summary, pred, rmse_i, relative_i, latent = evaluate(
|
| 23 |
+
model, u.to(device), y.to(device), times, cfg['inference']['batch_size'], True)
|
| 24 |
+
summary.update(split='independent_test', indices=[int(ids[0]), int(ids[-1])],
|
| 25 |
+
checkpoint_epoch=checkpoint['epoch'], checkpoint_sha256=digest(checkpoint_path),
|
| 26 |
+
checkpoint_validation_mse=checkpoint['validation_mse'],
|
| 27 |
+
selection_split=checkpoint['selection_split'],
|
| 28 |
+
paper_rmse_mean=0.00134, paper_rmse_std=0.00162,
|
| 29 |
+
elapsed_seconds=time.monotonic() - start,
|
| 30 |
+
slurm_job_id=os.environ.get('SLURM_JOB_ID'))
|
| 31 |
+
result_dir = resolve(cfg['paths']['results'])
|
| 32 |
+
result_dir.mkdir(parents=True, exist_ok=True)
|
| 33 |
+
np.savez_compressed(result_dir / 'test_predictions.npz', prediction=pred.numpy(),
|
| 34 |
+
reference=y.numpy(), initial=u.numpy(), latent=latent.numpy(),
|
| 35 |
+
sample_ids=ids, x=np.linspace(0, 1, 33), times=times.numpy())
|
| 36 |
+
with (result_dir / 'test_per_sample.csv').open('w', newline='') as file:
|
| 37 |
+
writer = csv.writer(file)
|
| 38 |
+
writer.writerow(['sample_id', 'rmse', 'relative_l2'])
|
| 39 |
+
writer.writerows(zip(ids, rmse_i, relative_i))
|
| 40 |
+
save_json(result_dir / 'test_metrics.json', summary)
|
| 41 |
+
update_run_manifest(cfg, status='inference_completed', test_samples=len(ids),
|
| 42 |
+
test_indices=[int(ids[0]), int(ids[-1])],
|
| 43 |
+
test_metrics_file='test_metrics.json')
|
| 44 |
+
print(f'INDEPENDENT TEST n={len(ids)} RMSE mean={summary["trajectory_rmse_mean"]:.9e} '
|
| 45 |
+
f'std(ddof1)={summary["trajectory_rmse_std_ddof1"]:.9e} '
|
| 46 |
+
f'global_RMSE={summary["global_rmse"]:.9e} '
|
| 47 |
+
f'relative_L2={summary["relative_l2_mean"]:.9e}', flush=True)
|
| 48 |
+
print(f'Table 1(a) units 1e-3: {summary["trajectory_rmse_mean"] * 1000:.4f} '
|
| 49 |
+
f'({summary["trajectory_rmse_std_ddof1"] * 1000:.4f}); paper 1.34 (1.62)', flush=True)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
if __name__ == '__main__':
|
| 53 |
+
main()
|
scripts/result.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Visualize independent Burgers predictions, errors and the actual training log."""
|
| 2 |
+
import csv
|
| 3 |
+
import json
|
| 4 |
+
|
| 5 |
+
import matplotlib
|
| 6 |
+
matplotlib.use('Agg')
|
| 7 |
+
import matplotlib.pyplot as plt
|
| 8 |
+
import numpy as np
|
| 9 |
+
from common import parser, load_config, resolve, save_json, update_run_manifest
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def main():
|
| 13 |
+
args = parser(__doc__).parse_args()
|
| 14 |
+
cfg = load_config(args.config)
|
| 15 |
+
folder = resolve(cfg['paths']['results'])
|
| 16 |
+
data = np.load(folder / 'test_predictions.npz')
|
| 17 |
+
pred, truth, times, x = (data[k] for k in ['prediction', 'reference', 'times', 'x'])
|
| 18 |
+
if pred.shape != (200, 11, 33) or not np.isfinite(pred).all():
|
| 19 |
+
raise ValueError('Invalid independent test predictions')
|
| 20 |
+
summary = json.loads((folder / 'test_metrics.json').read_text())
|
| 21 |
+
rmse = np.sqrt(np.mean((pred - truth)**2, axis=(1, 2)))
|
| 22 |
+
plot_dir = folder / 'figures'
|
| 23 |
+
plot_dir.mkdir(exist_ok=True)
|
| 24 |
+
dpi = cfg['visualization']['dpi']
|
| 25 |
+
files = []
|
| 26 |
+
|
| 27 |
+
def save(fig, name):
|
| 28 |
+
for suffix in ['png', 'pdf']:
|
| 29 |
+
destination = plot_dir / f'{name}.{suffix}'
|
| 30 |
+
fig.savefig(destination, dpi=dpi, bbox_inches='tight')
|
| 31 |
+
files.append(str(destination.relative_to(folder)))
|
| 32 |
+
plt.close(fig)
|
| 33 |
+
|
| 34 |
+
selected = list(dict.fromkeys(cfg['visualization']['sample_indices'] + [int(rmse.argmax())]))
|
| 35 |
+
for i in selected:
|
| 36 |
+
uid = int(data['sample_ids'][i])
|
| 37 |
+
fig, axes = plt.subplots(1, 3, figsize=(12, 3.5), constrained_layout=True)
|
| 38 |
+
vlim = max(float(np.max(np.abs(truth[i]))), float(np.max(np.abs(pred[i]))))
|
| 39 |
+
for ax, field, title in zip(axes[:2], [truth[i], pred[i]], ['Reference', 'RONOM']):
|
| 40 |
+
im = ax.pcolormesh(times, x, field.T, shading='nearest', cmap='RdBu_r',
|
| 41 |
+
vmin=-vlim, vmax=vlim)
|
| 42 |
+
ax.set(title=title, xlabel='t', ylabel='x')
|
| 43 |
+
fig.colorbar(im, ax=axes[:2], label='u(x,t)')
|
| 44 |
+
im = axes[2].pcolormesh(times, x, np.abs(pred[i] - truth[i]).T,
|
| 45 |
+
shading='nearest', cmap='magma')
|
| 46 |
+
axes[2].set(title='Absolute error', xlabel='t', ylabel='x')
|
| 47 |
+
fig.colorbar(im, ax=axes[2], label='|prediction - reference|')
|
| 48 |
+
fig.suptitle(f'Independent test case {uid}, trajectory RMSE = {rmse[i]:.3e}')
|
| 49 |
+
save(fig, f'test_case_{uid}_fields')
|
| 50 |
+
fig, axes = plt.subplots(1, 3, figsize=(12, 3.2), constrained_layout=True)
|
| 51 |
+
for ax, j in zip(axes, [0, 5, 10]):
|
| 52 |
+
ax.plot(x, truth[i, j], color='black', label='Reference')
|
| 53 |
+
ax.plot(x, pred[i, j], '--o', ms=3, color='tab:blue', label='RONOM')
|
| 54 |
+
ax.set(title=f't = {times[j]:.1f}', xlabel='x', ylabel='u(x,t)')
|
| 55 |
+
ax.grid(alpha=.2)
|
| 56 |
+
axes[0].legend()
|
| 57 |
+
fig.suptitle(f'Independent test case {uid}')
|
| 58 |
+
save(fig, f'test_case_{uid}_profiles')
|
| 59 |
+
|
| 60 |
+
epochs = np.array([], dtype=int)
|
| 61 |
+
if (folder / 'training_history.csv').exists():
|
| 62 |
+
with (folder / 'training_history.csv').open() as f:
|
| 63 |
+
history = list(csv.DictReader(f))
|
| 64 |
+
epochs = np.array([int(r['epoch']) for r in history])
|
| 65 |
+
if len(epochs) != 5000 or not np.array_equal(epochs, np.arange(1, 5001)):
|
| 66 |
+
raise ValueError('Expected one complete formal training history, epochs 1 through 5000')
|
| 67 |
+
val_rows = [r for r in history if r['validation_mse']]
|
| 68 |
+
fig, ax = plt.subplots(figsize=(7, 4))
|
| 69 |
+
ax.semilogy(epochs, [float(r['train_mse']) for r in history], label='Training MSE')
|
| 70 |
+
ax.semilogy([int(r['epoch']) for r in val_rows],
|
| 71 |
+
[float(r['validation_mse']) for r in val_rows], 'o-', label='Validation MSE')
|
| 72 |
+
ax.axvline(summary['checkpoint_epoch'], color='grey', ls=':', label='Selected epoch')
|
| 73 |
+
ax.set(xlabel='Epoch', ylabel='MSE', title='Formal 5000-epoch training')
|
| 74 |
+
ax.legend(); ax.grid(alpha=.2)
|
| 75 |
+
save(fig, 'training_curve')
|
| 76 |
+
else:
|
| 77 |
+
print('Training history is absent in this results directory; generating independent test figures.', flush=True)
|
| 78 |
+
fig, axes = plt.subplots(1, 2, figsize=(10, 3.6), constrained_layout=True)
|
| 79 |
+
axes[0].plot(times, summary['rmse_by_time'], 'o-')
|
| 80 |
+
axes[0].set(xlabel='t', ylabel='RMSE across independent test cases and x', title='Error by time')
|
| 81 |
+
axes[1].hist(rmse * 1000, bins=25, edgecolor='white')
|
| 82 |
+
axes[1].set(xlabel='Trajectory RMSE (× 10⁻³)', ylabel='Test cases', title='200 independent trajectories')
|
| 83 |
+
save(fig, 'test_error_summary')
|
| 84 |
+
comparison = folder / 'table1a_comparison.csv'
|
| 85 |
+
with comparison.open('w', newline='') as f:
|
| 86 |
+
writer = csv.writer(f)
|
| 87 |
+
writer.writerow(['source', 'split', 'samples', 'rmse_mean', 'rmse_std_ddof1', 'units'])
|
| 88 |
+
writer.writerow(['paper_Table1a_RONOM', 'paper_test', 200, .00134, .00162, 'raw'])
|
| 89 |
+
writer.writerow(['this_run', 'independent_test_800_999', 200,
|
| 90 |
+
summary['trajectory_rmse_mean'], summary['trajectory_rmse_std_ddof1'], 'raw'])
|
| 91 |
+
save_json(folder / 'visualization_manifest.json', {
|
| 92 |
+
'figures': files, 'sample_ids': [int(data['sample_ids'][i]) for i in selected],
|
| 93 |
+
'dpi': dpi, 'test_shape': list(pred.shape), 'training_epochs': len(epochs) if len(epochs) else None,
|
| 94 |
+
'colormaps': {'field': 'RdBu_r', 'absolute_error': 'magma'},
|
| 95 |
+
'note': 'Validation selected weights. Test figures and metrics are post-training only.'})
|
| 96 |
+
update_run_manifest(cfg, status='completed', figure_files=len(files),
|
| 97 |
+
visualization_manifest='visualization_manifest.json')
|
| 98 |
+
print(f'VISUALIZATION COMPLETE: {len(files)} figures in {plot_dir}', flush=True)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
if __name__ == '__main__':
|
| 102 |
+
main()
|
scripts/train.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Full 5000-epoch Burgers input-generalization experiment; validation selects best."""
|
| 2 |
+
import csv
|
| 3 |
+
import os
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
import time
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
from torch.utils.data import DataLoader, TensorDataset
|
| 9 |
+
from common import (parser, load_config, seed_everything, device_for, load_splits,
|
| 10 |
+
resolve, save_json, digest, build_model, checkpoint_save, evaluate,
|
| 11 |
+
update_run_manifest)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def main():
|
| 15 |
+
p = parser(__doc__)
|
| 16 |
+
p.add_argument('--resume', default=None, help='Resume a locally generated last checkpoint')
|
| 17 |
+
args = p.parse_args()
|
| 18 |
+
cfg = load_config(args.config)
|
| 19 |
+
tr = cfg['training']
|
| 20 |
+
if tr['epochs'] != 5000 or tr['batch_size'] != 32:
|
| 21 |
+
raise ValueError('Formal experiment requires 5000 epochs and batch size 32')
|
| 22 |
+
seed_everything(cfg['seed'], tr['num_threads'])
|
| 23 |
+
device = device_for(tr['device'])
|
| 24 |
+
result_dir = resolve(cfg['paths']['results'])
|
| 25 |
+
result_dir.mkdir(parents=True, exist_ok=True)
|
| 26 |
+
history_path = result_dir / 'training_history.csv'
|
| 27 |
+
if history_path.exists() and not args.resume:
|
| 28 |
+
raise FileExistsError('Training history already exists. Use --resume to continue this run.')
|
| 29 |
+
splits, times, data_path = load_splits(cfg, ['train', 'validation'])
|
| 30 |
+
train_u, train_y, train_ids = splits['train']
|
| 31 |
+
val_u, val_y, val_ids = splits['validation']
|
| 32 |
+
train_u, train_y = train_u.to(device), train_y.to(device)
|
| 33 |
+
val_u, val_y = val_u.to(device), val_y.to(device)
|
| 34 |
+
loader = DataLoader(TensorDataset(train_u, train_y), batch_size=32,
|
| 35 |
+
shuffle=True, drop_last=False, num_workers=0)
|
| 36 |
+
model = build_model(cfg, device)
|
| 37 |
+
optimizer = torch.optim.Adam(model.parameters(), lr=tr['learning_rate'],
|
| 38 |
+
weight_decay=tr['weight_decay'])
|
| 39 |
+
scheduler = torch.optim.lr_scheduler.MultiplicativeLR(
|
| 40 |
+
optimizer, lr_lambda=lambda _: tr['scheduler_gamma'])
|
| 41 |
+
start_epoch, best_mse, best_epoch = 0, float('inf'), None
|
| 42 |
+
if args.resume:
|
| 43 |
+
old = torch.load(resolve(args.resume), map_location='cpu', weights_only=False)
|
| 44 |
+
if old['model_config'] != cfg['model'] or old['data_sha256'] != digest(data_path):
|
| 45 |
+
raise ValueError('Resume checkpoint architecture or source data differs')
|
| 46 |
+
model.load_state_dict(old['model_state_dict'])
|
| 47 |
+
optimizer.load_state_dict(old['optimizer_state_dict'])
|
| 48 |
+
scheduler.load_state_dict(old['scheduler_state_dict'])
|
| 49 |
+
torch.set_rng_state(old['torch_rng'])
|
| 50 |
+
torch.cuda.set_rng_state_all(old['cuda_rng'])
|
| 51 |
+
start_epoch, best_mse, best_epoch = old['epoch'], old['best_validation_mse'], old['best_epoch']
|
| 52 |
+
# Logs may contain epochs beyond the last atomic checkpoint after an
|
| 53 |
+
# interrupted allocation. Replay those epochs from saved RNG/optimizer
|
| 54 |
+
# state, keeping exactly one history row for every completed epoch.
|
| 55 |
+
with history_path.open(newline='') as previous:
|
| 56 |
+
reader = csv.DictReader(previous)
|
| 57 |
+
fields = reader.fieldnames
|
| 58 |
+
retained = [r for r in reader if int(r['epoch']) <= start_epoch]
|
| 59 |
+
if [int(r['epoch']) for r in retained] != list(range(1, start_epoch + 1)):
|
| 60 |
+
raise ValueError('Resume history is incomplete before the checkpoint epoch')
|
| 61 |
+
temporary_history = history_path.with_suffix('.csv.tmp')
|
| 62 |
+
with temporary_history.open('w', newline='') as previous:
|
| 63 |
+
writer = csv.DictWriter(previous, fieldnames=fields)
|
| 64 |
+
writer.writeheader()
|
| 65 |
+
writer.writerows(retained)
|
| 66 |
+
temporary_history.replace(history_path)
|
| 67 |
+
print(f'Resuming after completed epoch {start_epoch}', flush=True)
|
| 68 |
+
data_sha = digest(data_path)
|
| 69 |
+
save_json(result_dir / 'run_manifest.json', {
|
| 70 |
+
'status': 'running', 'config': cfg, 'data_sha256': data_sha,
|
| 71 |
+
'slurm_job_id': os.environ.get('SLURM_JOB_ID'), 'torch_version': torch.__version__,
|
| 72 |
+
'train_indices': train_ids.tolist(), 'validation_indices': val_ids.tolist(),
|
| 73 |
+
'test_indices_reserved': [800, 1000], 'times': times.tolist(),
|
| 74 |
+
'model_parameters': sum(v.numel() for v in model.parameters()),
|
| 75 |
+
'test_used_for_selection': False, 'precision': 'float32'})
|
| 76 |
+
header = ['epoch', 'train_half_mse', 'train_mse', 'last_batch_half_mse', 'learning_rate',
|
| 77 |
+
'validation_mse', 'validation_rmse_mean', 'validation_rmse_std', 'elapsed_seconds']
|
| 78 |
+
started = time.monotonic()
|
| 79 |
+
with history_path.open('a' if args.resume else 'w', newline='', buffering=1) as handle:
|
| 80 |
+
writer = csv.DictWriter(handle, fieldnames=header)
|
| 81 |
+
if not args.resume:
|
| 82 |
+
writer.writeheader()
|
| 83 |
+
for epoch_index in range(start_epoch, tr['epochs']):
|
| 84 |
+
epoch = epoch_index + 1
|
| 85 |
+
model.train()
|
| 86 |
+
total, seen, last_loss = 0., 0, None
|
| 87 |
+
lr_used = optimizer.param_groups[0]['lr']
|
| 88 |
+
for u, target in loader:
|
| 89 |
+
optimizer.zero_grad(set_to_none=True)
|
| 90 |
+
predicted = model(u, times)
|
| 91 |
+
loss = 0.5 * (predicted - target).square().mean()
|
| 92 |
+
if not torch.isfinite(loss):
|
| 93 |
+
raise RuntimeError(f'Nonfinite training loss at epoch {epoch}')
|
| 94 |
+
loss.backward()
|
| 95 |
+
optimizer.step()
|
| 96 |
+
last_loss = float(loss.detach())
|
| 97 |
+
total += last_loss * len(u)
|
| 98 |
+
seen += len(u)
|
| 99 |
+
if seen != 600:
|
| 100 |
+
raise RuntimeError(f'Only {seen} training samples processed')
|
| 101 |
+
if epoch_index > 0 and epoch_index % tr['scheduler_frequency'] == 0:
|
| 102 |
+
scheduler.step()
|
| 103 |
+
row = {'epoch': epoch, 'train_half_mse': total / seen,
|
| 104 |
+
'train_mse': 2 * total / seen, 'last_batch_half_mse': last_loss,
|
| 105 |
+
'learning_rate': lr_used, 'elapsed_seconds': time.monotonic() - started}
|
| 106 |
+
val_summary = None
|
| 107 |
+
if epoch_index % tr['validation_frequency'] == 0 or epoch == tr['epochs']:
|
| 108 |
+
val_summary, *_ = evaluate(model, val_u, val_y, times, tr['batch_size'])
|
| 109 |
+
row.update(validation_mse=val_summary['mse'],
|
| 110 |
+
validation_rmse_mean=val_summary['trajectory_rmse_mean'],
|
| 111 |
+
validation_rmse_std=val_summary['trajectory_rmse_std_ddof1'])
|
| 112 |
+
if val_summary['mse'] < best_mse:
|
| 113 |
+
best_mse, best_epoch = val_summary['mse'], epoch
|
| 114 |
+
checkpoint_save(cfg['paths']['checkpoint'], {
|
| 115 |
+
'format_version': 1, 'model_config': cfg['model'],
|
| 116 |
+
'model_state_dict': model.state_dict(), 'epoch': epoch,
|
| 117 |
+
'validation_mse': best_mse, 'validation_metrics': val_summary,
|
| 118 |
+
'selection_split': 'validation[600:800]',
|
| 119 |
+
'data_sha256': data_sha, 'seed': cfg['seed']})
|
| 120 |
+
save_json(result_dir / 'best_validation_metrics.json',
|
| 121 |
+
{'epoch': epoch, 'split': 'validation', **val_summary})
|
| 122 |
+
print(f'VALIDATION epoch={epoch} mse={val_summary["mse"]:.9e} '
|
| 123 |
+
f'RMSE_mean={val_summary["trajectory_rmse_mean"]:.9e} '
|
| 124 |
+
f'RMSE_std={val_summary["trajectory_rmse_std_ddof1"]:.9e} '
|
| 125 |
+
f'best_epoch={best_epoch}', flush=True)
|
| 126 |
+
writer.writerow(row)
|
| 127 |
+
print(f'epoch={epoch:04d}/5000 train_loss(half_MSE)={total / seen:.9e} '
|
| 128 |
+
f'train_MSE={2 * total / seen:.9e} lr={lr_used:.6e} '
|
| 129 |
+
f'samples={seen} elapsed={row["elapsed_seconds"]:.1f}s', flush=True)
|
| 130 |
+
if epoch % tr['checkpoint_frequency'] == 0 or epoch == tr['epochs']:
|
| 131 |
+
checkpoint_save(cfg['paths']['last_checkpoint'], {
|
| 132 |
+
'model_config': cfg['model'], 'model_state_dict': model.state_dict(),
|
| 133 |
+
'optimizer_state_dict': optimizer.state_dict(),
|
| 134 |
+
'scheduler_state_dict': scheduler.state_dict(), 'epoch': epoch,
|
| 135 |
+
'best_validation_mse': best_mse, 'best_epoch': best_epoch,
|
| 136 |
+
'torch_rng': torch.get_rng_state(), 'cuda_rng': torch.cuda.get_rng_state_all(),
|
| 137 |
+
'data_sha256': data_sha})
|
| 138 |
+
summary = {'status': 'completed', 'completed_epochs': tr['epochs'],
|
| 139 |
+
'batches_per_epoch': len(loader), 'training_samples_per_epoch': 600,
|
| 140 |
+
'best_epoch': best_epoch, 'best_validation_mse': best_mse,
|
| 141 |
+
'selection_split': 'validation[600:800]', 'test_used_for_selection': False,
|
| 142 |
+
'checkpoint': str(cfg['paths']['checkpoint']), 'data_sha256': data_sha,
|
| 143 |
+
'elapsed_seconds': time.monotonic() - started,
|
| 144 |
+
'slurm_job_id': os.environ.get('SLURM_JOB_ID')}
|
| 145 |
+
save_json(result_dir / 'training_summary.json', summary)
|
| 146 |
+
update_run_manifest(cfg, status='training_completed', completed_epochs=tr['epochs'],
|
| 147 |
+
best_epoch=best_epoch, best_validation_mse=best_mse)
|
| 148 |
+
print('FORMAL TRAINING COMPLETE', summary, flush=True)
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
if __name__ == '__main__':
|
| 152 |
+
main()
|
weight/best_model.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fd6917b63e596ad919bf7ad8504794039d303a3910925acac75f3fccd4ac08a8
|
| 3 |
+
size 1681430
|