yushuang88 commited on
Commit
e456924
·
verified ·
1 Parent(s): 032ff90

Upload folder using huggingface_hub

Browse files
Files changed (43) hide show
  1. .gitattributes +7 -0
  2. README.md +37 -0
  3. config.json +10 -0
  4. config/config.yaml +42 -0
  5. model/__init__.py +1 -0
  6. model/ronom.py +165 -0
  7. results/best_validation_metrics.json +23 -0
  8. results/data_provenance.json +42 -0
  9. results/figures/test_case_800_fields.pdf +0 -0
  10. results/figures/test_case_800_fields.png +0 -0
  11. results/figures/test_case_800_profiles.pdf +0 -0
  12. results/figures/test_case_800_profiles.png +3 -0
  13. results/figures/test_case_880_fields.pdf +0 -0
  14. results/figures/test_case_880_fields.png +0 -0
  15. results/figures/test_case_880_profiles.pdf +0 -0
  16. results/figures/test_case_880_profiles.png +3 -0
  17. results/figures/test_case_960_fields.pdf +0 -0
  18. results/figures/test_case_960_fields.png +0 -0
  19. results/figures/test_case_960_profiles.pdf +0 -0
  20. results/figures/test_case_960_profiles.png +3 -0
  21. results/figures/test_case_994_fields.pdf +0 -0
  22. results/figures/test_case_994_fields.png +3 -0
  23. results/figures/test_case_994_profiles.pdf +0 -0
  24. results/figures/test_case_994_profiles.png +3 -0
  25. results/figures/test_error_summary.pdf +0 -0
  26. results/figures/test_error_summary.png +3 -0
  27. results/figures/training_curve.pdf +0 -0
  28. results/figures/training_curve.png +3 -0
  29. results/reproduction_summary.json +95 -0
  30. results/runtime_result.json +28 -0
  31. results/table1a_comparison.csv +3 -0
  32. results/test_metrics.json +34 -0
  33. results/test_per_sample.csv +201 -0
  34. results/test_predictions.npz +3 -0
  35. results/training_history.csv +0 -0
  36. results/training_summary.json +14 -0
  37. results/verification.json +41 -0
  38. results/visualization_manifest.json +42 -0
  39. scripts/common.py +193 -0
  40. scripts/inference.py +53 -0
  41. scripts/result.py +102 -0
  42. scripts/train.py +152 -0
  43. 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

  • SHA256: bb219ed2a7e127a330a7e248a0aab497516e6c9556f42f692d1368eaa01696a3
  • Pointer size: 131 Bytes
  • Size of remote file: 120 kB
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

  • SHA256: b9269498da5ced70eb4c1a73333aeb51cab0029c174f8146af995e99b8d39828
  • Pointer size: 131 Bytes
  • Size of remote file: 123 kB
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

  • SHA256: 8ec709256598851bec8897a6b024d404fb69f5ace69949d86227129acb8a8399
  • Pointer size: 131 Bytes
  • Size of remote file: 131 kB
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

  • SHA256: aca1732b09178975995304bc30e89eb6402c32f3b41263430bd5728e296162a7
  • Pointer size: 131 Bytes
  • Size of remote file: 100 kB
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

  • SHA256: 2ebb9f286b9e48f9d89746abd49798f0202b6d8a7a01b9739a91d4ede6f2181f
  • Pointer size: 131 Bytes
  • Size of remote file: 126 kB
results/figures/test_error_summary.pdf ADDED
Binary file (16.9 kB). View file
 
results/figures/test_error_summary.png ADDED

Git LFS Details

  • SHA256: ee84af9bb12ebfa4e89c0578c510b0efb72a90fff4a09eaf21afd17de4def3a7
  • Pointer size: 131 Bytes
  • Size of remote file: 114 kB
results/figures/training_curve.pdf ADDED
Binary file (42.8 kB). View file
 
results/figures/training_curve.png ADDED

Git LFS Details

  • SHA256: d28a039e5e49bc746f5a0b3639d2fd9d567124619da50e56888755e1d5aefac8
  • Pointer size: 131 Bytes
  • Size of remote file: 120 kB
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