Upload folder using huggingface_hub
Browse files- .gitattributes +24 -10
- README.md +140 -0
- conf/config.yaml +46 -0
- configuration.json +1 -0
- model/__init__.py +3 -0
- model/pinnsformer.py +146 -0
- result/.gitkeep +1 -0
- scripts/common.py +100 -0
- scripts/fake_data.py +31 -0
- scripts/inference.py +85 -0
- scripts/result.py +71 -0
- scripts/train.py +172 -0
- weight/.gitkeep +1 -0
.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
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
language:
|
| 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 |
+
|
| 42 |
+
## 1. OneCode
|
| 43 |
+
|
| 44 |
+
Use the online OneCode environment for an intelligent, one-click AI for Science (AI4S) programming experience:
|
| 45 |
+
|
| 46 |
+
[Launch OneCode for one-click AI4S programming](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home)
|
| 47 |
+
|
| 48 |
+
## 2. Manual Setup
|
| 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 |
+
hf download --model OneScience-Sugon/PINNsformer --local-dir ./PINNsformer
|
| 60 |
+
cd PINNsformer
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
### Set Up the Runtime Environment
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
**DCU Environment**
|
| 67 |
+
|
| 68 |
+
```bash
|
| 69 |
+
# Activate DTK and Conda first
|
| 70 |
+
conda create -n onescience311 python=3.11 -y
|
| 71 |
+
conda activate onescience311
|
| 72 |
+
# Installation with uv is also supported
|
| 73 |
+
pip install onescience[cfd-dcu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
**GPU Environment**
|
| 77 |
+
```bash
|
| 78 |
+
# Activate Conda first
|
| 79 |
+
conda create -n onescience311 python=3.11 -y libstdcxx-ng=12 libgcc-ng=12 gcc_linux-64=12 gxx_linux-64=12
|
| 80 |
+
conda activate onescience311
|
| 81 |
+
# Installation with uv is also supported
|
| 82 |
+
pip install onescience[cfd-gpu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 83 |
+
```
|
| 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 |
+
|
| 114 |
+
```bash
|
| 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 |
+
|
| 126 |
+
```bash
|
| 127 |
+
python scripts/result.py
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
# Official OneScience Resources
|
| 131 |
+
|
| 132 |
+
| Platform | OneScience Repository | Skills Repository |
|
| 133 |
+
| --- | --- | --- |
|
| 134 |
+
| Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
|
| 135 |
+
| GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
|
| 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
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"framework":"Pytorch","task":"other","model":"PINNsformer","domain":"cfd"}
|
model/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 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()
|
weight/.gitkeep
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|