Upload folder using huggingface_hub
Browse files- .gitattributes +24 -10
- README.md +44 -30
- conf/config.yaml +42 -48
- configuration.json +1 -1
- model/__init__.py +2 -2
- model/pinnsformer.py +146 -0
- result/.gitkeep +1 -0
- scripts/common.py +65 -302
- scripts/fake_data.py +20 -48
- scripts/inference.py +60 -58
- scripts/result.py +58 -37
- scripts/train.py +144 -115
.gitattributes
CHANGED
|
@@ -1,35 +1,49 @@
|
|
| 1 |
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 4 |
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
*.xz 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
|
|
|
|
|
|
|
|
|
| 1 |
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bin.* filter=lfs diff=lfs merge=lfs -text
|
| 5 |
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 6 |
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 11 |
*.model filter=lfs diff=lfs merge=lfs -text
|
| 12 |
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
| 13 |
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 14 |
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 15 |
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 16 |
*.pb filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
| 17 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 18 |
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 19 |
*.rar filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 20 |
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
|
|
|
|
| 22 |
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 23 |
*.tgz filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 24 |
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 25 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
*.zstandard filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.db* filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.ark* filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
**/*ckpt*data* filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
**/*ckpt*.meta filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
**/*ckpt*.index filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*.gguf* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.ggml filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
*.llamafile* filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
*.pt2 filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
|
| 45 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 46 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 47 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 48 |
+
|
| 49 |
+
navier_stokes/cylinder_nektar_wake.mat filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -4,37 +4,38 @@ language:
|
|
| 4 |
- en
|
| 5 |
tags:
|
| 6 |
- OneScience
|
| 7 |
-
-
|
| 8 |
-
-
|
| 9 |
-
-
|
| 10 |
-
- PDEBench
|
| 11 |
frameworks: PyTorch
|
| 12 |
-
|
| 13 |
-
datasets:
|
| 14 |
-
- OneScience/pdenneval
|
| 15 |
---
|
| 16 |
<p align="center">
|
| 17 |
<strong>
|
| 18 |
-
<span style="font-size: 30px;">
|
| 19 |
</strong>
|
| 20 |
</p>
|
| 21 |
|
| 22 |
# Model Overview
|
| 23 |
-
PDENNEval is a comprehensive benchmark developed by researchers at Sun Yat-sen University for evaluating neural-network methods for solving partial differential equations. It enables systematic comparison of function-learning and operator-learning approaches across a broad range of PDE tasks.
|
| 24 |
|
| 25 |
-
|
|
|
|
|
|
|
| 26 |
|
| 27 |
# Model Description
|
| 28 |
-
PDENNEval provides a unified framework for evaluating neural-network PDE solvers. It contains 16 datasets generated with high-accuracy conventional scientific computing methods, covering 19 PDE problem classes from fluid dynamics, materials science, finance, electromagnetics, and other domains. The benchmark evaluates the solution accuracy, computational efficiency, and robustness of 12 neural-network methods.
|
| 29 |
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
| Use Case | Description |
|
| 33 |
-
| --- | --- |
|
| 34 |
-
|
|
| 35 |
-
|
|
|
|
|
| 36 |
| ModelScope/OneCode execution | Download the standalone model package, install its dependencies, and run the provided scripts |
|
| 37 |
-
|
| 38 |
|
| 39 |
# Usage
|
| 40 |
|
|
@@ -48,16 +49,15 @@ Use the online OneCode environment for an intelligent, one-click AI for Science
|
|
| 48 |
|
| 49 |
**Hardware Requirements**
|
| 50 |
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
|
| 56 |
### Download the Model Package
|
| 57 |
|
| 58 |
```bash
|
| 59 |
-
modelscope download --model OneScience/
|
| 60 |
-
cd
|
| 61 |
```
|
| 62 |
|
| 63 |
### Set Up the Runtime Environment
|
|
@@ -84,20 +84,30 @@ pip install onescience[cfd-gpu] -i http://mirrors.onescience.ai:3141/pypi/simple
|
|
| 84 |
|
| 85 |
### Training Data
|
| 86 |
|
| 87 |
-
|
| 88 |
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
```
|
| 92 |
|
| 93 |
### Training
|
| 94 |
|
| 95 |
```bash
|
| 96 |
python scripts/train.py
|
| 97 |
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
### Model Weights
|
| 100 |
-
This repository will provide
|
| 101 |
|
| 102 |
### Inference
|
| 103 |
|
|
@@ -105,7 +115,11 @@ This repository will provide weights trained on the `pdenneval` dataset in the `
|
|
| 105 |
python scripts/inference.py
|
| 106 |
```
|
| 107 |
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
### Evaluation and Visualization
|
| 111 |
|
|
@@ -122,5 +136,5 @@ python scripts/result.py
|
|
| 122 |
|
| 123 |
# Citations and License
|
| 124 |
|
| 125 |
-
|
| 126 |
-
|
|
|
|
| 4 |
- en
|
| 5 |
tags:
|
| 6 |
- OneScience
|
| 7 |
+
- fluid dynamics
|
| 8 |
+
- physics-informed neural networks
|
| 9 |
+
- long-horizon physical prediction
|
|
|
|
| 10 |
frameworks: PyTorch
|
|
|
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
<p align="center">
|
| 13 |
<strong>
|
| 14 |
+
<span style="font-size: 30px;">PINNsformer</span>
|
| 15 |
</strong>
|
| 16 |
</p>
|
| 17 |
|
| 18 |
# Model Overview
|
|
|
|
| 19 |
|
| 20 |
+
PINNsFormer is a Transformer-based physics-informed neural network framework developed by researchers at the Georgia Institute of Technology and Carnegie Mellon University. It enables rapid prediction of solutions to time-dependent partial differential equations and their associated physical fields.
|
| 21 |
+
|
| 22 |
+
Paper: [PINNsFormer: A Transformer-Based Framework For Physics-Informed Neural Networks](https://arxiv.org/abs/2307.11833).
|
| 23 |
|
| 24 |
# Model Description
|
|
|
|
| 25 |
|
| 26 |
+
PINNsFormer uses a Transformer encoder–decoder with multi-head attention to numerically solve time-dependent partial differential equations, including convection, reaction, wave, and Navier–Stokes equations.
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
# Use Cases
|
| 31 |
|
| 32 |
| Use Case | Description |
|
| 33 |
+
| :--- | :--- |
|
| 34 |
+
| Time-dependent PDE solving | Train a continuous-field surrogate constrained by physical residuals, boundary conditions, and initial conditions |
|
| 35 |
+
| Physics-informed neural network validation | Rapidly validate the PINNsFormer network, loss functions, weight serialization, and inference pipeline |
|
| 36 |
+
| One-dimensional reaction equation example | Generate target fields from an analytical solution for pipeline validation and error analysis |
|
| 37 |
| ModelScope/OneCode execution | Download the standalone model package, install its dependencies, and run the provided scripts |
|
| 38 |
+
|
| 39 |
|
| 40 |
# Usage
|
| 41 |
|
|
|
|
| 49 |
|
| 50 |
**Hardware Requirements**
|
| 51 |
|
| 52 |
+
* A CPU can be used for small-scale pipeline validation.
|
| 53 |
+
* A GPU or DCU is recommended for training on larger grids or for more epochs.
|
| 54 |
+
* DCU users must install DTK in advance. DTK 25.04.2 or later, or the OneScience-recommended version for the target cluster, is recommended.
|
|
|
|
| 55 |
|
| 56 |
### Download the Model Package
|
| 57 |
|
| 58 |
```bash
|
| 59 |
+
modelscope download --model OneScience/PINNsformer --local_dir ./PINNsformer
|
| 60 |
+
cd PINNsformer
|
| 61 |
```
|
| 62 |
|
| 63 |
### Set Up the Runtime Environment
|
|
|
|
| 84 |
|
| 85 |
### Training Data
|
| 86 |
|
| 87 |
+
To use real data, download it from the link below and set `data.data_dir` in `conf/config.yaml` to the correct path.
|
| 88 |
|
| 89 |
+
| Source | Link | Extraction Code | Destination |
|
| 90 |
+
|---|---|---|---|
|
| 91 |
+
| Baidu Netdisk | https://pan.baidu.com/s/1pM4ICc6FJX5pLF7WEoozxQ?pwd=5gha | `5gha` | `convection/convection.mat` and `navier_stokes/cylinder_nektar_wake.mat` |
|
| 92 |
|
| 93 |
### Training
|
| 94 |
|
| 95 |
```bash
|
| 96 |
python scripts/train.py
|
| 97 |
```
|
| 98 |
+
The default configuration uses a smaller grid and fewer L-BFGS iterations for rapid end-to-end validation. To restore the scale of the original example, edit `conf/config.yaml`:
|
| 99 |
+
|
| 100 |
+
```yaml
|
| 101 |
+
data:
|
| 102 |
+
x_num: 101
|
| 103 |
+
t_num: 101
|
| 104 |
+
|
| 105 |
+
training:
|
| 106 |
+
epochs: 500
|
| 107 |
+
```
|
| 108 |
|
| 109 |
### Model Weights
|
| 110 |
+
This repository will provide PINNsFormer model weights in the `weights/` directory. The weights will be uploaded soon.
|
| 111 |
|
| 112 |
### Inference
|
| 113 |
|
|
|
|
| 115 |
python scripts/inference.py
|
| 116 |
```
|
| 117 |
|
| 118 |
+
Inference loads `weight/1dreaction_pinnsformer.pt` and saves:
|
| 119 |
+
|
| 120 |
+
```text
|
| 121 |
+
result/prediction.npz
|
| 122 |
+
```
|
| 123 |
|
| 124 |
### Evaluation and Visualization
|
| 125 |
|
|
|
|
| 136 |
|
| 137 |
# Citations and License
|
| 138 |
|
| 139 |
+
* Original PINNsformer paper: [PINNsFormer: A Transformer-Based Framework For Physics-Informed Neural Networks](https://arxiv.org/abs/2307.11833).
|
| 140 |
+
* This repository retains the relevant source and attribution notices. Follow all applicable license requirements when using, modifying, or distributing its contents.
|
conf/config.yaml
CHANGED
|
@@ -1,52 +1,46 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
data_dir: "./data"
|
| 5 |
-
file_name: "2D_DarcyFlow_beta0.1_Train.hdf5"
|
| 6 |
-
verbose: true
|
| 7 |
-
data:
|
| 8 |
-
single_file: true
|
| 9 |
-
reduced_resolution: 1
|
| 10 |
-
reduced_resolution_t: 1
|
| 11 |
-
reduced_batch: 1
|
| 12 |
-
initial_step: 1
|
| 13 |
-
test_ratio: 0.1
|
| 14 |
-
pde_name: "2D_Darcy_Flow"
|
| 15 |
-
dataloader:
|
| 16 |
-
batch_size: 2
|
| 17 |
-
num_workers: 0
|
| 18 |
-
pin_memory: false
|
| 19 |
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
modes: 4
|
| 24 |
-
width: 8
|
| 25 |
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
training_type: "single"
|
| 34 |
-
t_train: 2
|
| 35 |
-
epochs: 1
|
| 36 |
-
optimizer:
|
| 37 |
-
name: "Adam"
|
| 38 |
-
lr: 1.0e-3
|
| 39 |
-
weight_decay: 1.0e-4
|
| 40 |
-
scheduler:
|
| 41 |
-
name: "StepLR"
|
| 42 |
-
step_size: 100
|
| 43 |
-
gamma: 0.5
|
| 44 |
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
project:
|
| 2 |
+
name: PINNsformer
|
| 3 |
+
task: 1d_reaction
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
+
runtime:
|
| 6 |
+
seed: 0
|
| 7 |
+
device: auto
|
|
|
|
|
|
|
| 8 |
|
| 9 |
+
model:
|
| 10 |
+
name: PINNsformer1D
|
| 11 |
+
d_out: 1
|
| 12 |
+
d_hidden: 128
|
| 13 |
+
d_model: 32
|
| 14 |
+
num_layers: 1
|
| 15 |
+
heads: 2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
+
data:
|
| 18 |
+
x_range: [0.0, 6.283185307179586]
|
| 19 |
+
t_range: [0.0, 1.0]
|
| 20 |
+
x_num: 31
|
| 21 |
+
t_num: 31
|
| 22 |
+
sequence:
|
| 23 |
+
num_step: 5
|
| 24 |
+
step: 0.0001
|
| 25 |
|
| 26 |
+
equation:
|
| 27 |
+
reaction_rate: 5.0
|
| 28 |
+
initial:
|
| 29 |
+
center: 3.141592653589793
|
| 30 |
+
sigma: 0.7853981633974483
|
| 31 |
+
|
| 32 |
+
training:
|
| 33 |
+
epochs: 2
|
| 34 |
+
optimizer:
|
| 35 |
+
name: LBFGS
|
| 36 |
+
lr: 1.0
|
| 37 |
+
max_iter: 5
|
| 38 |
+
line_search_fn: strong_wolfe
|
| 39 |
+
checkpoint: weight/1dreaction_pinnsformer.pt
|
| 40 |
+
|
| 41 |
+
paths:
|
| 42 |
+
fake_data: data/reaction_fake.npz
|
| 43 |
+
prediction: result/prediction.npz
|
| 44 |
+
metrics: result/metrics.json
|
| 45 |
+
loss: result/train_loss.npy
|
| 46 |
+
figure: result/1dreaction_pinnsformer.png
|
configuration.json
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
{"framework":"Pytorch","task":"other"}
|
|
|
|
| 1 |
+
{"framework":"Pytorch","task":"other","model":"PINNsformer","domain":"cfd"}
|
model/__init__.py
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
-
from .
|
| 2 |
|
| 3 |
-
__all__ = ["
|
|
|
|
| 1 |
+
from .pinnsformer import PINNsformer1D, PINNsformer2D, WaveAct
|
| 2 |
|
| 3 |
+
__all__ = ["PINNsformer1D", "PINNsformer2D", "WaveAct"]
|
model/pinnsformer.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# implementation of PINNsformer
|
| 2 |
+
# paper: PINNsFormer: A Transformer-Based Framework For Physics-Informed Neural Networks
|
| 3 |
+
# link: https://arxiv.org/abs/2307.11833
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
|
| 8 |
+
from onescience.utils.pinnsformer_util import get_clones
|
| 9 |
+
|
| 10 |
+
class WaveAct(nn.Module):
|
| 11 |
+
def __init__(self):
|
| 12 |
+
super(WaveAct, self).__init__()
|
| 13 |
+
self.w1 = nn.Parameter(torch.ones(1), requires_grad=True)
|
| 14 |
+
self.w2 = nn.Parameter(torch.ones(1), requires_grad=True)
|
| 15 |
+
|
| 16 |
+
def forward(self, x):
|
| 17 |
+
return self.w1 * torch.sin(x)+ self.w2 * torch.cos(x)
|
| 18 |
+
|
| 19 |
+
class FeedForward(nn.Module):
|
| 20 |
+
def __init__(self, d_model, d_ff=256):
|
| 21 |
+
super(FeedForward, self).__init__()
|
| 22 |
+
self.linear = nn.Sequential(*[
|
| 23 |
+
nn.Linear(d_model, d_ff),
|
| 24 |
+
WaveAct(),
|
| 25 |
+
nn.Linear(d_ff, d_ff),
|
| 26 |
+
WaveAct(),
|
| 27 |
+
nn.Linear(d_ff, d_model)
|
| 28 |
+
])
|
| 29 |
+
|
| 30 |
+
def forward(self, x):
|
| 31 |
+
return self.linear(x)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class EncoderLayer(nn.Module):
|
| 35 |
+
def __init__(self, d_model, heads):
|
| 36 |
+
super(EncoderLayer, self).__init__()
|
| 37 |
+
|
| 38 |
+
self.attn = nn.MultiheadAttention(embed_dim=d_model, num_heads=heads, batch_first=True)
|
| 39 |
+
self.ff = FeedForward(d_model)
|
| 40 |
+
self.act1 = WaveAct()
|
| 41 |
+
self.act2 = WaveAct()
|
| 42 |
+
|
| 43 |
+
def forward(self, x):
|
| 44 |
+
x2 = self.act1(x)
|
| 45 |
+
# pdb.set_trace()
|
| 46 |
+
x = x + self.attn(x2,x2,x2)[0]
|
| 47 |
+
x2 = self.act2(x)
|
| 48 |
+
x = x + self.ff(x2)
|
| 49 |
+
return x
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class DecoderLayer(nn.Module):
|
| 53 |
+
def __init__(self, d_model, heads):
|
| 54 |
+
super(DecoderLayer, self).__init__()
|
| 55 |
+
|
| 56 |
+
self.attn = nn.MultiheadAttention(embed_dim=d_model, num_heads=heads, batch_first=True)
|
| 57 |
+
self.ff = FeedForward(d_model)
|
| 58 |
+
self.act1 = WaveAct()
|
| 59 |
+
self.act2 = WaveAct()
|
| 60 |
+
|
| 61 |
+
def forward(self, x, e_outputs):
|
| 62 |
+
x2 = self.act1(x)
|
| 63 |
+
x = x + self.attn(x2, e_outputs, e_outputs)[0]
|
| 64 |
+
x2 = self.act2(x)
|
| 65 |
+
x = x + self.ff(x2)
|
| 66 |
+
return x
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
class Encoder(nn.Module):
|
| 70 |
+
def __init__(self, d_model, N, heads):
|
| 71 |
+
super(Encoder, self).__init__()
|
| 72 |
+
self.N = N
|
| 73 |
+
self.layers = get_clones(EncoderLayer(d_model, heads), N)
|
| 74 |
+
self.act = WaveAct()
|
| 75 |
+
|
| 76 |
+
def forward(self, x):
|
| 77 |
+
for i in range(self.N):
|
| 78 |
+
x = self.layers[i](x)
|
| 79 |
+
return self.act(x)
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
class Decoder(nn.Module):
|
| 83 |
+
def __init__(self, d_model, N, heads):
|
| 84 |
+
super(Decoder, self).__init__()
|
| 85 |
+
self.N = N
|
| 86 |
+
self.layers = get_clones(DecoderLayer(d_model, heads), N)
|
| 87 |
+
self.act = WaveAct()
|
| 88 |
+
|
| 89 |
+
def forward(self, x, e_outputs):
|
| 90 |
+
for i in range(self.N):
|
| 91 |
+
x = self.layers[i](x, e_outputs)
|
| 92 |
+
return self.act(x)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class PINNsformer1D(nn.Module):
|
| 97 |
+
def __init__(self, d_out, d_model, d_hidden, N, heads):
|
| 98 |
+
super(PINNsformer1D, self).__init__()
|
| 99 |
+
|
| 100 |
+
self.linear_emb = nn.Linear(2, d_model)
|
| 101 |
+
|
| 102 |
+
self.encoder = Encoder(d_model, N, heads)
|
| 103 |
+
self.decoder = Decoder(d_model, N, heads)
|
| 104 |
+
self.linear_out = nn.Sequential(*[
|
| 105 |
+
nn.Linear(d_model, d_hidden),
|
| 106 |
+
WaveAct(),
|
| 107 |
+
nn.Linear(d_hidden, d_hidden),
|
| 108 |
+
WaveAct(),
|
| 109 |
+
nn.Linear(d_hidden, d_out)
|
| 110 |
+
])
|
| 111 |
+
|
| 112 |
+
def forward(self, x, t):
|
| 113 |
+
src = torch.cat((x,t), dim=-1)
|
| 114 |
+
src = self.linear_emb(src)
|
| 115 |
+
|
| 116 |
+
e_outputs = self.encoder(src)
|
| 117 |
+
d_output = self.decoder(src, e_outputs)
|
| 118 |
+
output = self.linear_out(d_output)
|
| 119 |
+
# pdb.set_trace()
|
| 120 |
+
# raise Exception('stop')
|
| 121 |
+
return output
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
class PINNsformer2D(nn.Module):
|
| 125 |
+
def __init__(self, d_out, d_model, d_hidden, N, heads):
|
| 126 |
+
super(PINNsformer2D, self).__init__()
|
| 127 |
+
|
| 128 |
+
self.linear_emb = nn.Linear(3, d_model)
|
| 129 |
+
|
| 130 |
+
self.encoder = Encoder(d_model, N, heads)
|
| 131 |
+
self.decoder = Decoder(d_model, N, heads)
|
| 132 |
+
self.linear_out = nn.Sequential(*[
|
| 133 |
+
nn.Linear(d_model, d_hidden),
|
| 134 |
+
WaveAct(),
|
| 135 |
+
nn.Linear(d_hidden, d_hidden),
|
| 136 |
+
WaveAct(),
|
| 137 |
+
nn.Linear(d_hidden, d_out)
|
| 138 |
+
])
|
| 139 |
+
|
| 140 |
+
def forward(self, x, y, t):
|
| 141 |
+
src = torch.cat((x,y,t), dim=-1)
|
| 142 |
+
src = self.linear_emb(src)
|
| 143 |
+
e_outputs = self.encoder(src)
|
| 144 |
+
d_output = self.decoder(src, e_outputs)
|
| 145 |
+
output = self.linear_out(d_output)
|
| 146 |
+
return output
|
result/.gitkeep
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
scripts/common.py
CHANGED
|
@@ -1,337 +1,100 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
-
import
|
|
|
|
| 4 |
import sys
|
| 5 |
-
import warnings
|
| 6 |
from pathlib import Path
|
| 7 |
-
from
|
| 8 |
-
from typing import Any, Optional
|
| 9 |
|
| 10 |
-
import h5py
|
| 11 |
import numpy as np
|
| 12 |
import torch
|
| 13 |
-
|
| 14 |
-
|
| 15 |
|
| 16 |
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
| 17 |
-
DEFAULT_CONFIG = PROJECT_ROOT / "conf" / "config.yaml"
|
| 18 |
-
|
| 19 |
if str(PROJECT_ROOT) not in sys.path:
|
| 20 |
sys.path.insert(0, str(PROJECT_ROOT))
|
| 21 |
|
| 22 |
-
from model.fno import FNO1d, FNO2d, FNO3d, FNO_maxwell
|
| 23 |
-
|
| 24 |
-
try:
|
| 25 |
-
from onescience.utils.YParams import YParams
|
| 26 |
-
except ModuleNotFoundError as exc:
|
| 27 |
-
YParams = None
|
| 28 |
-
YPARAMS_IMPORT_ERROR = exc
|
| 29 |
-
|
| 30 |
-
try:
|
| 31 |
-
from onescience.distributed.manager import DistributedManager
|
| 32 |
-
except ModuleNotFoundError as exc:
|
| 33 |
-
DistributedManager = None
|
| 34 |
-
DISTRIBUTED_IMPORT_ERROR = exc
|
| 35 |
-
|
| 36 |
-
try:
|
| 37 |
-
from onescience.datapipes.cfd.PDENNEval import PDEBenchFNODatapipe
|
| 38 |
-
except (ImportError, ModuleNotFoundError, OSError) as exc:
|
| 39 |
-
PDEBenchFNODatapipe = None
|
| 40 |
-
DATAPIPE_IMPORT_ERROR = exc
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
def get_attr(obj: Any, name: str, default: Any = None) -> Any:
|
| 44 |
-
if isinstance(obj, dict):
|
| 45 |
-
return obj.get(name, default)
|
| 46 |
-
return getattr(obj, name, default)
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
def set_attr(obj: Any, name: str, value: Any) -> None:
|
| 50 |
-
if isinstance(obj, dict):
|
| 51 |
-
obj[name] = value
|
| 52 |
-
else:
|
| 53 |
-
setattr(obj, name, value)
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
def to_namespace(value: Any) -> Any:
|
| 57 |
-
if isinstance(value, dict):
|
| 58 |
-
return SimpleNamespace(**{key: to_namespace(val) for key, val in value.items()})
|
| 59 |
-
if isinstance(value, list):
|
| 60 |
-
return [to_namespace(item) for item in value]
|
| 61 |
-
return value
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
def load_config(config_path: Path | str = DEFAULT_CONFIG) -> Any:
|
| 65 |
-
config_path = Path(config_path).expanduser().resolve()
|
| 66 |
-
if YParams is not None:
|
| 67 |
-
return YParams(str(config_path), "fno_config")
|
| 68 |
-
|
| 69 |
-
import yaml
|
| 70 |
-
|
| 71 |
-
with config_path.open("r", encoding="utf-8") as handle:
|
| 72 |
-
raw = yaml.safe_load(handle)
|
| 73 |
-
if not isinstance(raw, dict) or "fno_config" not in raw:
|
| 74 |
-
raise ValueError(f"{config_path} must contain root key 'fno_config'")
|
| 75 |
-
return to_namespace(raw["fno_config"])
|
| 76 |
-
|
| 77 |
|
| 78 |
-
def
|
| 79 |
-
if
|
| 80 |
-
raise ValueError("path value can not be null")
|
| 81 |
-
expanded = os.path.expandvars(os.path.expanduser(str(value)))
|
| 82 |
-
path = Path(expanded)
|
| 83 |
if not path.is_absolute():
|
| 84 |
-
path =
|
| 85 |
-
|
|
|
|
| 86 |
|
| 87 |
|
| 88 |
-
def
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
output_dir: Optional[str] = None,
|
| 92 |
-
checkpoint: Optional[str] = None,
|
| 93 |
-
) -> Any:
|
| 94 |
-
source = cfg.datapipe.source
|
| 95 |
-
training = cfg.training
|
| 96 |
-
inference = get_attr(cfg, "inference", None)
|
| 97 |
|
| 98 |
-
resolved_data = resolve_path(data_dir or source.data_dir)
|
| 99 |
-
set_attr(source, "data_dir", str(resolved_data))
|
| 100 |
|
| 101 |
-
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
|
| 104 |
-
model_path = get_attr(training, "model_path", None)
|
| 105 |
-
if model_path:
|
| 106 |
-
set_attr(training, "model_path", str(resolve_path(model_path)))
|
| 107 |
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
if infer_checkpoint:
|
| 113 |
-
set_attr(inference, "checkpoint", str(resolve_path(infer_checkpoint)))
|
| 114 |
|
| 115 |
-
return cfg
|
| 116 |
|
|
|
|
|
|
|
| 117 |
|
| 118 |
-
class SingleProcessManager:
|
| 119 |
-
rank = 0
|
| 120 |
-
local_rank = 0
|
| 121 |
-
world_size = 1
|
| 122 |
-
distributed = False
|
| 123 |
-
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
| 124 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
|
| 126 |
-
def initialize_distributed() -> Any:
|
| 127 |
-
if DistributedManager is None:
|
| 128 |
-
warnings.warn(
|
| 129 |
-
f"Falling back to a single-process manager: {DISTRIBUTED_IMPORT_ERROR}",
|
| 130 |
-
RuntimeWarning,
|
| 131 |
-
)
|
| 132 |
-
return SingleProcessManager()
|
| 133 |
-
DistributedManager.initialize()
|
| 134 |
-
return DistributedManager()
|
| 135 |
|
|
|
|
|
|
|
| 136 |
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
|
| 142 |
-
|
| 143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
|
| 145 |
-
def __init__(self, datapipe_cfg: Any, mode: str):
|
| 146 |
-
self.mode = mode
|
| 147 |
-
self.data_cfg = datapipe_cfg.data
|
| 148 |
-
self.source_cfg = datapipe_cfg.source
|
| 149 |
-
self.initial_step = int(self.data_cfg.initial_step)
|
| 150 |
-
self.reduced_resolution = int(self.data_cfg.reduced_resolution)
|
| 151 |
-
self.reduced_resolution_t = int(self.data_cfg.reduced_resolution_t)
|
| 152 |
-
self.reduced_batch = int(self.data_cfg.reduced_batch)
|
| 153 |
-
self.test_ratio = float(get_attr(self.data_cfg, "test_ratio", 0.1))
|
| 154 |
-
self.file_path = Path(self.source_cfg.data_dir) / self.source_cfg.file_name
|
| 155 |
-
self._load_single_file()
|
| 156 |
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
:: self.reduced_resolution,
|
| 170 |
-
]
|
| 171 |
-
data = np.transpose(data, (0, 2, 1))
|
| 172 |
-
self.data = data[:, :, :, None]
|
| 173 |
-
x = np.asarray(handle["x-coordinate"], dtype=np.float32)
|
| 174 |
-
self.grid = torch.tensor(
|
| 175 |
-
x[:: self.reduced_resolution], dtype=torch.float32
|
| 176 |
-
).unsqueeze(-1)
|
| 177 |
-
elif tensor.ndim == 4:
|
| 178 |
-
data = tensor[
|
| 179 |
-
:: self.reduced_batch,
|
| 180 |
-
:,
|
| 181 |
-
:: self.reduced_resolution,
|
| 182 |
-
:: self.reduced_resolution,
|
| 183 |
-
]
|
| 184 |
-
data = np.transpose(data, (0, 2, 3, 1))
|
| 185 |
-
if "nu" in handle:
|
| 186 |
-
nu = np.asarray(handle["nu"], dtype=np.float32)[
|
| 187 |
-
:: self.reduced_batch,
|
| 188 |
-
:: self.reduced_resolution,
|
| 189 |
-
:: self.reduced_resolution,
|
| 190 |
-
]
|
| 191 |
-
data = np.concatenate([nu[:, :, :, None], data], axis=-1)
|
| 192 |
-
self.data = data[:, :, :, :, None]
|
| 193 |
-
x = torch.tensor(np.asarray(handle["x-coordinate"], dtype=np.float32))
|
| 194 |
-
y = torch.tensor(np.asarray(handle["y-coordinate"], dtype=np.float32))
|
| 195 |
-
xx, yy = torch.meshgrid(x, y, indexing="ij")
|
| 196 |
-
self.grid = torch.stack((xx, yy), dim=-1)[
|
| 197 |
-
:: self.reduced_resolution, :: self.reduced_resolution
|
| 198 |
-
].float()
|
| 199 |
-
else:
|
| 200 |
-
raise ValueError(f"unsupported tensor ndim for FNO fallback: {tensor.ndim}")
|
| 201 |
-
|
| 202 |
-
sample_count = self.data.shape[0]
|
| 203 |
-
val_count = max(1, int(sample_count * self.test_ratio)) if sample_count > 1 else 0
|
| 204 |
-
if self.mode == "train":
|
| 205 |
-
self.data = self.data[val_count:]
|
| 206 |
-
else:
|
| 207 |
-
self.data = self.data[:val_count]
|
| 208 |
-
self.data = torch.tensor(self.data, dtype=torch.float32)
|
| 209 |
-
self.spatial_dim = len(self.data.shape) - 3
|
| 210 |
-
|
| 211 |
-
def __len__(self) -> int:
|
| 212 |
-
return len(self.data)
|
| 213 |
-
|
| 214 |
-
def __getitem__(self, idx: int):
|
| 215 |
-
sample = self.data[idx]
|
| 216 |
-
return sample[..., : self.initial_step, :], sample, self.grid
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
class LocalPDEBenchFNODatapipe:
|
| 220 |
-
def __init__(self, cfg: Any, distributed: bool = False):
|
| 221 |
-
self.config = cfg
|
| 222 |
-
self.distributed = distributed
|
| 223 |
-
self.train_dataset = LocalPDEBenchFNODataset(cfg.datapipe, "train")
|
| 224 |
-
self.val_dataset = LocalPDEBenchFNODataset(cfg.datapipe, "val")
|
| 225 |
-
self.spatial_dim = self.train_dataset.spatial_dim
|
| 226 |
-
|
| 227 |
-
def train_dataloader(self):
|
| 228 |
-
loader_args = self.config.datapipe.dataloader
|
| 229 |
-
return DataLoader(
|
| 230 |
-
self.train_dataset,
|
| 231 |
-
batch_size=int(loader_args.batch_size),
|
| 232 |
-
num_workers=int(loader_args.num_workers),
|
| 233 |
-
pin_memory=bool(loader_args.pin_memory),
|
| 234 |
-
shuffle=True,
|
| 235 |
-
drop_last=True,
|
| 236 |
-
), None
|
| 237 |
-
|
| 238 |
-
def val_dataloader(self):
|
| 239 |
-
loader_args = self.config.datapipe.dataloader
|
| 240 |
-
return DataLoader(
|
| 241 |
-
self.val_dataset,
|
| 242 |
-
batch_size=int(loader_args.batch_size),
|
| 243 |
-
num_workers=int(loader_args.num_workers),
|
| 244 |
-
pin_memory=bool(loader_args.pin_memory),
|
| 245 |
-
shuffle=False,
|
| 246 |
-
drop_last=False,
|
| 247 |
-
), None
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
def build_datapipe(cfg: Any, distributed: bool = False, force_local: bool = False) -> Any:
|
| 251 |
-
if PDEBenchFNODatapipe is not None and not force_local:
|
| 252 |
-
return PDEBenchFNODatapipe(cfg, distributed=distributed)
|
| 253 |
-
if PDEBenchFNODatapipe is None:
|
| 254 |
-
warnings.warn(
|
| 255 |
-
f"Using local FNO HDF5 datapipe because OneScience datapipe import failed: "
|
| 256 |
-
f"{DATAPIPE_IMPORT_ERROR}",
|
| 257 |
-
RuntimeWarning,
|
| 258 |
-
)
|
| 259 |
-
return LocalPDEBenchFNODatapipe(cfg, distributed=distributed)
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
def build_model(spatial_dim: int, cfg: Any) -> torch.nn.Module:
|
| 263 |
-
model_args = cfg.model
|
| 264 |
-
data_cfg = cfg.datapipe.data
|
| 265 |
-
initial_step = int(data_cfg.initial_step)
|
| 266 |
-
pde_name = get_attr(data_cfg, "pde_name", "")
|
| 267 |
-
modes = int(model_args.modes)
|
| 268 |
-
|
| 269 |
-
if pde_name == "3D_Maxwell":
|
| 270 |
-
return FNO_maxwell(
|
| 271 |
-
num_channels=int(model_args.num_channels),
|
| 272 |
-
width=int(model_args.width),
|
| 273 |
-
modes1=modes,
|
| 274 |
-
modes2=modes,
|
| 275 |
-
modes3=modes,
|
| 276 |
-
initial_step=initial_step,
|
| 277 |
-
)
|
| 278 |
-
if spatial_dim == 1:
|
| 279 |
-
return FNO1d(
|
| 280 |
-
num_channels=int(model_args.num_channels),
|
| 281 |
-
width=int(model_args.width),
|
| 282 |
-
modes=modes,
|
| 283 |
-
initial_step=initial_step,
|
| 284 |
-
)
|
| 285 |
-
if spatial_dim == 2:
|
| 286 |
-
return FNO2d(
|
| 287 |
-
num_channels=int(model_args.num_channels),
|
| 288 |
-
width=int(model_args.width),
|
| 289 |
-
modes1=modes,
|
| 290 |
-
modes2=modes,
|
| 291 |
-
initial_step=initial_step,
|
| 292 |
-
)
|
| 293 |
-
if spatial_dim == 3:
|
| 294 |
-
return FNO3d(
|
| 295 |
-
num_channels=int(model_args.num_channels),
|
| 296 |
-
width=int(model_args.width),
|
| 297 |
-
modes1=modes,
|
| 298 |
-
modes2=modes,
|
| 299 |
-
modes3=modes,
|
| 300 |
-
initial_step=initial_step,
|
| 301 |
-
)
|
| 302 |
-
raise ValueError(f"unsupported spatial dimension: {spatial_dim}")
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
def predict_batch(model: torch.nn.Module, x: torch.Tensor, y: torch.Tensor, grid: torch.Tensor, cfg: Any):
|
| 306 |
-
data_cfg = cfg.datapipe.data
|
| 307 |
-
train_cfg = cfg.training
|
| 308 |
-
initial_step = int(data_cfg.initial_step)
|
| 309 |
-
t_train = min(int(train_cfg.t_train), y.shape[-2])
|
| 310 |
-
input_shape = list(x.shape)[:-2] + [-1]
|
| 311 |
|
| 312 |
-
if get_attr(train_cfg, "training_type", "single") == "autoregressive":
|
| 313 |
-
pred = y[..., :initial_step, :]
|
| 314 |
-
for _ in range(initial_step, t_train):
|
| 315 |
-
model_input = x.reshape(input_shape)
|
| 316 |
-
model_output = model(model_input, grid)
|
| 317 |
-
if model_output.dim() == pred.dim() - 1:
|
| 318 |
-
model_output = model_output.unsqueeze(-2)
|
| 319 |
-
pred = torch.cat((pred, model_output), dim=-2)
|
| 320 |
-
x = torch.cat((x[..., 1:, :], model_output), dim=-2)
|
| 321 |
-
return pred, y[..., :t_train, :]
|
| 322 |
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
return pred, target
|
| 329 |
|
| 330 |
|
| 331 |
-
def
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
return checkpoint["model_state_dict"]
|
| 335 |
-
if isinstance(checkpoint, dict):
|
| 336 |
-
return checkpoint
|
| 337 |
-
raise ValueError(f"unsupported checkpoint format: {path}")
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
+
import json
|
| 4 |
+
import random
|
| 5 |
import sys
|
|
|
|
| 6 |
from pathlib import Path
|
| 7 |
+
from typing import Any
|
|
|
|
| 8 |
|
|
|
|
| 9 |
import numpy as np
|
| 10 |
import torch
|
| 11 |
+
import yaml
|
|
|
|
| 12 |
|
| 13 |
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
|
| 14 |
if str(PROJECT_ROOT) not in sys.path:
|
| 15 |
sys.path.insert(0, str(PROJECT_ROOT))
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
+
def load_config(config_path: str | Path | None = None) -> dict[str, Any]:
|
| 19 |
+
path = Path(config_path) if config_path else PROJECT_ROOT / "conf" / "config.yaml"
|
|
|
|
|
|
|
|
|
|
| 20 |
if not path.is_absolute():
|
| 21 |
+
path = PROJECT_ROOT / path
|
| 22 |
+
with path.open("r", encoding="utf-8") as f:
|
| 23 |
+
return yaml.safe_load(f)
|
| 24 |
|
| 25 |
|
| 26 |
+
def project_path(path: str | Path) -> Path:
|
| 27 |
+
path = Path(path)
|
| 28 |
+
return path if path.is_absolute() else PROJECT_ROOT / path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
|
|
|
|
|
|
| 30 |
|
| 31 |
+
def seed_everything(seed: int) -> None:
|
| 32 |
+
random.seed(seed)
|
| 33 |
+
np.random.seed(seed)
|
| 34 |
+
torch.manual_seed(seed)
|
| 35 |
+
if torch.cuda.is_available():
|
| 36 |
+
torch.cuda.manual_seed_all(seed)
|
| 37 |
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
+
def select_device(value: str = "auto") -> torch.device:
|
| 40 |
+
if value == "auto":
|
| 41 |
+
return torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
| 42 |
+
return torch.device(value)
|
|
|
|
|
|
|
| 43 |
|
|
|
|
| 44 |
|
| 45 |
+
def ensure_parent(path: Path) -> None:
|
| 46 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 47 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
+
def ensure_runtime_dirs(cfg: dict[str, Any]) -> None:
|
| 50 |
+
for key in ("fake_data", "prediction", "metrics", "loss", "figure"):
|
| 51 |
+
ensure_parent(project_path(cfg["paths"][key]))
|
| 52 |
+
ensure_parent(project_path(cfg["training"]["checkpoint"]))
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
+
def build_model(cfg: dict[str, Any]) -> torch.nn.Module:
|
| 56 |
+
from model import PINNsformer1D
|
| 57 |
|
| 58 |
+
model_cfg = cfg["model"]
|
| 59 |
+
return PINNsformer1D(
|
| 60 |
+
d_out=int(model_cfg["d_out"]),
|
| 61 |
+
d_hidden=int(model_cfg["d_hidden"]),
|
| 62 |
+
d_model=int(model_cfg["d_model"]),
|
| 63 |
+
N=int(model_cfg["num_layers"]),
|
| 64 |
+
heads=int(model_cfg["heads"]),
|
| 65 |
+
)
|
| 66 |
|
| 67 |
|
| 68 |
+
def initial_condition(x: np.ndarray | torch.Tensor, cfg: dict[str, Any]):
|
| 69 |
+
initial = cfg["equation"]["initial"]
|
| 70 |
+
center = initial["center"]
|
| 71 |
+
sigma = initial["sigma"]
|
| 72 |
+
if isinstance(x, torch.Tensor):
|
| 73 |
+
return torch.exp(-((x - center) ** 2) / (2 * sigma**2))
|
| 74 |
+
return np.exp(-((x - center) ** 2) / (2 * sigma**2))
|
| 75 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
+
def exact_reaction_solution(
|
| 78 |
+
x: np.ndarray | torch.Tensor,
|
| 79 |
+
t: np.ndarray | torch.Tensor,
|
| 80 |
+
cfg: dict[str, Any],
|
| 81 |
+
):
|
| 82 |
+
h = initial_condition(x, cfg)
|
| 83 |
+
rate = cfg["equation"]["reaction_rate"]
|
| 84 |
+
if isinstance(h, torch.Tensor):
|
| 85 |
+
exp_term = torch.exp(rate * t)
|
| 86 |
+
else:
|
| 87 |
+
exp_term = np.exp(rate * t)
|
| 88 |
+
return h * exp_term / (h * exp_term + 1 - h)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
|
| 91 |
+
def relative_errors(pred: np.ndarray, target: np.ndarray) -> dict[str, float]:
|
| 92 |
+
return {
|
| 93 |
+
"relative_l1": float(np.sum(np.abs(target - pred)) / np.sum(np.abs(target))),
|
| 94 |
+
"relative_l2": float(np.sqrt(np.sum((target - pred) ** 2) / np.sum(target**2))),
|
| 95 |
+
}
|
|
|
|
| 96 |
|
| 97 |
|
| 98 |
+
def write_json(path: Path, payload: dict[str, Any]) -> None:
|
| 99 |
+
ensure_parent(path)
|
| 100 |
+
path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
|
|
|
|
|
|
|
|
|
|
|
scripts/fake_data.py
CHANGED
|
@@ -1,59 +1,31 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
import argparse
|
| 4 |
-
from pathlib import Path
|
| 5 |
|
| 6 |
-
import h5py
|
| 7 |
import numpy as np
|
| 8 |
|
| 9 |
-
from common import
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
def
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
return
|
| 16 |
-
|
| 17 |
-
path.parent.mkdir(parents=True, exist_ok=True)
|
| 18 |
-
rng = np.random.default_rng(seed)
|
| 19 |
-
x = np.linspace(0.0, 1.0, grid_size, dtype=np.float32)
|
| 20 |
-
y = np.linspace(0.0, 1.0, grid_size, dtype=np.float32)
|
| 21 |
-
xx, yy = np.meshgrid(x, y, indexing="ij")
|
| 22 |
-
|
| 23 |
-
nu = rng.uniform(0.05, 0.25, size=(samples, grid_size, grid_size)).astype(np.float32)
|
| 24 |
-
tensor = np.empty((samples, 2, grid_size, grid_size), dtype=np.float32)
|
| 25 |
-
base = np.sin(np.pi * xx) * np.sin(np.pi * yy)
|
| 26 |
-
for idx in range(samples):
|
| 27 |
-
tensor[idx, 0] = base + 0.05 * rng.standard_normal(base.shape)
|
| 28 |
-
tensor[idx, 1] = tensor[idx, 0] * (1.0 + nu[idx]) + 0.01 * rng.standard_normal(base.shape)
|
| 29 |
-
|
| 30 |
-
with h5py.File(path, "w") as handle:
|
| 31 |
-
handle.create_dataset("tensor", data=tensor)
|
| 32 |
-
handle.create_dataset("nu", data=nu)
|
| 33 |
-
handle.create_dataset("x-coordinate", data=x)
|
| 34 |
-
handle.create_dataset("y-coordinate", data=y)
|
| 35 |
-
handle.create_dataset("t-coordinate", data=np.array([0.0, 1.0], dtype=np.float32))
|
| 36 |
-
|
| 37 |
-
print(f"Fake Darcy HDF5 written: {path}")
|
| 38 |
-
print(f"tensor={tensor.shape}, nu={nu.shape}")
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
def main() -> int:
|
| 42 |
-
parser = argparse.ArgumentParser(description="Generate a small PDENNEval FNO fake dataset.")
|
| 43 |
-
parser.add_argument("--config", default=str(DEFAULT_CONFIG), help="Path to conf/config.yaml")
|
| 44 |
-
parser.add_argument("--data-dir", default=None, help="Override datapipe.source.data_dir")
|
| 45 |
-
parser.add_argument("--samples", type=int, default=20)
|
| 46 |
-
parser.add_argument("--grid-size", type=int, default=16)
|
| 47 |
-
parser.add_argument("--seed", type=int, default=0)
|
| 48 |
-
parser.add_argument("--overwrite", action="store_true")
|
| 49 |
args = parser.parse_args()
|
| 50 |
|
| 51 |
-
cfg =
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
|
| 58 |
if __name__ == "__main__":
|
| 59 |
-
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
import argparse
|
|
|
|
| 4 |
|
|
|
|
| 5 |
import numpy as np
|
| 6 |
|
| 7 |
+
from common import exact_reaction_solution, load_config, project_path
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def main() -> None:
|
| 11 |
+
parser = argparse.ArgumentParser(description="Generate fake PINNsformer reaction data.")
|
| 12 |
+
parser.add_argument("--config", default=None, help="Path to config.yaml.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
args = parser.parse_args()
|
| 14 |
|
| 15 |
+
cfg = load_config(args.config)
|
| 16 |
+
data_cfg = cfg["data"]
|
| 17 |
+
x = np.linspace(*data_cfg["x_range"], int(data_cfg["x_num"]), dtype=np.float32)
|
| 18 |
+
t = np.linspace(*data_cfg["t_range"], int(data_cfg["t_num"]), dtype=np.float32)
|
| 19 |
+
x_mesh, t_mesh = np.meshgrid(x, t)
|
| 20 |
+
u = exact_reaction_solution(x_mesh, t_mesh, cfg).astype(np.float32)
|
| 21 |
+
|
| 22 |
+
output_path = project_path(cfg["paths"]["fake_data"])
|
| 23 |
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
| 24 |
+
np.savez(output_path, x=x, t=t, u=u)
|
| 25 |
+
|
| 26 |
+
print(f"fake data saved to {output_path}")
|
| 27 |
+
print(f"x={x.shape}, t={t.shape}, u={u.shape}")
|
| 28 |
|
| 29 |
|
| 30 |
if __name__ == "__main__":
|
| 31 |
+
main()
|
scripts/inference.py
CHANGED
|
@@ -1,83 +1,85 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
import argparse
|
| 4 |
-
from pathlib import Path
|
| 5 |
|
| 6 |
import numpy as np
|
| 7 |
import torch
|
| 8 |
|
|
|
|
|
|
|
| 9 |
from common import (
|
| 10 |
-
DEFAULT_CONFIG,
|
| 11 |
-
build_datapipe,
|
| 12 |
build_model,
|
| 13 |
-
|
| 14 |
-
get_attr,
|
| 15 |
-
initialize_distributed,
|
| 16 |
load_config,
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
)
|
| 21 |
|
| 22 |
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
parser =
|
| 26 |
-
parser.add_argument("--
|
| 27 |
-
parser.add_argument("--
|
| 28 |
-
parser.add_argument("--checkpoint", default=None, help="Checkpoint path")
|
| 29 |
-
parser.add_argument("--output-dir", default=None, help="Directory for npz predictions")
|
| 30 |
-
parser.add_argument("--max-batches", type=int, default=None)
|
| 31 |
-
parser.add_argument("--force-local-datapipe", action="store_true")
|
| 32 |
args = parser.parse_args()
|
| 33 |
|
| 34 |
-
|
| 35 |
-
device =
|
| 36 |
-
|
| 37 |
-
if
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
checkpoint = Path(args.checkpoint or cfg.inference.checkpoint)
|
| 41 |
-
if not checkpoint.is_file():
|
| 42 |
-
raise FileNotFoundError(f"checkpoint not found: {checkpoint}")
|
| 43 |
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
model = build_model(datapipe.spatial_dim, cfg).to(device)
|
| 51 |
-
model.load_state_dict(load_model_state(checkpoint, device))
|
| 52 |
model.eval()
|
| 53 |
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
x = x.to(device)
|
| 63 |
-
y = y.to(device)
|
| 64 |
-
grid = grid.to(device)
|
| 65 |
-
pred, target = predict_batch(model, x, y, grid, cfg)
|
| 66 |
-
path = output_dir / f"batch_{batch_idx:04d}.npz"
|
| 67 |
-
np.savez_compressed(
|
| 68 |
-
path,
|
| 69 |
-
prediction=pred.detach().cpu().numpy(),
|
| 70 |
-
target=target.detach().cpu().numpy(),
|
| 71 |
)
|
| 72 |
-
print(f"wrote {path}")
|
| 73 |
-
written += 1
|
| 74 |
|
| 75 |
-
|
| 76 |
-
|
|
|
|
|
|
|
| 77 |
|
| 78 |
-
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
|
| 81 |
|
| 82 |
if __name__ == "__main__":
|
| 83 |
-
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
import argparse
|
|
|
|
| 4 |
|
| 5 |
import numpy as np
|
| 6 |
import torch
|
| 7 |
|
| 8 |
+
from onescience.utils.pinnsformer_util import get_data, make_time_sequence
|
| 9 |
+
|
| 10 |
from common import (
|
|
|
|
|
|
|
| 11 |
build_model,
|
| 12 |
+
exact_reaction_solution,
|
|
|
|
|
|
|
| 13 |
load_config,
|
| 14 |
+
project_path,
|
| 15 |
+
relative_errors,
|
| 16 |
+
select_device,
|
| 17 |
)
|
| 18 |
|
| 19 |
|
| 20 |
+
def main() -> None:
|
| 21 |
+
parser = argparse.ArgumentParser(description="Run PINNsformer inference for the 1D reaction equation.")
|
| 22 |
+
parser.add_argument("--config", default=None, help="Path to config.yaml.")
|
| 23 |
+
parser.add_argument("--checkpoint", default=None, help="Path to a trained checkpoint.")
|
| 24 |
+
parser.add_argument("--device", default=None, help="Override runtime.device.")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
args = parser.parse_args()
|
| 26 |
|
| 27 |
+
cfg = load_config(args.config)
|
| 28 |
+
device = select_device(args.device or cfg["runtime"]["device"])
|
| 29 |
+
checkpoint = project_path(args.checkpoint or cfg["training"]["checkpoint"])
|
| 30 |
+
if not checkpoint.exists():
|
| 31 |
+
raise FileNotFoundError(f"Checkpoint not found: {checkpoint}. Run scripts/train.py first.")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
+
model = build_model(cfg).to(device)
|
| 34 |
+
try:
|
| 35 |
+
state = torch.load(checkpoint, map_location=device, weights_only=True)
|
| 36 |
+
except TypeError:
|
| 37 |
+
state = torch.load(checkpoint, map_location=device)
|
| 38 |
+
model.load_state_dict(state.get("model_state_dict", state))
|
|
|
|
|
|
|
| 39 |
model.eval()
|
| 40 |
|
| 41 |
+
data_cfg = cfg["data"]
|
| 42 |
+
res, _, _, _, _ = get_data(
|
| 43 |
+
data_cfg["x_range"],
|
| 44 |
+
data_cfg["t_range"],
|
| 45 |
+
int(data_cfg["x_num"]),
|
| 46 |
+
int(data_cfg["t_num"]),
|
| 47 |
+
)
|
| 48 |
+
seq = make_time_sequence(
|
| 49 |
+
res,
|
| 50 |
+
num_step=int(data_cfg["sequence"]["num_step"]),
|
| 51 |
+
step=float(data_cfg["sequence"]["step"]),
|
| 52 |
+
)
|
| 53 |
+
res_tensor = torch.tensor(seq, dtype=torch.float32, requires_grad=False, device=device)
|
| 54 |
+
x_test, t_test = res_tensor[:, :, 0:1], res_tensor[:, :, 1:2]
|
| 55 |
|
| 56 |
+
with torch.no_grad():
|
| 57 |
+
pred = model(x_test, t_test)[:, 0:1].detach().cpu().numpy().reshape(
|
| 58 |
+
int(data_cfg["t_num"]),
|
| 59 |
+
int(data_cfg["x_num"]),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
)
|
|
|
|
|
|
|
| 61 |
|
| 62 |
+
x = res[:, 0].reshape(int(data_cfg["t_num"]), int(data_cfg["x_num"]))
|
| 63 |
+
t = res[:, 1].reshape(int(data_cfg["t_num"]), int(data_cfg["x_num"]))
|
| 64 |
+
target = exact_reaction_solution(x, t, cfg).astype(np.float32)
|
| 65 |
+
metrics = relative_errors(pred, target)
|
| 66 |
|
| 67 |
+
output_path = project_path(cfg["paths"]["prediction"])
|
| 68 |
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
| 69 |
+
np.savez(
|
| 70 |
+
output_path,
|
| 71 |
+
x=x.astype(np.float32),
|
| 72 |
+
t=t.astype(np.float32),
|
| 73 |
+
prediction=pred.astype(np.float32),
|
| 74 |
+
target=target,
|
| 75 |
+
absolute_error=np.abs(pred - target).astype(np.float32),
|
| 76 |
+
relative_l1=metrics["relative_l1"],
|
| 77 |
+
relative_l2=metrics["relative_l2"],
|
| 78 |
+
)
|
| 79 |
+
print(f"prediction saved to {output_path}")
|
| 80 |
+
print(f"relative L1 error: {metrics['relative_l1']:.6f}")
|
| 81 |
+
print(f"relative L2 error: {metrics['relative_l2']:.6f}")
|
| 82 |
|
| 83 |
|
| 84 |
if __name__ == "__main__":
|
| 85 |
+
main()
|
scripts/result.py
CHANGED
|
@@ -1,50 +1,71 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
import argparse
|
| 4 |
-
import json
|
| 5 |
-
from pathlib import Path
|
| 6 |
|
| 7 |
import numpy as np
|
| 8 |
|
| 9 |
-
from common import
|
| 10 |
|
| 11 |
|
| 12 |
-
def
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
args = parser.parse_args()
|
| 18 |
|
| 19 |
-
cfg =
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
"mae": float(np.mean(mae_values)),
|
| 40 |
-
}
|
| 41 |
-
output_dir.mkdir(parents=True, exist_ok=True)
|
| 42 |
-
metrics_path = output_dir / "metrics.json"
|
| 43 |
-
metrics_path.write_text(json.dumps(metrics, indent=2), encoding="utf-8")
|
| 44 |
-
print(json.dumps(metrics, indent=2))
|
| 45 |
-
print(f"wrote {metrics_path}")
|
| 46 |
-
return 0
|
| 47 |
|
| 48 |
|
| 49 |
if __name__ == "__main__":
|
| 50 |
-
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
import argparse
|
|
|
|
|
|
|
| 4 |
|
| 5 |
import numpy as np
|
| 6 |
|
| 7 |
+
from common import load_config, project_path, relative_errors, write_json
|
| 8 |
|
| 9 |
|
| 10 |
+
def plot_result(prediction: np.ndarray, target: np.ndarray, cfg: dict, figure_path) -> bool:
|
| 11 |
+
try:
|
| 12 |
+
import matplotlib
|
| 13 |
+
|
| 14 |
+
matplotlib.use("Agg")
|
| 15 |
+
import matplotlib.pyplot as plt
|
| 16 |
+
except Exception as exc:
|
| 17 |
+
print(f"matplotlib unavailable, skip figure: {exc}")
|
| 18 |
+
return False
|
| 19 |
+
|
| 20 |
+
figure_path.parent.mkdir(parents=True, exist_ok=True)
|
| 21 |
+
fig, axes = plt.subplots(1, 3, figsize=(12, 4))
|
| 22 |
+
x_min, x_max = cfg["data"]["x_range"]
|
| 23 |
+
t_min, t_max = cfg["data"]["t_range"]
|
| 24 |
+
items = [
|
| 25 |
+
(prediction, "Predicted u(x,t)"),
|
| 26 |
+
(target, "Exact u(x,t)"),
|
| 27 |
+
(np.abs(prediction - target), "Absolute Error"),
|
| 28 |
+
]
|
| 29 |
+
for ax, (data, title) in zip(axes, items):
|
| 30 |
+
image = ax.imshow(data, extent=[x_min, x_max, t_max, t_min], aspect="auto")
|
| 31 |
+
ax.set_xlabel("x")
|
| 32 |
+
ax.set_ylabel("t")
|
| 33 |
+
ax.set_title(title)
|
| 34 |
+
fig.colorbar(image, ax=ax)
|
| 35 |
+
|
| 36 |
+
plt.tight_layout()
|
| 37 |
+
plt.savefig(figure_path)
|
| 38 |
+
plt.close(fig)
|
| 39 |
+
return True
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def main() -> None:
|
| 43 |
+
parser = argparse.ArgumentParser(description="Evaluate and plot PINNsformer inference output.")
|
| 44 |
+
parser.add_argument("--config", default=None, help="Path to config.yaml.")
|
| 45 |
+
parser.add_argument("--prediction", default=None, help="Path to prediction .npz.")
|
| 46 |
args = parser.parse_args()
|
| 47 |
|
| 48 |
+
cfg = load_config(args.config)
|
| 49 |
+
prediction_path = project_path(args.prediction or cfg["paths"]["prediction"])
|
| 50 |
+
if not prediction_path.exists():
|
| 51 |
+
raise FileNotFoundError(f"Prediction file not found: {prediction_path}. Run scripts/inference.py first.")
|
| 52 |
+
|
| 53 |
+
data = np.load(prediction_path)
|
| 54 |
+
prediction = data["prediction"]
|
| 55 |
+
target = data["target"]
|
| 56 |
+
metrics = relative_errors(prediction, target)
|
| 57 |
+
|
| 58 |
+
metrics_path = project_path(cfg["paths"]["metrics"])
|
| 59 |
+
write_json(metrics_path, metrics)
|
| 60 |
+
figure_path = project_path(cfg["paths"]["figure"])
|
| 61 |
+
plotted = plot_result(prediction, target, cfg, figure_path)
|
| 62 |
+
|
| 63 |
+
print(f"metrics saved to {metrics_path}")
|
| 64 |
+
if plotted:
|
| 65 |
+
print(f"figure saved to {figure_path}")
|
| 66 |
+
print(f"relative L1 error: {metrics['relative_l1']:.6f}")
|
| 67 |
+
print(f"relative L2 error: {metrics['relative_l2']:.6f}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
|
| 70 |
if __name__ == "__main__":
|
| 71 |
+
main()
|
scripts/train.py
CHANGED
|
@@ -6,138 +6,167 @@ from pathlib import Path
|
|
| 6 |
import numpy as np
|
| 7 |
import torch
|
| 8 |
import torch.nn as nn
|
| 9 |
-
from torch.
|
|
|
|
|
|
|
| 10 |
|
| 11 |
from common import (
|
| 12 |
-
DEFAULT_CONFIG,
|
| 13 |
-
build_datapipe,
|
| 14 |
build_model,
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
initialize_distributed,
|
| 18 |
load_config,
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
)
|
| 23 |
|
| 24 |
|
| 25 |
-
def
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
x = x.to(device)
|
| 30 |
-
y = y.to(device)
|
| 31 |
-
grid = grid.to(device)
|
| 32 |
-
pred, target = predict_batch(model, x, y, grid, cfg)
|
| 33 |
-
if pred.numel() == 0:
|
| 34 |
-
continue
|
| 35 |
-
loss = loss_fn(pred.reshape(pred.size(0), -1), target.reshape(target.size(0), -1))
|
| 36 |
-
optimizer.zero_grad()
|
| 37 |
-
loss.backward()
|
| 38 |
-
optimizer.step()
|
| 39 |
-
losses.append(float(loss.item()))
|
| 40 |
-
return float(np.mean(losses)) if losses else 0.0
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
@torch.no_grad()
|
| 44 |
-
def validate(model, loader, loss_fn, device, cfg) -> float:
|
| 45 |
-
model.eval()
|
| 46 |
-
losses = []
|
| 47 |
-
for x, y, grid in loader:
|
| 48 |
-
x = x.to(device)
|
| 49 |
-
y = y.to(device)
|
| 50 |
-
grid = grid.to(device)
|
| 51 |
-
pred, target = predict_batch(model, x, y, grid, cfg)
|
| 52 |
-
if pred.numel() == 0:
|
| 53 |
-
continue
|
| 54 |
-
loss = loss_fn(pred.reshape(pred.size(0), -1), target.reshape(target.size(0), -1))
|
| 55 |
-
losses.append(float(loss.item()))
|
| 56 |
-
return float(np.mean(losses)) if losses else 0.0
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
def main() -> int:
|
| 60 |
-
parser = argparse.ArgumentParser(description="Train the standard PDENNEval FNO package.")
|
| 61 |
-
parser.add_argument("--config", default=str(DEFAULT_CONFIG), help="Path to conf/config.yaml")
|
| 62 |
-
parser.add_argument("--data-dir", default=None, help="Override datapipe.source.data_dir")
|
| 63 |
-
parser.add_argument("--output-dir", default=None, help="Override training.output_dir")
|
| 64 |
-
parser.add_argument("--force-local-datapipe", action="store_true")
|
| 65 |
-
args = parser.parse_args()
|
| 66 |
|
| 67 |
-
dist = initialize_distributed()
|
| 68 |
-
device = dist.device
|
| 69 |
-
cfg = prepare_config(load_config(args.config), data_dir=args.data_dir, output_dir=args.output_dir)
|
| 70 |
|
| 71 |
-
|
| 72 |
-
torch.
|
| 73 |
-
np.random.seed(seed + dist.rank)
|
| 74 |
|
| 75 |
-
output_dir = Path(cfg.training.output_dir)
|
| 76 |
-
if dist.rank == 0:
|
| 77 |
-
output_dir.mkdir(parents=True, exist_ok=True)
|
| 78 |
-
print(f"Output directory: {output_dir}")
|
| 79 |
-
print(f"Device: {device}, world_size={dist.world_size}")
|
| 80 |
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
)
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
optimizer_cfg = cfg.training.optimizer
|
| 94 |
-
scheduler_cfg = cfg.training.scheduler
|
| 95 |
-
optimizer = getattr(torch.optim, optimizer_cfg.name)(
|
| 96 |
-
model.parameters(),
|
| 97 |
-
lr=float(optimizer_cfg.lr),
|
| 98 |
-
weight_decay=float(optimizer_cfg.weight_decay),
|
| 99 |
)
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
)
|
| 105 |
-
loss_fn = nn.MSELoss()
|
| 106 |
|
| 107 |
-
if bool(get_attr(cfg.training, "continue_training", False)) and cfg.training.model_path:
|
| 108 |
-
model_to_load = model.module if hasattr(model, "module") else model
|
| 109 |
-
model_to_load.load_state_dict(load_model_state(Path(cfg.training.model_path), device))
|
| 110 |
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
|
|
|
|
| 115 |
for epoch in range(epochs):
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
|
| 142 |
if __name__ == "__main__":
|
| 143 |
-
|
|
|
|
| 6 |
import numpy as np
|
| 7 |
import torch
|
| 8 |
import torch.nn as nn
|
| 9 |
+
from torch.optim import Adam, LBFGS
|
| 10 |
+
|
| 11 |
+
from onescience.utils.pinnsformer_util import get_data, get_n_params, make_time_sequence
|
| 12 |
|
| 13 |
from common import (
|
|
|
|
|
|
|
| 14 |
build_model,
|
| 15 |
+
ensure_runtime_dirs,
|
| 16 |
+
initial_condition,
|
|
|
|
| 17 |
load_config,
|
| 18 |
+
project_path,
|
| 19 |
+
seed_everything,
|
| 20 |
+
select_device,
|
| 21 |
)
|
| 22 |
|
| 23 |
|
| 24 |
+
def init_weights(module: nn.Module) -> None:
|
| 25 |
+
if isinstance(module, nn.Linear):
|
| 26 |
+
torch.nn.init.xavier_uniform_(module.weight)
|
| 27 |
+
module.bias.data.fill_(0.01)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
+
def tensorize(array: np.ndarray, device: torch.device) -> torch.Tensor:
|
| 31 |
+
return torch.tensor(array, dtype=torch.float32, requires_grad=True, device=device)
|
|
|
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
+
def prepare_tensors(cfg: dict, device: torch.device, args: argparse.Namespace):
|
| 35 |
+
data_cfg = cfg["data"]
|
| 36 |
+
x_num = int(args.x_num or data_cfg["x_num"])
|
| 37 |
+
t_num = int(args.t_num or data_cfg["t_num"])
|
| 38 |
+
num_step = int(args.num_step or data_cfg["sequence"]["num_step"])
|
| 39 |
+
step = float(data_cfg["sequence"]["step"])
|
| 40 |
+
|
| 41 |
+
res, b_left, b_right, b_upper, b_lower = get_data(
|
| 42 |
+
data_cfg["x_range"],
|
| 43 |
+
data_cfg["t_range"],
|
| 44 |
+
x_num,
|
| 45 |
+
t_num,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
)
|
| 47 |
+
|
| 48 |
+
tensors = []
|
| 49 |
+
for values in (res, b_left, b_right, b_upper, b_lower):
|
| 50 |
+
tensors.append(tensorize(make_time_sequence(values, num_step=num_step, step=step), device))
|
| 51 |
+
|
| 52 |
+
return tuple(tensors)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def loss_components(model: nn.Module, tensors: tuple[torch.Tensor, ...], cfg: dict):
|
| 56 |
+
res, b_left, b_right, b_upper, b_lower = tensors
|
| 57 |
+
x_res, t_res = res[:, :, 0:1], res[:, :, 1:2]
|
| 58 |
+
x_left, t_left = b_left[:, :, 0:1], b_left[:, :, 1:2]
|
| 59 |
+
x_upper, t_upper = b_upper[:, :, 0:1], b_upper[:, :, 1:2]
|
| 60 |
+
x_lower, t_lower = b_lower[:, :, 0:1], b_lower[:, :, 1:2]
|
| 61 |
+
|
| 62 |
+
pred_res = model(x_res, t_res)
|
| 63 |
+
pred_left = model(x_left, t_left)
|
| 64 |
+
pred_upper = model(x_upper, t_upper)
|
| 65 |
+
pred_lower = model(x_lower, t_lower)
|
| 66 |
+
|
| 67 |
+
u_t = torch.autograd.grad(
|
| 68 |
+
pred_res,
|
| 69 |
+
t_res,
|
| 70 |
+
grad_outputs=torch.ones_like(pred_res),
|
| 71 |
+
retain_graph=True,
|
| 72 |
+
create_graph=True,
|
| 73 |
+
)[0]
|
| 74 |
+
|
| 75 |
+
rate = float(cfg["equation"]["reaction_rate"])
|
| 76 |
+
target_ic = initial_condition(x_left[:, 0, :], cfg)
|
| 77 |
+
loss_res = torch.mean((u_t - rate * pred_res * (1 - pred_res)) ** 2)
|
| 78 |
+
loss_bc = torch.mean((pred_upper - pred_lower) ** 2)
|
| 79 |
+
loss_ic = torch.mean((pred_left[:, 0, :] - target_ic) ** 2)
|
| 80 |
+
loss = loss_res + loss_bc + loss_ic
|
| 81 |
+
return loss, (loss_res, loss_bc, loss_ic)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def build_optimizer(model: nn.Module, cfg: dict):
|
| 85 |
+
opt_cfg = cfg["training"]["optimizer"]
|
| 86 |
+
name = opt_cfg["name"].lower()
|
| 87 |
+
if name == "adam":
|
| 88 |
+
return Adam(model.parameters(), lr=float(opt_cfg.get("lr", 1e-3)))
|
| 89 |
+
if name == "lbfgs":
|
| 90 |
+
return LBFGS(
|
| 91 |
+
model.parameters(),
|
| 92 |
+
lr=float(opt_cfg.get("lr", 1.0)),
|
| 93 |
+
max_iter=int(opt_cfg.get("max_iter", 20)),
|
| 94 |
+
line_search_fn=opt_cfg.get("line_search_fn", "strong_wolfe"),
|
| 95 |
+
)
|
| 96 |
+
raise ValueError(f"Unsupported optimizer: {opt_cfg['name']}")
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def save_checkpoint(path: Path, model: nn.Module, cfg: dict, loss_history: list[list[float]]) -> None:
|
| 100 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 101 |
+
torch.save(
|
| 102 |
+
{
|
| 103 |
+
"model_state_dict": model.state_dict(),
|
| 104 |
+
"config": cfg,
|
| 105 |
+
"loss_history": loss_history,
|
| 106 |
+
},
|
| 107 |
+
path,
|
| 108 |
)
|
|
|
|
| 109 |
|
|
|
|
|
|
|
|
|
|
| 110 |
|
| 111 |
+
def main() -> None:
|
| 112 |
+
parser = argparse.ArgumentParser(description="Train PINNsformer on the 1D reaction equation.")
|
| 113 |
+
parser.add_argument("--config", default=None, help="Path to config.yaml.")
|
| 114 |
+
parser.add_argument("--epochs", type=int, default=None, help="Override training epochs.")
|
| 115 |
+
parser.add_argument("--x-num", type=int, default=None, help="Override x grid count.")
|
| 116 |
+
parser.add_argument("--t-num", type=int, default=None, help="Override t grid count.")
|
| 117 |
+
parser.add_argument("--num-step", type=int, default=None, help="Override pseudo-sequence length.")
|
| 118 |
+
parser.add_argument("--device", default=None, help="Override runtime.device.")
|
| 119 |
+
args = parser.parse_args()
|
| 120 |
+
|
| 121 |
+
cfg = load_config(args.config)
|
| 122 |
+
ensure_runtime_dirs(cfg)
|
| 123 |
+
seed_everything(int(cfg["runtime"]["seed"]))
|
| 124 |
+
device = select_device(args.device or cfg["runtime"]["device"])
|
| 125 |
+
|
| 126 |
+
tensors = prepare_tensors(cfg, device, args)
|
| 127 |
+
model = build_model(cfg).to(device)
|
| 128 |
+
model.apply(init_weights)
|
| 129 |
+
optimizer = build_optimizer(model, cfg)
|
| 130 |
+
epochs = int(args.epochs or cfg["training"]["epochs"])
|
| 131 |
+
|
| 132 |
+
print(model)
|
| 133 |
+
print(f"parameters: {get_n_params(model)}")
|
| 134 |
+
print(f"device: {device}")
|
| 135 |
|
| 136 |
+
loss_history: list[list[float]] = []
|
| 137 |
for epoch in range(epochs):
|
| 138 |
+
latest: dict[str, float] = {}
|
| 139 |
+
|
| 140 |
+
def closure():
|
| 141 |
+
loss, parts = loss_components(model, tensors, cfg)
|
| 142 |
+
optimizer.zero_grad()
|
| 143 |
+
loss.backward()
|
| 144 |
+
latest["loss"] = float(loss.detach().cpu())
|
| 145 |
+
latest["loss_res"] = float(parts[0].detach().cpu())
|
| 146 |
+
latest["loss_bc"] = float(parts[1].detach().cpu())
|
| 147 |
+
latest["loss_ic"] = float(parts[2].detach().cpu())
|
| 148 |
+
return loss
|
| 149 |
+
|
| 150 |
+
if isinstance(optimizer, LBFGS):
|
| 151 |
+
optimizer.step(closure)
|
| 152 |
+
else:
|
| 153 |
+
closure()
|
| 154 |
+
optimizer.step()
|
| 155 |
+
|
| 156 |
+
loss_history.append([latest["loss_res"], latest["loss_bc"], latest["loss_ic"], latest["loss"]])
|
| 157 |
+
print(
|
| 158 |
+
f"epoch {epoch + 1}/{epochs} "
|
| 159 |
+
f"loss={latest['loss']:.6f} "
|
| 160 |
+
f"res={latest['loss_res']:.6f} "
|
| 161 |
+
f"bc={latest['loss_bc']:.6f} "
|
| 162 |
+
f"ic={latest['loss_ic']:.6f}"
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
checkpoint = project_path(cfg["training"]["checkpoint"])
|
| 166 |
+
save_checkpoint(checkpoint, model, cfg, loss_history)
|
| 167 |
+
np.save(project_path(cfg["paths"]["loss"]), np.asarray(loss_history, dtype=np.float32))
|
| 168 |
+
print(f"checkpoint saved to {checkpoint}")
|
| 169 |
|
| 170 |
|
| 171 |
if __name__ == "__main__":
|
| 172 |
+
main()
|