OneScience commited on
Commit
ff0fadf
·
verified ·
1 Parent(s): c7fd066

Upload folder using huggingface_hub

Browse files
README.md ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ frameworks:
3
+ - ""
4
+ language:
5
+ - en
6
+ license: apache-2.0
7
+ tags:
8
+ - OneScience
9
+ - fluid dynamics
10
+ - neural PDE solver evaluation
11
+ - unstructured-mesh simulation
12
+ - multiphysics modeling
13
+ ---
14
+ <p align="center">
15
+ <strong>
16
+ <span style="font-size: 30px;">CFD_Benchmark</span>
17
+ </strong>
18
+ </p>
19
+
20
+ # Model Overview
21
+ CFD_Benchmark is an open-source deep-learning benchmark library for research on neural partial differential equation (PDE) solvers. It extends Tsinghua University's open-source Neural-Solver-Library with Distributed Data Parallel (DDP) training support, additional models, and new datasets, while retaining the original neural-operator and physical-field modeling framework. The library supports neural PDE solver evaluation, deep-learning research for CFD, multi-model performance comparisons, large-scale distributed training experiments, physical simulation dataset development, and algorithm benchmarking.
22
+
23
+ The library currently supports the following benchmarks:
24
+
25
+ - Six standard benchmarks from [[FNO]](https://arxiv.org/abs/2010.08895) and [[geo-FNO]](https://arxiv.org/abs/2207.05209)
26
+ - PDEBench [[NeurIPS 2022 Track dataset and benchmark]](https://arxiv.org/abs/2210.07182) for autoregressive tasks
27
+ - The ShapeNet-Car dataset [[TOG 2018]](https://dl.acm.org/doi/abs/10.1145/3197517.3201325) for industrial design benchmarks
28
+ - The BubbleML [[Multiphase Multiphysics Dataset]](https://arxiv.org/abs/2307.14623) for studying multiphysics phase-transition phenomena
29
+
30
+ ---
31
+
32
+ ## Supported Neural Solvers
33
+
34
+ The following neural PDE solvers are supported:
35
+
36
+
37
+ - **Transolver** - Transolver: A Fast Transformer Solver for PDEs on General Geometries [[ICML 2024]](https://arxiv.org/abs/2402.02366) [[Code]](https://github.com/thuml/Neural-Solver-Library/blob/main/models/Transolver.py)
38
+ - **ONO** - Improved Operator Learning by Orthogonal Attention [[ICML 2024]](https://arxiv.org/abs/2310.12487v3) [[Code]](https://github.com/thuml/Neural-Solver-Library/blob/main/models/ONO.py)
39
+ - **Factformer** - Scalable Transformer for PDE Surrogate Modeling [[NeurIPS 2023]](https://arxiv.org/abs/2305.17560) [[Code]](https://github.com/thuml/Neural-Solver-Library/blob/main/models/Factformer.py)
40
+ - **U-NO** - U-NO: U-shaped Neural Operators [[TMLR 2023]](https://openreview.net/pdf?id=j3oQF9coJd) [[Code]](https://github.com/thuml/Neural-Solver-Library/blob/main/models/U_NO.py)
41
+ - **LSM** - Solving High-Dimensional PDEs with Latent Spectral Models [[ICML 2023]](https://arxiv.org/pdf/2301.12664) [[Code]](https://github.com/thuml/Neural-Solver-Library/blob/main/models/LSM.py)
42
+ - **GNOT** - GNOT: A General Neural Operator Transformer for Operator Learning [[ICML 2023]](https://arxiv.org/abs/2302.14376) [[Code]](https://github.com/thuml/Neural-Solver-Library/blob/main/models/GNOT.py)
43
+ - **F-FNO** - Factorized Fourier Neural Operators [[ICLR 2023]](https://arxiv.org/abs/2111.13802) [[Code]](https://github.com/thuml/Neural-Solver-Library/blob/main/models/F_FNO.py)
44
+ - **U-FNO** - An enhanced Fourier neural operator-based deep-learning model for multiphase flow [[Advances in Water Resources 2022]](https://www.sciencedirect.com/science/article/pii/S0309170822000562) [[Code]](https://github.com/thuml/Neural-Solver-Library/blob/main/models/U_FNO.py)
45
+ - **Galerkin Transformer** - Choose a Transformer: Fourier or Galerkin [[NeurIPS 2021]](https://arxiv.org/abs/2105.14995) [[Code]](https://github.com/thuml/Neural-Solver-Library/blob/main/models/Galerkin_Transformer.py)
46
+ - **MWT** - Multiwavelet-based Operator Learning for Differential Equations [[NeurIPS 2021]](https://openreview.net/forum?id=LZDiWaC9CGL) [[Code]](https://github.com/thuml/Neural-Solver-Library/blob/main/models/MWT.py)
47
+ - **FNO** - Fourier Neural Operator for Parametric Partial Differential Equations [[ICLR 2021]](https://arxiv.org/pdf/2010.08895) [[Code]](https://github.com/thuml/Neural-Solver-Library/blob/main/models/FNO.py)
48
+ - **Transformer** - Attention Is All You Need [[NeurIPS 2017]](https://arxiv.org/pdf/1706.03762) [[Code]](https://github.com/thuml/Neural-Solver-Library/blob/main/models/Transformer.py)
49
+
50
+ - **GFNO** - Group Equivariant Fourier Neural Operators for Partial Differential Equations[[2023 Poster]](https://arxiv.org/pdf/1706.03762)[[Code]](https://github.com/divelab/AIRS/blob/main/OpenPDE/G-FNO/models/GFNO.py)
51
+
52
+ Several vision architectures also serve as effective baselines for structured-geometry tasks:
53
+
54
+ - **Swin Transformer** - Swin Transformer: Hierarchical Vision Transformer using Shifted Windows [[ICCV 2021]](https://arxiv.org/abs/2103.14030) [[Code]](https://github.com/thuml/Neural-Solver-Library/blob/main/models/Swin_Transformer.py)
55
+ - **U-Net** - U-Net: Convolutional Networks for Biomedical Image Segmentation [[MICCAI 2015]](https://arxiv.org/pdf/1505.04597) [[Code]](https://github.com/thuml/Neural-Solver-Library/blob/main/models/U_Net.py)
56
+
57
+ Several established geometric deep-learning models are included for design tasks:
58
+
59
+ - **Graph-UNet** - Graph U-Nets [[ICML 2019]](https://arxiv.org/pdf/1905.05178) [[Code]](https://github.com/thuml/Neural-Solver-Library/blob/main/models/Graph_UNet.py)
60
+ - **GraphSAGE** - Inductive Representation Learning on Large Graphs [[NeurIPS 2017]](https://arxiv.org/pdf/1706.02216) [[Code]](https://github.com/thuml/Neural-Solver-Library/blob/main/models/GraphSAGE.py)
61
+ - **PointNet** - PointNet: Deep Learning on Point Sets for 3D Classification and Segmentation [[CVPR 2017]](https://arxiv.org/pdf/1612.00593) [[Code]](https://github.com/thuml/Neural-Solver-Library/blob/main/models/PointNet.py)
62
+
63
+ The library also includes the following graph neural network:
64
+
65
+ - **MeshGraphNet** LEARNING MESH-BASED SIMULATION WITH GRAPH NETWORKS[ICLR 2021](https://arxiv.org/abs/2010.03409) [[Code]](https://github.com/google-deepmind/deepmind-research/tree/master/meshgraphnets)
66
+
67
+
68
+
69
+ ## Use Cases
70
+
71
+ | Use Case | Description |
72
+ |---|---|
73
+ | Neural PDE solver evaluation | Train, run inference with, and compare models such as FNO, Transolver, GNOT, ONO, and U-NO through a unified workflow |
74
+ | Autoregressive physical prediction | Predict the temporal evolution of PDE states step by step using datasets such as PDEBench |
75
+ | Multiphysics modeling | Study multiphase flows, multiphysics coupling, and phase-transition phenomena using datasets such as BubbleML |
76
+ | ModelScope/OneCode execution | Download the standalone model package, install its dependencies, and run the provided scripts |
77
+
78
+
79
+ # Usage
80
+
81
+ ## 1. OneCode
82
+
83
+ Use the online OneCode environment for an intelligent, one-click AI for Science (AI4S) programming experience:
84
+
85
+ [Launch OneCode for one-click AI4S programming](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home)
86
+
87
+ ## 2. Manual Setup
88
+
89
+ **Hardware Requirements**
90
+
91
+ - A GPU or DCU is recommended.
92
+ - A CPU can be used for import checks and small-scale pipeline validation, but full training and inference will be slow.
93
+ - DCU users must install DTK in advance. DTK 25.04.2 or later, or the OneScience-recommended version for the target cluster, is recommended.
94
+
95
+
96
+ ### Download the Model Package
97
+
98
+ ```bash
99
+ modelscope download --model OneScience/CFD_Benchmark --local_dir ./CFD_Benchmark
100
+ cd CFD_Benchmark
101
+ ```
102
+
103
+ ### Set Up the Runtime Environment
104
+
105
+
106
+ **DCU Environment**
107
+
108
+ ```bash
109
+ # Activate DTK and Conda first
110
+ conda create -n onescience311 python=3.11 -y
111
+ conda activate onescience311
112
+ # Installation with uv is also supported
113
+ pip install onescience[cfd-dcu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
114
+ ```
115
+
116
+ **GPU Environment**
117
+ ```bash
118
+ # Activate Conda first
119
+ conda create -n onescience311 python=3.11 -y libstdcxx-ng=12 libgcc-ng=12 gcc_linux-64=12 gxx_linux-64=12
120
+ conda activate onescience311
121
+ # Installation with uv is also supported
122
+ pip install onescience[cfd-gpu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
123
+ ```
124
+
125
+ ### Training Data
126
+
127
+ Use the dataset links in the benchmark overview above to download the required data.
128
+
129
+ The six standard benchmark datasets from [[FNO]](https://arxiv.org/abs/2010.08895) and [[geo-FNO]](https://arxiv.org/abs/2207.05209) are available from [this link](https://drive.google.com/drive/folders/1YBuaoTdOSr_qzaow-G-iwvbUI7fiUzu8).
130
+
131
+ The PDEBench [[NeurIPS 2022 Track dataset and benchmark]](https://arxiv.org/abs/2210.07182), used for benchmarking autoregressive tasks, is available from [this link](https://darus.uni-stuttgart.de/dataset.xhtml?persistentId=doi:10.18419/darus-2986).
132
+
133
+ The ShapeNet-Car [[TOG 2018]](https://dl.acm.org/doi/abs/10.1145/3197517.3201325) benchmark dataset for industrial design tasks is available from [[this link]](http://www.nobuyuki-umetani.com/publication/mlcfd_data.zip).
134
+
135
+ The BubbleML [[Multiphase Multiphysics Dataset]](https://arxiv.org/abs/2307.14623), designed for research on multiphysics phase-transition phenomena, is available from [[this link]](https://github.com/HPCForge/BubbleML/blob/main/bubbleml_data/README.md).
136
+
137
+ The OneScience community also provides the `cfd_benchmark` dataset for training. Download it with the command below and verify that the data path in `conf/config.yaml` is configured correctly:
138
+ ```
139
+ modelscope download --dataset OneScience/cfd_benchmark --local_dir ./data
140
+ ```
141
+ ### Training
142
+
143
+ ```bash
144
+ python scripts/train.py
145
+ ```
146
+
147
+ ### Model Weights
148
+ This repository will provide weights trained on the OneScience `cfd_benchmark` dataset in the `weights/` directory. The weights will be uploaded soon.
149
+
150
+ ### Inference
151
+
152
+ ```bash
153
+ python scripts/inference.py
154
+ ```
155
+
156
+ Inference loads the trained weights referenced by `paths.weight_path` and writes the metrics to:
157
+
158
+ ```text
159
+ ./results/{train.save_name}/metrics.json
160
+ ```
161
+
162
+
163
+ # Official OneScience Resources
164
+
165
+ | Platform | OneScience Repository | Skills Repository |
166
+ | --- | --- | --- |
167
+ | Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
168
+ | GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
169
+
170
+ # Citations and License
171
+ - Reference repository: [Neural-Solver-Library](https://github.com/thuml/Neural-Solver-Library).
172
+ - This repository retains source attribution and has been adapted for automated execution through OneScience and ModelScope.
config/config.yaml ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ project:
2
+ name: CFD_Benchmark
3
+ package: cfd_benchmark
4
+
5
+ runtime:
6
+ device: cpu
7
+
8
+ paths:
9
+ data_dir: ./data/fake_airfoil
10
+ checkpoint_dir: ./checkpoints
11
+ result_dir: ./results
12
+ weight_path: ./checkpoints/transolver_fake.pt
13
+
14
+ data:
15
+ loader: airfoil
16
+ task: steady
17
+ ntrain: 2
18
+ ntest: 1
19
+ batch_size: 1
20
+ downsamplex: 20
21
+ downsampley: 10
22
+ normalize: false
23
+ norm_type: UnitTransformer
24
+ space_dim: 2
25
+ fun_dim: 0
26
+ out_dim: 1
27
+ geotype: unstructured
28
+
29
+ model:
30
+ name: Transolver
31
+ n_hidden: 16
32
+ n_layers: 1
33
+ n_heads: 2
34
+ mlp_ratio: 1
35
+ dropout: 0.0
36
+ act: gelu
37
+ slice_num: 4
38
+ unified_pos: 0
39
+ ref: 4
40
+
41
+ train:
42
+ epochs: 1
43
+ lr: 0.001
44
+ weight_decay: 0.00001
45
+ optimizer: AdamW
46
+ scheduler: CosineAnnealingLR
47
+ pct_start: 0.3
48
+ max_grad_norm: null
49
+ derivloss: false
50
+ use_checkpoint: false
51
+ checkpoint_layers: ""
52
+ save_name: transolver_fake
53
+
54
+ inference:
55
+ eval: 1
56
+ vis_num: 0
configuration.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"framework":"Pytorch","task":"other"}
model/DeepONet.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ from onescience.modules.mlp.MLP import StandardMLP
4
+ from onescience.modules.embedding import timestep_embedding, unified_pos_embedding
5
+
6
+ class Model(nn.Module):
7
+ """
8
+ DeepONet 模型。
9
+
10
+ 采用双网络架构 (Branch Net 和 Trunk Net) 逼近非线性算子。
11
+ - Branch Net 处理输入函数 (如初始条件、边界条件)。
12
+ - Trunk Net 处理目标评估点的坐标 (物理位置)。
13
+ 两者输出特征做内积后得到目标位置的物理场预测值。
14
+ """
15
+ def __init__(self, args, device):
16
+ super(Model, self).__init__()
17
+ self.__name__ = "DeepONet"
18
+ self.args = args
19
+
20
+ # 位置处理
21
+ if args.unified_pos and args.geotype != "unstructured":
22
+ self.pos = unified_pos_embedding(args.shapelist, args.ref, device=device)
23
+ trunk_in_dim = args.ref ** len(args.shapelist)
24
+ else:
25
+ trunk_in_dim = args.space_dim
26
+
27
+ # 分支网络输入维度(函数输入+时间嵌入)
28
+ branch_in_dim = args.fun_dim
29
+ if args.time_input:
30
+ branch_in_dim += args.n_hidden # 增加时间嵌入维度
31
+
32
+ # 分支网络(函数空间)
33
+ self.branch_net = StandardMLP(
34
+ input_dim=branch_in_dim,
35
+ output_dim=args.n_hidden,
36
+ hidden_dims=[args.n_hidden] * args.branch_depth,
37
+ activation=args.act,
38
+ use_bias=True
39
+ )
40
+
41
+ # 主干网络(物理空间)
42
+ self.trunk_net = StandardMLP(
43
+ input_dim=trunk_in_dim,
44
+ output_dim=args.n_hidden,
45
+ hidden_dims=[args.n_hidden] * args.trunk_depth,
46
+ activation=args.act,
47
+ use_bias=True
48
+ )
49
+
50
+ # 时间处理层
51
+ if args.time_input:
52
+ self.time_fc = nn.Sequential(
53
+ nn.Linear(args.n_hidden, args.n_hidden),
54
+ nn.SiLU(),
55
+ nn.Linear(args.n_hidden, args.n_hidden),
56
+ )
57
+
58
+ # 输出层 - 支持多个输出通道
59
+ self.out_layer = nn.Linear(args.n_hidden, args.out_dim)
60
+ self.bias = nn.Parameter(torch.zeros(1, 1, args.out_dim))
61
+
62
+ def structured_forward(self, x, fx, T=None, geo=None):
63
+ B, N, _ = x.shape # x: [B, N, space_dim]
64
+ if self.args.unified_pos:
65
+ x = self.pos.repeat(B, 1, 1) # [B, N, d]
66
+
67
+ # 处理时间信息
68
+ if T is not None and self.args.time_input:
69
+ # 确保时间输入维度正确 [B, 1] -> [B]
70
+ T = T.view(-1)
71
+
72
+ # 生成时间嵌入 [B, D]
73
+ T_emb = timestep_embedding(T, self.args.n_hidden)
74
+
75
+ # 通过时间处理层 [B, D] -> [B, D]
76
+ T_emb = self.time_fc(T_emb)
77
+
78
+ # 扩展时间嵌入以匹配空间点 [B, D] -> [B, N, D]
79
+ T_emb_expanded = T_emb.unsqueeze(1).expand(-1, N, -1)
80
+
81
+ # 将时间嵌入连接到函数输入
82
+ if fx is not None:
83
+ # [B, N, fun_dim] + [B, N, D] = [B, N, fun_dim + D]
84
+ fx = torch.cat([fx, T_emb_expanded], dim=-1)
85
+ else:
86
+ fx = T_emb_expanded # [B, N, D]
87
+
88
+ # 分支网络处理
89
+ branch_feat = self.branch_net(fx) # [B, N, D]
90
+
91
+ # 主干网络处理
92
+ trunk_feat = self.trunk_net(x) # [B, N, D]
93
+
94
+ # 点积操作 + 输出层
95
+ inner = branch_feat * trunk_feat
96
+ out = self.out_layer(inner) + self.bias
97
+ return out
98
+
99
+ def unstructured_forward(self, x, fx, T=None):
100
+ B, N, _ = x.shape
101
+ if T is not None and self.args.time_input:
102
+ T = T.view(-1)
103
+ T_emb = timestep_embedding(T, self.args.n_hidden)
104
+ T_emb = self.time_fc(T_emb)
105
+ T_emb_expanded = T_emb.unsqueeze(1).expand(-1, N, -1)
106
+ if fx is not None:
107
+ fx = torch.cat([fx, T_emb_expanded], dim=-1)
108
+ else:
109
+ fx = T_emb_expanded
110
+
111
+ branch_feat = self.branch_net(fx)
112
+ trunk_feat = self.trunk_net(x)
113
+
114
+ inner = branch_feat * trunk_feat
115
+ out = self.out_layer(inner) + self.bias
116
+ return out
117
+
118
+ def forward(self, x, fx, T=None, geo=None):
119
+ if self.args.geotype == "unstructured":
120
+ return self.unstructured_forward(x, fx, T)
121
+ else:
122
+ return self.structured_forward(x, fx, T)
model/FNO.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import math
3
+ import torch.nn as nn
4
+ import numpy as np
5
+ import torch.nn.functional as F
6
+
7
+ from onescience.modules.fourier.fno_layers import (
8
+ SpectralConv1d,
9
+ SpectralConv2d,
10
+ SpectralConv3d,
11
+ )
12
+ from onescience.modules.fourier.geo_spectral import GeoSpectralConv2d, IPHI
13
+ from onescience.modules.mlp.MLP import StandardMLP
14
+ from onescience.modules.embedding import timestep_embedding, unified_pos_embedding
15
+
16
+ ConvList = [None, nn.Conv1d, nn.Conv2d, nn.Conv3d]
17
+
18
+
19
+ class Model(nn.Module):
20
+ """
21
+ 傅里叶神经算子 (Fourier Neural Operator, FNO)。
22
+ 支持 1D/2D/3D 结构化网格,以及基于 Geo-FNO 的非结构化网格。
23
+ """
24
+ def __init__(self, args, device, s1=96, s2=96):
25
+ super(Model, self).__init__()
26
+ self.__name__ = "FNO"
27
+ self.args = args
28
+
29
+ # ==========================================
30
+ # 1. Embedding & Preprocess
31
+ # ==========================================
32
+ if args.unified_pos and args.geotype != "unstructured": # structured mesh
33
+ self.pos = unified_pos_embedding(args.shapelist, args.ref, device=device)
34
+ input_dim = args.fun_dim + args.ref ** len(args.shapelist)
35
+ else:
36
+ input_dim = args.fun_dim + args.space_dim
37
+
38
+ self.preprocess = StandardMLP(
39
+ input_dim=input_dim,
40
+ hidden_dims=[args.n_hidden * 2],
41
+ output_dim=args.n_hidden,
42
+ activation=args.act,
43
+ n_layers=0,
44
+ res=False,
45
+ )
46
+
47
+ if args.time_input:
48
+ self.time_fc = nn.Sequential(
49
+ nn.Linear(args.n_hidden, args.n_hidden),
50
+ nn.SiLU(),
51
+ nn.Linear(args.n_hidden, args.n_hidden),
52
+ )
53
+
54
+ # ==========================================
55
+ # 2. Geometry Projection (GeoFNO 特有)
56
+ # ==========================================
57
+ if self.args.geotype == "unstructured":
58
+ # 明确传入具体参数
59
+ self.fftproject_in = GeoSpectralConv2d(
60
+ in_channels=args.n_hidden,
61
+ out_channels=args.n_hidden,
62
+ modes1=args.modes,
63
+ modes2=args.modes,
64
+ s1=s1,
65
+ s2=s2
66
+ )
67
+ self.fftproject_out = GeoSpectralConv2d(
68
+ in_channels=args.n_hidden,
69
+ out_channels=args.n_hidden,
70
+ modes1=args.modes,
71
+ modes2=args.modes,
72
+ s1=s1,
73
+ s2=s2
74
+ )
75
+ self.iphi = IPHI()
76
+ self.padding = [(16 - size % 16) % 16 for size in [s1, s2]]
77
+ else:
78
+ self.padding = [(16 - size % 16) % 16 for size in args.shapelist]
79
+
80
+ # ==========================================
81
+ # 3. FNO Blocks (显式参数实例化)
82
+ # ==========================================
83
+ dim = len(self.padding)
84
+
85
+ if dim == 1:
86
+ self.conv0 = SpectralConv1d(in_channels=args.n_hidden, out_channels=args.n_hidden, modes1=args.modes)
87
+ self.conv1 = SpectralConv1d(in_channels=args.n_hidden, out_channels=args.n_hidden, modes1=args.modes)
88
+ self.conv2 = SpectralConv1d(in_channels=args.n_hidden, out_channels=args.n_hidden, modes1=args.modes)
89
+ self.conv3 = SpectralConv1d(in_channels=args.n_hidden, out_channels=args.n_hidden, modes1=args.modes)
90
+ elif dim == 2:
91
+ self.conv0 = SpectralConv2d(in_channels=args.n_hidden, out_channels=args.n_hidden, modes1=args.modes, modes2=args.modes)
92
+ self.conv1 = SpectralConv2d(in_channels=args.n_hidden, out_channels=args.n_hidden, modes1=args.modes, modes2=args.modes)
93
+ self.conv2 = SpectralConv2d(in_channels=args.n_hidden, out_channels=args.n_hidden, modes1=args.modes, modes2=args.modes)
94
+ self.conv3 = SpectralConv2d(in_channels=args.n_hidden, out_channels=args.n_hidden, modes1=args.modes, modes2=args.modes)
95
+ elif dim == 3:
96
+ self.conv0 = SpectralConv3d(in_channels=args.n_hidden, out_channels=args.n_hidden, modes1=args.modes, modes2=args.modes, modes3=args.modes)
97
+ self.conv1 = SpectralConv3d(in_channels=args.n_hidden, out_channels=args.n_hidden, modes1=args.modes, modes2=args.modes, modes3=args.modes)
98
+ self.conv2 = SpectralConv3d(in_channels=args.n_hidden, out_channels=args.n_hidden, modes1=args.modes, modes2=args.modes, modes3=args.modes)
99
+ self.conv3 = SpectralConv3d(in_channels=args.n_hidden, out_channels=args.n_hidden, modes1=args.modes, modes2=args.modes, modes3=args.modes)
100
+ else:
101
+ raise ValueError(f"Unsupported dimension: {dim}. Only 1D, 2D, and 3D are supported.")
102
+
103
+ # 对应的 1x1 卷积通道混合层
104
+ self.w0 = ConvList[dim](args.n_hidden, args.n_hidden, 1)
105
+ self.w1 = ConvList[dim](args.n_hidden, args.n_hidden, 1)
106
+ self.w2 = ConvList[dim](args.n_hidden, args.n_hidden, 1)
107
+ self.w3 = ConvList[dim](args.n_hidden, args.n_hidden, 1)
108
+
109
+ # ==========================================
110
+ # 4. Projectors (输出层)
111
+ # ==========================================
112
+ self.fc1 = nn.Linear(args.n_hidden, args.n_hidden)
113
+ self.fc2 = nn.Linear(args.n_hidden, args.out_dim)
114
+
115
+ def structured_geo(self, x, fx, T=None):
116
+ B, N, _ = x.shape
117
+ if self.args.unified_pos:
118
+ x = self.pos.repeat(x.shape[0], 1, 1)
119
+
120
+ if fx is not None:
121
+ fx = torch.cat((x, fx), -1)
122
+ fx = self.preprocess(fx)
123
+ else:
124
+ fx = self.preprocess(x)
125
+
126
+ if T is not None:
127
+ Time_emb = timestep_embedding(T, self.args.n_hidden).repeat(1, x.shape[1], 1)
128
+ Time_emb = self.time_fc(Time_emb)
129
+ fx = fx + Time_emb
130
+
131
+ x = fx.permute(0, 2, 1).reshape(B, self.args.n_hidden, *self.args.shapelist)
132
+
133
+ # Padding
134
+ if not all(item == 0 for item in self.padding):
135
+ if len(self.args.shapelist) == 2:
136
+ x = F.pad(x, [0, self.padding[1], 0, self.padding[0]])
137
+ elif len(self.args.shapelist) == 3:
138
+ x = F.pad(x, [0, self.padding[2], 0, self.padding[1], 0, self.padding[0]])
139
+
140
+ # Spectral Convs + Res connections
141
+ x = F.gelu(self.conv0(x) + self.w0(x))
142
+ x = F.gelu(self.conv1(x) + self.w1(x))
143
+ x = F.gelu(self.conv2(x) + self.w2(x))
144
+ x = self.conv3(x) + self.w3(x)
145
+
146
+ # Unpadding
147
+ if not all(item == 0 for item in self.padding):
148
+ if len(self.args.shapelist) == 2:
149
+ x = x[..., : -self.padding[0], : -self.padding[1]]
150
+ elif len(self.args.shapelist) == 3:
151
+ x = x[..., : -self.padding[0], : -self.padding[1], : -self.padding[2]]
152
+
153
+ x = x.reshape(B, self.args.n_hidden, -1).permute(0, 2, 1)
154
+ x = F.gelu(self.fc1(x))
155
+ x = self.fc2(x)
156
+ return x
157
+
158
+ def unstructured_geo(self, x, fx, T=None):
159
+ original_pos = x
160
+ if fx is not None:
161
+ fx = torch.cat((x, fx), -1)
162
+ fx = self.preprocess(fx)
163
+ else:
164
+ fx = self.preprocess(x)
165
+
166
+ if T is not None:
167
+ Time_emb = timestep_embedding(T, self.args.n_hidden).repeat(1, x.shape[1], 1)
168
+ Time_emb = self.time_fc(Time_emb)
169
+ fx = fx + Time_emb
170
+
171
+ # 透传参数到 GeoSpectralConv2d
172
+ x = self.fftproject_in(
173
+ fx.permute(0, 2, 1), x_in=original_pos, iphi=self.iphi, code=None
174
+ )
175
+
176
+ x = F.gelu(self.conv0(x) + self.w0(x))
177
+ x = F.gelu(self.conv1(x) + self.w1(x))
178
+ x = F.gelu(self.conv2(x) + self.w2(x))
179
+ x = self.conv3(x) + self.w3(x)
180
+
181
+ # 透传参数到 GeoSpectralConv2d
182
+ x = self.fftproject_out(
183
+ x, x_out=original_pos, iphi=self.iphi, code=None
184
+ ).permute(0, 2, 1)
185
+
186
+ x = F.gelu(self.fc1(x))
187
+ x = self.fc2(x)
188
+ return x
189
+
190
+ def forward(self, x, fx, T=None, geo=None):
191
+ if self.args.geotype == "unstructured":
192
+ return self.unstructured_geo(x, fx, T)
193
+ else:
194
+ return self.structured_geo(x, fx, T)
model/F_FNO.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ import numpy as np
5
+
6
+ from onescience.modules.fourier.ffno_layers import (
7
+ SpectralConv1d,
8
+ SpectralConv2d,
9
+ SpectralConv3d,
10
+ )
11
+ from onescience.modules.mlp import StandardMLP
12
+ from onescience.modules.embedding import timestep_embedding, unified_pos_embedding
13
+ from onescience.modules.fourier.geo_spectral import GeoSpectralConv2d, IPHI
14
+
15
+ SpectralConvList = [None, SpectralConv1d, SpectralConv2d, SpectralConv3d]
16
+
17
+ class Model(nn.Module):
18
+ """
19
+ Factorized Fourier Neural Operator (F-FNO) 模型。
20
+ """
21
+ def __init__(self, args, device, s1=96, s2=96):
22
+ super(Model, self).__init__()
23
+ self.__name__ = "F-FNO"
24
+ self.args = args
25
+ self.device = device
26
+
27
+ # 1. Embedding & Preprocessing
28
+ input_dim = args.fun_dim
29
+
30
+ if args.unified_pos and args.geotype != "unstructured":
31
+ self.pos = unified_pos_embedding(args.shapelist, args.ref, device=device)
32
+ input_dim += args.ref ** len(args.shapelist)
33
+ else:
34
+ input_dim += args.space_dim
35
+
36
+ self.preprocess = StandardMLP(
37
+ input_dim=input_dim,
38
+ output_dim=args.n_hidden,
39
+ hidden_dims=[args.n_hidden * 2],
40
+ activation=args.act,
41
+ use_bias=True
42
+ )
43
+
44
+ if args.time_input:
45
+ self.time_fc = nn.Sequential(
46
+ nn.Linear(args.n_hidden, args.n_hidden),
47
+ nn.SiLU(),
48
+ nn.Linear(args.n_hidden, args.n_hidden),
49
+ )
50
+
51
+ # 2. Geometry Projection & Spectral Layers
52
+ self.spectral_layers = nn.ModuleList([])
53
+
54
+ if self.args.geotype == "unstructured":
55
+ # --- 非结构化网格路径 (GeoFNO) ---
56
+ self.fftproject_in = GeoSpectralConv2d(
57
+ in_channels=args.n_hidden,
58
+ out_channels=args.n_hidden,
59
+ modes1=args.modes,
60
+ modes2=args.modes,
61
+ s1=s1,
62
+ s2=s2
63
+ )
64
+
65
+ self.fftproject_out = GeoSpectralConv2d(
66
+ in_channels=args.n_hidden,
67
+ out_channels=args.n_hidden,
68
+ modes1=args.modes,
69
+ modes2=args.modes,
70
+ s1=s1,
71
+ s2=s2
72
+ )
73
+
74
+ self.iphi = IPHI()
75
+ self.padding = [(16 - size % 16) % 16 for size in [s1, s2]]
76
+
77
+ # 中间层使用 FFNO
78
+ spectral_class = SpectralConv2d
79
+ conv_args = {
80
+ "in_dim": args.n_hidden,
81
+ "out_dim": args.n_hidden,
82
+ "modes_x": args.modes,
83
+ "modes_y": args.modes
84
+ }
85
+
86
+ else:
87
+ # --- 结构化网格路径 (FFNO) ---
88
+ self.padding = [(16 - size % 16) % 16 for size in args.shapelist]
89
+
90
+ dim = len(self.padding)
91
+ spectral_class = SpectralConvList[dim]
92
+
93
+ conv_args = {
94
+ "in_dim": args.n_hidden,
95
+ "out_dim": args.n_hidden,
96
+ }
97
+
98
+ mode_names = ["modes_x", "modes_y", "modes_z"]
99
+ for i in range(dim):
100
+ if i < len(mode_names):
101
+ conv_args[mode_names[i]] = args.modes
102
+
103
+ for _ in range(args.n_layers):
104
+ self.spectral_layers.append(
105
+ spectral_class(**conv_args)
106
+ )
107
+
108
+ # 3. Projectors (Decoder)
109
+ self.fc1 = nn.Linear(args.n_hidden, args.n_hidden)
110
+ self.fc2 = nn.Linear(args.n_hidden, args.out_dim)
111
+
112
+ def structured_geo(self, x, fx, T=None):
113
+ B, N, _ = x.shape
114
+
115
+ if self.args.unified_pos:
116
+ x = self.pos.repeat(x.shape[0], 1, 1)
117
+
118
+ if fx is not None:
119
+ fx = torch.cat((x, fx), -1)
120
+ fx = self.preprocess(fx)
121
+ else:
122
+ fx = self.preprocess(x)
123
+
124
+ if T is not None:
125
+ # 【修复】使用广播机制,避免 repeat 导致的维度错误
126
+ Time_emb = timestep_embedding(T, self.args.n_hidden) # (B, C)
127
+ Time_emb = self.time_fc(Time_emb)
128
+ if Time_emb.ndim == 2:
129
+ Time_emb = Time_emb.unsqueeze(1) # (B, 1, C)
130
+ fx = fx + Time_emb # Broadcasting: (B, N, C) + (B, 1, C)
131
+
132
+ x = fx.permute(0, 2, 1).reshape(B, self.args.n_hidden, *self.args.shapelist)
133
+
134
+ if not all(item == 0 for item in self.padding):
135
+ pad_arg = []
136
+ for p in reversed(self.padding):
137
+ pad_arg.extend([0, p])
138
+ x = F.pad(x, pad_arg)
139
+
140
+ for i in range(self.args.n_layers):
141
+ x = x + self.spectral_layers[i](x)
142
+
143
+ if not all(item == 0 for item in self.padding):
144
+ if len(self.args.shapelist) == 1:
145
+ x = x[..., :-self.padding[0]]
146
+ elif len(self.args.shapelist) == 2:
147
+ x = x[..., :-self.padding[0], :-self.padding[1]]
148
+ elif len(self.args.shapelist) == 3:
149
+ x = x[..., :-self.padding[0], :-self.padding[1], :-self.padding[2]]
150
+
151
+ x = x.reshape(B, self.args.n_hidden, -1).permute(0, 2, 1)
152
+ x = self.fc1(x)
153
+ x = F.gelu(x)
154
+ x = self.fc2(x)
155
+ return x
156
+
157
+ def unstructured_geo(self, x, fx, T=None):
158
+ original_pos = x
159
+
160
+ if fx is not None:
161
+ fx = torch.cat((x, fx), -1)
162
+ fx = self.preprocess(fx)
163
+ else:
164
+ fx = self.preprocess(x)
165
+
166
+ if T is not None:
167
+ # 【修复】使用广播机制,避免 repeat 导致的维度错误
168
+ Time_emb = timestep_embedding(T, self.args.n_hidden) # (B, C)
169
+ Time_emb = self.time_fc(Time_emb)
170
+ if Time_emb.ndim == 2:
171
+ Time_emb = Time_emb.unsqueeze(1) # (B, 1, C)
172
+ fx = fx + Time_emb # Broadcasting: (B, N, C) + (B, 1, C)
173
+
174
+ x = self.fftproject_in(
175
+ fx.permute(0, 2, 1), x_in=original_pos, iphi=self.iphi, code=None
176
+ )
177
+
178
+ for i in range(self.args.n_layers):
179
+ x = x + self.spectral_layers[i](x)
180
+
181
+ x = self.fftproject_out(
182
+ x, x_out=original_pos, iphi=self.iphi, code=None
183
+ ).permute(0, 2, 1)
184
+
185
+ x = self.fc1(x)
186
+ x = F.gelu(x)
187
+ x = self.fc2(x)
188
+ return x
189
+
190
+ def forward(self, x, fx, T=None, geo=None):
191
+ if self.args.geotype == "unstructured":
192
+ return self.unstructured_geo(x, fx, T)
193
+ else:
194
+ return self.structured_geo(x, fx, T)
model/Factformer.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ from timm.layers import trunc_normal_
4
+ from onescience.modules.mlp.MLP import StandardMLP
5
+ from onescience.modules.transformer.factformer_block import Factformer_block
6
+ from onescience.modules.embedding import timestep_embedding, unified_pos_embedding
7
+
8
+ class Model(nn.Module):
9
+ """
10
+ FactFormer 模型。
11
+
12
+ 基于 Factorized Attention 的 Transformer 架构,用于处理结构化网格数据。
13
+
14
+ """
15
+ def __init__(self, args, device):
16
+ super(Model, self).__init__()
17
+ self.args = args
18
+ self.__name__ = "Factformer"
19
+ # 1. 几何类型检查
20
+ if args.geotype == "unstructured":
21
+ raise ValueError(
22
+ "Factformer does not support unstructured geometry, please try to integrate GeoFNO layer"
23
+ )
24
+
25
+ # 2. Embedding & Preprocessing
26
+ input_dim = args.fun_dim
27
+ if args.unified_pos: # only for structured mesh
28
+ self.pos = unified_pos_embedding(args.shapelist, args.ref, device=device)
29
+ input_dim += args.ref ** len(args.shapelist)
30
+ else:
31
+ input_dim += args.space_dim
32
+
33
+ self.preprocess = StandardMLP(
34
+ input_dim=input_dim,
35
+ output_dim=args.n_hidden,
36
+ hidden_dims=[args.n_hidden * 2],
37
+ activation=args.act,
38
+ use_bias=True,
39
+ res=False,
40
+ )
41
+
42
+ if args.time_input:
43
+ self.time_fc = nn.Sequential(
44
+ nn.Linear(args.n_hidden, args.n_hidden),
45
+ nn.SiLU(),
46
+ nn.Linear(args.n_hidden, args.n_hidden),
47
+ )
48
+
49
+ # 3. Transformer Blocks
50
+ self.blocks = nn.ModuleList([
51
+ Factformer_block(
52
+ num_heads=args.n_heads,
53
+ hidden_dim=args.n_hidden,
54
+ dropout=args.dropout,
55
+ act=args.act,
56
+ mlp_ratio=args.mlp_ratio,
57
+ out_dim=args.out_dim,
58
+ last_layer=(_ == args.n_layers - 1),
59
+ geotype=args.geotype,
60
+ shapelist=args.shapelist,
61
+ )
62
+ for _ in range(args.n_layers)
63
+ ])
64
+
65
+ self.placeholder = nn.Parameter(
66
+ (1 / (args.n_hidden)) * torch.rand(args.n_hidden, dtype=torch.float)
67
+ )
68
+ self.initialize_weights()
69
+
70
+ def initialize_weights(self):
71
+ self.apply(self._init_weights)
72
+
73
+ def _init_weights(self, m):
74
+ if isinstance(m, nn.Linear):
75
+ trunc_normal_(m.weight, std=0.02)
76
+ if isinstance(m, nn.Linear) and m.bias is not None:
77
+ nn.init.constant_(m.bias, 0)
78
+ elif isinstance(m, (nn.LayerNorm, nn.BatchNorm1d)):
79
+ nn.init.constant_(m.bias, 0)
80
+ nn.init.constant_(m.weight, 1.0)
81
+
82
+ def structured_geo(self, x, fx, T=None):
83
+ if self.args.unified_pos:
84
+ x = self.pos.repeat(x.shape[0], 1, 1)
85
+
86
+ if fx is not None:
87
+ fx = torch.cat((x, fx), -1)
88
+ fx = self.preprocess(fx)
89
+ else:
90
+ fx = self.preprocess(x)
91
+
92
+ fx = fx + self.placeholder[None, None, :]
93
+
94
+ if T is not None:
95
+ Time_emb = timestep_embedding(T, self.args.n_hidden) # (B, C)
96
+ Time_emb = self.time_fc(Time_emb)
97
+ if Time_emb.ndim == 2:
98
+ Time_emb = Time_emb.unsqueeze(1) # (B, 1, C)
99
+ fx = fx + Time_emb
100
+
101
+ for block in self.blocks:
102
+ fx = block(fx)
103
+ return fx
104
+
105
+ def unstructured_geo(self, x, fx, T=None):
106
+ # 保持对非结构化网格的异常抛出
107
+ raise ValueError(
108
+ "Factformer does not support unstructured geometry, please try to integrate GeoFNO layer"
109
+ )
110
+
111
+ def forward(self, x, fx, T=None, geo=None):
112
+ if self.args.geotype == "unstructured":
113
+ return self.unstructured_geo(x, fx, T)
114
+ else:
115
+ return self.structured_geo(x, fx, T)
model/GFNO.py ADDED
@@ -0,0 +1,264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ import numpy as np
5
+
6
+ from onescience.modules.equivariant.group_conv import GroupEquivariantConv2d
7
+ from onescience.modules.fourier.group_spectral import GSpectralConv2d
8
+ from onescience.modules.mlp.GMLP import GroupEquivariantMLP2d
9
+ from onescience.modules.mlp.MLP import StandardMLP
10
+ from onescience.modules.embedding import timestep_embedding, unified_pos_embedding
11
+ from onescience.modules.fourier.geo_spectral import GeoSpectralConv2d, IPHI
12
+
13
+ class GNorm(nn.Module):
14
+ def __init__(self, width, group_size):
15
+ super().__init__()
16
+ self.group_size = group_size
17
+ self.norm = torch.nn.InstanceNorm3d(width)
18
+
19
+ def forward(self, x):
20
+ # x shape: (B, C*Group, H, W) -> (B, C, Group, H, W)
21
+ x = x.view(x.shape[0], -1, self.group_size, x.shape[-2], x.shape[-1])
22
+ x = self.norm(x)
23
+ x = x.view(x.shape[0], -1, x.shape[-2], x.shape[-1])
24
+ return x
25
+
26
+ class Model(nn.Module):
27
+ """
28
+ GFNO (Group Factorized Neural Operator) 模型。
29
+
30
+ """
31
+ def __init__(self, args, device, s1=96, s2=96):
32
+ super(Model, self).__init__()
33
+ self.__name__ = "GFNO"
34
+ self.args = args
35
+ self.device = device
36
+
37
+ self.in_channels = args.fun_dim
38
+ self.out_channels = args.out_dim
39
+ self.modes = args.modes
40
+ self.width = args.n_hidden
41
+
42
+ reflection = False
43
+
44
+ # 1. Embedding & Preprocessing
45
+ if args.unified_pos and args.geotype != "unstructured":
46
+ self.pos = unified_pos_embedding(args.shapelist, args.ref, device=device)
47
+ in_dim = args.fun_dim + args.ref ** len(args.shapelist)
48
+ else:
49
+ in_dim = args.fun_dim + args.space_dim
50
+
51
+ self.preprocess = StandardMLP(
52
+ input_dim=in_dim,
53
+ output_dim=args.n_hidden,
54
+ hidden_dims=[args.n_hidden * 2],
55
+ activation=args.act,
56
+ use_bias=True
57
+ )
58
+
59
+ # 2. Time Embedding
60
+ if args.time_input:
61
+ self.time_fc = nn.Sequential(
62
+ nn.Linear(args.n_hidden, args.n_hidden),
63
+ nn.SiLU(),
64
+ nn.Linear(args.n_hidden, args.n_hidden),
65
+ )
66
+ else:
67
+ self.time_fc = None
68
+
69
+ # 3. Geometry Projection & Output Heads
70
+ if args.geotype == "unstructured":
71
+ self.group_size = 4 * (1 + reflection)
72
+
73
+ self.fftproject_in = GeoSpectralConv2d(
74
+ in_channels=args.n_hidden,
75
+ out_channels=args.n_hidden,
76
+ modes1=args.modes,
77
+ modes2=args.modes,
78
+ s1=s1,
79
+ s2=s2,
80
+ )
81
+ self.fftproject_out = GeoSpectralConv2d(
82
+ in_channels=self.width * self.group_size,
83
+ out_channels=self.width,
84
+ modes1=args.modes,
85
+ modes2=args.modes,
86
+ s1=s1,
87
+ s2=s2,
88
+ )
89
+ self.iphi = IPHI()
90
+
91
+ grid_h, grid_w = s1, s2
92
+ self.point_q = nn.Sequential(
93
+ nn.Linear(self.width, self.width * 4),
94
+ nn.GELU(),
95
+ nn.Linear(self.width * 4, self.out_channels),
96
+ )
97
+ else:
98
+ grid_h, grid_w = args.shapelist
99
+ # 结构化输出头 (Explicit instantiation)
100
+ self.q = GroupEquivariantMLP2d(
101
+ in_channels=self.width,
102
+ out_channels=self.out_channels,
103
+ mid_channels=self.width * 4,
104
+ reflection=reflection,
105
+ last_layer=True,
106
+ )
107
+
108
+ self.padding = [(16 - size % 16) % 16 for size in [grid_h, grid_w]]
109
+
110
+ # 4. GFNO Stem Layers
111
+ # Lifting Layer
112
+ self.p = GroupEquivariantConv2d(
113
+ in_channels=args.n_hidden,
114
+ out_channels=self.width,
115
+ kernel_size=1,
116
+ reflection=reflection,
117
+ first_layer=True,
118
+ )
119
+
120
+ # Spectral Layers
121
+ self.conv0 = GSpectralConv2d(in_channels=self.width, out_channels=self.width, modes=self.modes, reflection=reflection)
122
+ self.conv1 = GSpectralConv2d(in_channels=self.width, out_channels=self.width, modes=self.modes, reflection=reflection)
123
+ self.conv2 = GSpectralConv2d(in_channels=self.width, out_channels=self.width, modes=self.modes, reflection=reflection)
124
+ self.conv3 = GSpectralConv2d(in_channels=self.width, out_channels=self.width, modes=self.modes, reflection=reflection)
125
+
126
+ self.mlp0 = GroupEquivariantMLP2d(
127
+ in_channels=self.width,
128
+ out_channels=self.width,
129
+ mid_channels=self.width,
130
+ reflection=reflection
131
+ )
132
+ self.mlp1 = GroupEquivariantMLP2d(
133
+ in_channels=self.width,
134
+ out_channels=self.width,
135
+ mid_channels=self.width,
136
+ reflection=reflection
137
+ )
138
+ self.mlp2 = GroupEquivariantMLP2d(
139
+ in_channels=self.width,
140
+ out_channels=self.width,
141
+ mid_channels=self.width,
142
+ reflection=reflection
143
+ )
144
+ self.mlp3 = GroupEquivariantMLP2d(
145
+ in_channels=self.width,
146
+ out_channels=self.width,
147
+ mid_channels=self.width,
148
+ reflection=reflection
149
+ )
150
+
151
+ # Residual Weights (1x1 GConv)
152
+ self.w0 = GroupEquivariantConv2d(
153
+ in_channels=self.width,
154
+ out_channels=self.width,
155
+ kernel_size=1,
156
+ reflection=reflection
157
+ )
158
+ self.w1 = GroupEquivariantConv2d(
159
+ in_channels=self.width,
160
+ out_channels=self.width,
161
+ kernel_size=1,
162
+ reflection=reflection
163
+ )
164
+ self.w2 = GroupEquivariantConv2d(
165
+ in_channels=self.width,
166
+ out_channels=self.width,
167
+ kernel_size=1,
168
+ reflection=reflection
169
+ )
170
+ self.w3 = GroupEquivariantConv2d(
171
+ in_channels=self.width,
172
+ out_channels=self.width,
173
+ kernel_size=1,
174
+ reflection=reflection
175
+ )
176
+
177
+ self.norm = GNorm(self.width, group_size=4 * (1 + reflection))
178
+
179
+ def _gfno_stem_forward(self, x):
180
+ # x: (B, C, H, W)
181
+ x = self.p(x) # Lifting
182
+
183
+ # 4 Layers
184
+ x1 = self.norm(self.conv0(self.norm(x)))
185
+ x1 = self.mlp0(x1)
186
+ x = F.gelu(x1 + self.w0(x))
187
+
188
+ x1 = self.norm(self.conv1(self.norm(x)))
189
+ x1 = self.mlp1(x1)
190
+ x = F.gelu(x1 + self.w1(x))
191
+
192
+ x1 = self.norm(self.conv2(self.norm(x)))
193
+ x1 = self.mlp2(x1)
194
+ x = F.gelu(x1 + self.w2(x))
195
+
196
+ x1 = self.norm(self.conv3(self.norm(x)))
197
+ x1 = self.mlp3(x1)
198
+ x = x1 + self.w3(x)
199
+ return x
200
+
201
+ def structured_geo(self, x, fx, T=None):
202
+ B, N, _ = x.shape
203
+
204
+ if self.args.unified_pos:
205
+ pos = self.pos.repeat(B, 1, 1)
206
+ feats = torch.cat([pos, fx], dim=-1) if fx is not None else pos
207
+ else:
208
+ feats = torch.cat([x, fx], dim=-1) if fx is not None else x
209
+
210
+ feats = self.preprocess(feats)
211
+
212
+ if (T is not None) and (self.time_fc is not None):
213
+ t_emb = timestep_embedding(T, self.args.n_hidden)
214
+ t_emb = self.time_fc(t_emb)
215
+ if t_emb.ndim == 2:
216
+ t_emb = t_emb.unsqueeze(1)
217
+ feats = feats + t_emb # Broadcast
218
+
219
+ H, W = self.args.shapelist
220
+ xg = feats.permute(0, 2, 1).reshape(B, self.args.n_hidden, H, W)
221
+
222
+ if not all(p == 0 for p in self.padding):
223
+ xg = F.pad(xg, [0, self.padding[1], 0, self.padding[0]])
224
+
225
+ xg = self._gfno_stem_forward(xg)
226
+
227
+ if not all(p == 0 for p in self.padding):
228
+ xg = xg[..., : -self.padding[0], : -self.padding[1]]
229
+
230
+ xg = self.q(xg)
231
+ out = xg.reshape(B, self.out_channels, -1).permute(0, 2, 1)
232
+ return out
233
+
234
+ def unstructured_geo(self, x, fx, T=None):
235
+ B, N, _ = x.shape
236
+
237
+ feats = torch.cat([x, fx], dim=-1) if fx is not None else x
238
+ feats = self.preprocess(feats)
239
+
240
+ if (T is not None) and (self.time_fc is not None):
241
+ t_emb = timestep_embedding(T, self.args.n_hidden)
242
+ t_emb = self.time_fc(t_emb)
243
+ if t_emb.ndim == 2:
244
+ t_emb = t_emb.unsqueeze(1)
245
+ feats = feats + t_emb
246
+
247
+ xg = self.fftproject_in(
248
+ feats.permute(0, 2, 1), x_in=x, iphi=self.iphi, code=None
249
+ )
250
+
251
+ xg = self._gfno_stem_forward(xg)
252
+
253
+ xp = self.fftproject_out(xg, x_out=x, iphi=self.iphi, code=None).permute(
254
+ 0, 2, 1
255
+ )
256
+
257
+ out = self.point_q(xp)
258
+ return out
259
+
260
+ def forward(self, x, fx=None, T=None, geo=None):
261
+ if self.args.geotype == "unstructured":
262
+ return self.unstructured_geo(x, fx, T)
263
+ else:
264
+ return self.structured_geo(x, fx, T)
model/GNOT.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ from timm.layers import trunc_normal_
5
+ from onescience.modules.mlp.MLP import StandardMLP
6
+ from onescience.modules.transformer.gnot_transformer_block import GNOTTransformerBlock
7
+ from onescience.modules.embedding import timestep_embedding, unified_pos_embedding
8
+
9
+ class Model(nn.Module):
10
+ """
11
+ GNOT (General Neural Operator Transformer) 模型。
12
+ """
13
+ def __init__(self, args, device, n_experts=3):
14
+ super(Model, self).__init__()
15
+ self.__name__ = "GNOT"
16
+ self.args = args
17
+
18
+ # 1. Embedding & Preprocessing
19
+ # -----------------------------------------------------------
20
+ if args.unified_pos and args.geotype != "unstructured":
21
+ self.pos = unified_pos_embedding(args.shapelist, args.ref, device=device)
22
+ dim_x = args.ref ** len(args.shapelist)
23
+ dim_z = args.fun_dim + args.ref ** len(args.shapelist)
24
+ else:
25
+ dim_x = args.space_dim
26
+ dim_z = args.fun_dim + args.space_dim
27
+
28
+ self.preprocess_x = StandardMLP(
29
+ input_dim=dim_x,
30
+ output_dim=args.n_hidden,
31
+ hidden_dims=[args.n_hidden * 2],
32
+ activation=args.act,
33
+ use_bias=True
34
+ )
35
+
36
+ self.preprocess_z = StandardMLP(
37
+ input_dim=dim_z,
38
+ output_dim=args.n_hidden,
39
+ hidden_dims=[args.n_hidden * 2],
40
+ activation=args.act,
41
+ use_bias=True
42
+ )
43
+
44
+ if args.time_input:
45
+ self.time_fc = nn.Sequential(
46
+ nn.Linear(args.n_hidden, args.n_hidden),
47
+ nn.SiLU(),
48
+ nn.Linear(args.n_hidden, args.n_hidden),
49
+ )
50
+
51
+ # 2. Transformer Blocks (MoE Style)
52
+ # -----------------------------------------------------------
53
+ self.blocks = nn.ModuleList([
54
+ GNOTTransformerBlock(
55
+ num_heads=args.n_heads,
56
+ hidden_dim=args.n_hidden,
57
+ dropout=args.dropout,
58
+ act=args.act,
59
+ mlp_ratio=args.mlp_ratio,
60
+ space_dim=args.space_dim,
61
+ n_experts=n_experts,
62
+ )
63
+ for _ in range(args.n_layers)
64
+ ])
65
+
66
+ self.placeholder = nn.Parameter(
67
+ (1 / (args.n_hidden)) * torch.rand(args.n_hidden, dtype=torch.float)
68
+ )
69
+
70
+ # 3. Projectors (Decoder)
71
+ self.fc1 = nn.Linear(args.n_hidden, args.n_hidden * 2)
72
+ self.fc2 = nn.Linear(args.n_hidden * 2, args.out_dim)
73
+
74
+ self.initialize_weights()
75
+
76
+ def initialize_weights(self):
77
+ self.apply(self._init_weights)
78
+
79
+ def _init_weights(self, m):
80
+ if isinstance(m, nn.Linear):
81
+ trunc_normal_(m.weight, std=0.02)
82
+ if isinstance(m, nn.Linear) and m.bias is not None:
83
+ nn.init.constant_(m.bias, 0)
84
+ elif isinstance(m, (nn.LayerNorm, nn.BatchNorm1d)):
85
+ nn.init.constant_(m.bias, 0)
86
+ nn.init.constant_(m.weight, 1.0)
87
+
88
+ def forward(self, x, fx, T=None, geo=None):
89
+ pos = x
90
+ if self.args.unified_pos:
91
+ x = self.pos.repeat(x.shape[0], 1, 1)
92
+
93
+ if fx is not None:
94
+ fx = torch.cat((x, fx), -1)
95
+ fx = self.preprocess_z(fx)
96
+ else:
97
+ fx = self.preprocess_z(x)
98
+
99
+ fx = fx + self.placeholder[None, None, :]
100
+ x = self.preprocess_x(x) # x here becomes embedding of geometric info
101
+
102
+ if T is not None:
103
+ Time_emb = timestep_embedding(T, self.args.n_hidden) # (B, C)
104
+ Time_emb = self.time_fc(Time_emb)
105
+ if Time_emb.ndim == 2:
106
+ Time_emb = Time_emb.unsqueeze(1) # (B, 1, C)
107
+ fx = fx + Time_emb
108
+
109
+ for block in self.blocks:
110
+ # GNOT block 需要三个参数: x(geo_emb), fx(phys_emb), pos(coords)
111
+ fx = block(x, fx, pos)
112
+
113
+ fx = self.fc1(fx)
114
+ fx = F.gelu(fx)
115
+ fx = self.fc2(fx)
116
+ return fx
model/Galerkin_Transformer.py ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # import torch
2
+ # import torch.nn as nn
3
+ # import torch.nn.functional as F
4
+ # import numpy as np
5
+ # from timm.layers import trunc_normal_
6
+ # from onescience.modules.layer.layers.Basic import MLP
7
+ # from onescience.modules.attention.linearattention import LinearAttention
8
+ # from onescience.modules.embedding import timestep_embedding, unified_pos_embedding
9
+ # from einops import rearrange, repeat
10
+ # from einops.layers.torch import Rearrange
11
+
12
+
13
+ # class Galerkin_Transformer_block(nn.Module):
14
+ # """Transformer encoder block."""
15
+
16
+ # def __init__(
17
+ # self,
18
+ # num_heads: int,
19
+ # hidden_dim: int,
20
+ # dropout: float,
21
+ # act="gelu",
22
+ # mlp_ratio=4,
23
+ # last_layer=False,
24
+ # out_dim=1,
25
+ # ):
26
+ # super().__init__()
27
+ # self.last_layer = last_layer
28
+ # self.ln_1 = nn.LayerNorm(hidden_dim)
29
+ # self.ln_1a = nn.LayerNorm(hidden_dim)
30
+ # self.Attn = LinearAttention(
31
+ # hidden_dim,
32
+ # heads=num_heads,
33
+ # dim_head=hidden_dim // num_heads,
34
+ # dropout=dropout,
35
+ # attn_type="galerkin",
36
+ # )
37
+ # self.ln_2 = nn.LayerNorm(hidden_dim)
38
+ # self.mlp = MLP(
39
+ # hidden_dim,
40
+ # hidden_dim * mlp_ratio,
41
+ # hidden_dim,
42
+ # n_layers=0,
43
+ # res=False,
44
+ # act=act,
45
+ # )
46
+ # if self.last_layer:
47
+ # self.ln_3 = nn.LayerNorm(hidden_dim)
48
+ # self.mlp2 = nn.Linear(hidden_dim, out_dim)
49
+
50
+ # def forward(self, fx):
51
+ # fx = self.Attn(self.ln_1(fx), self.ln_1a(fx)) + fx
52
+ # fx = self.mlp(self.ln_2(fx)) + fx
53
+ # if self.last_layer:
54
+ # return self.mlp2(self.ln_3(fx))
55
+ # else:
56
+ # return fx
57
+
58
+
59
+ # class Model(nn.Module):
60
+ # ## Galerkin_Transformer
61
+ # def __init__(self, args, device):
62
+ # super(Model, self).__init__()
63
+ # self.__name__ = "Galerkin_Transformer"
64
+ # self.args = args
65
+ # ## embedding
66
+ # if (
67
+ # args.unified_pos and args.geotype != "unstructured"
68
+ # ): # only for structured mesh
69
+ # self.pos = unified_pos_embedding(args.shapelist, args.ref, device=device)
70
+ # self.preprocess = MLP(
71
+ # args.fun_dim + args.ref ** len(args.shapelist),
72
+ # args.n_hidden * 2,
73
+ # args.n_hidden,
74
+ # n_layers=0,
75
+ # res=False,
76
+ # act=args.act,
77
+ # )
78
+ # else:
79
+ # self.preprocess = MLP(
80
+ # args.fun_dim + args.space_dim,
81
+ # args.n_hidden * 2,
82
+ # args.n_hidden,
83
+ # n_layers=0,
84
+ # res=False,
85
+ # act=args.act,
86
+ # )
87
+ # if args.time_input:
88
+ # self.time_fc = nn.Sequential(
89
+ # nn.Linear(args.n_hidden, args.n_hidden),
90
+ # nn.SiLU(),
91
+ # nn.Linear(args.n_hidden, args.n_hidden),
92
+ # )
93
+
94
+ # ## models
95
+ # self.blocks = nn.ModuleList(
96
+ # [
97
+ # Galerkin_Transformer_block(
98
+ # num_heads=args.n_heads,
99
+ # hidden_dim=args.n_hidden,
100
+ # dropout=args.dropout,
101
+ # act=args.act,
102
+ # mlp_ratio=args.mlp_ratio,
103
+ # out_dim=args.out_dim,
104
+ # last_layer=(_ == args.n_layers - 1),
105
+ # )
106
+ # for _ in range(args.n_layers)
107
+ # ]
108
+ # )
109
+ # self.placeholder = nn.Parameter(
110
+ # (1 / (args.n_hidden)) * torch.rand(args.n_hidden, dtype=torch.float)
111
+ # )
112
+ # self.initialize_weights()
113
+
114
+ # def initialize_weights(self):
115
+ # self.apply(self._init_weights)
116
+
117
+ # def _init_weights(self, m):
118
+ # if isinstance(m, nn.Linear):
119
+ # trunc_normal_(m.weight, std=0.02)
120
+ # if isinstance(m, nn.Linear) and m.bias is not None:
121
+ # nn.init.constant_(m.bias, 0)
122
+ # elif isinstance(m, (nn.LayerNorm, nn.BatchNorm1d)):
123
+ # nn.init.constant_(m.bias, 0)
124
+ # nn.init.constant_(m.weight, 1.0)
125
+
126
+ # def forward(self, x, fx, T=None, geo=None):
127
+ # if self.args.unified_pos:
128
+ # x = self.pos.repeat(x.shape[0], 1, 1)
129
+ # if fx is not None:
130
+ # fx = torch.cat((x, fx), -1)
131
+ # fx = self.preprocess(fx)
132
+ # else:
133
+ # fx = self.preprocess(x)
134
+ # fx = fx + self.placeholder[None, None, :]
135
+
136
+ # if T is not None:
137
+ # Time_emb = timestep_embedding(T, self.args.n_hidden) # (B, C)
138
+ # Time_emb = self.time_fc(Time_emb) # (B, C)
139
+
140
+ # if Time_emb.ndim == 2:
141
+ # Time_emb = Time_emb.unsqueeze(1) # (B, 1, C)
142
+
143
+ # fx = fx + Time_emb # Broadcasting: (B, N, C) + (B, 1, C) -> (B, N, C)
144
+
145
+ # for block in self.blocks:
146
+ # fx = block(fx)
147
+ # return fx
148
+
149
+
150
+
151
+ import torch
152
+ import torch.nn as nn
153
+ from timm.layers import trunc_normal_
154
+
155
+ # --- 引入模块工厂 ---
156
+ from onescience.modules.mlp.MLP import StandardMLP
157
+ from onescience.modules.transformer.galerkin_transformer_block import Galerkin_Transformer_block
158
+ from onescience.modules.embedding import timestep_embedding, unified_pos_embedding
159
+
160
+ class Model(nn.Module):
161
+ """
162
+ Galerkin Transformer 模型。
163
+
164
+ 使用 Galerkin 线性注意力机制处理物理场数据的 Transformer 架构。
165
+ """
166
+ def __init__(self, args, device):
167
+ super(Model, self).__init__()
168
+ self.__name__ = "Galerkin_Transformer"
169
+ self.args = args
170
+
171
+ # 1. Embedding & Preprocessing
172
+ # -----------------------------------------------------------
173
+ input_dim = args.fun_dim
174
+ if args.unified_pos and args.geotype != "unstructured":
175
+ self.pos = unified_pos_embedding(args.shapelist, args.ref, device=device)
176
+ input_dim += args.ref ** len(args.shapelist)
177
+ else:
178
+ input_dim += args.space_dim
179
+
180
+ # 对应原代码: MLP(input_dim, hidden*2, hidden, n_layers=0)
181
+ self.preprocess = StandardMLP(
182
+ input_dim=input_dim,
183
+ output_dim=args.n_hidden,
184
+ hidden_dims=[args.n_hidden * 2], # 中间层
185
+ activation=args.act,
186
+ use_bias=True
187
+ )
188
+
189
+ if args.time_input:
190
+ self.time_fc = nn.Sequential(
191
+ nn.Linear(args.n_hidden, args.n_hidden),
192
+ nn.SiLU(),
193
+ nn.Linear(args.n_hidden, args.n_hidden),
194
+ )
195
+
196
+ # 2. Transformer Blocks
197
+ # -----------------------------------------------------------
198
+ # 使用工厂实例化 Galerkin_Transformer_block
199
+ self.blocks = nn.ModuleList([
200
+ Galerkin_Transformer_block(
201
+ num_heads=args.n_heads,
202
+ hidden_dim=args.n_hidden,
203
+ dropout=args.dropout,
204
+ act=args.act,
205
+ mlp_ratio=args.mlp_ratio,
206
+ out_dim=args.out_dim,
207
+ last_layer=(_ == args.n_layers - 1)
208
+ )
209
+ for _ in range(args.n_layers)
210
+ ])
211
+
212
+ self.placeholder = nn.Parameter(
213
+ (1 / (args.n_hidden)) * torch.rand(args.n_hidden, dtype=torch.float)
214
+ )
215
+ self.initialize_weights()
216
+
217
+ def initialize_weights(self):
218
+ self.apply(self._init_weights)
219
+
220
+ def _init_weights(self, m):
221
+ if isinstance(m, nn.Linear):
222
+ trunc_normal_(m.weight, std=0.02)
223
+ if isinstance(m, nn.Linear) and m.bias is not None:
224
+ nn.init.constant_(m.bias, 0)
225
+ elif isinstance(m, (nn.LayerNorm, nn.BatchNorm1d)):
226
+ nn.init.constant_(m.bias, 0)
227
+ nn.init.constant_(m.weight, 1.0)
228
+
229
+ def forward(self, x, fx, T=None, geo=None):
230
+ if self.args.unified_pos:
231
+ x = self.pos.repeat(x.shape[0], 1, 1)
232
+
233
+ if fx is not None:
234
+ fx = torch.cat((x, fx), -1)
235
+ fx = self.preprocess(fx)
236
+ else:
237
+ fx = self.preprocess(x)
238
+
239
+ fx = fx + self.placeholder[None, None, :]
240
+
241
+ if T is not None:
242
+ Time_emb = timestep_embedding(T, self.args.n_hidden) # (B, C)
243
+ Time_emb = self.time_fc(Time_emb) # (B, C)
244
+
245
+ if Time_emb.ndim == 2:
246
+ Time_emb = Time_emb.unsqueeze(1) # (B, 1, C)
247
+
248
+ fx = fx + Time_emb # Broadcasting: (B, N, C) + (B, 1, C) -> (B, N, C)
249
+
250
+ for block in self.blocks:
251
+ fx = block(fx)
252
+ return fx
model/GraphSAGE.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch_geometric.nn as nng
4
+ from onescience.modules.mlp.MLP import StandardMLP
5
+
6
+ class Model(nn.Module):
7
+ """
8
+ GraphSAGE 模型。
9
+
10
+ 使用 SAGEConv 进行邻居聚合,并结合 MLP 进行特征编码和解码。
11
+ """
12
+ def __init__(self, args, device):
13
+ super(Model, self).__init__()
14
+ self.__name__ = "GraphSAGE"
15
+
16
+ self.nb_hidden_layers = args.n_layers
17
+ self.size_hidden_layers = args.n_hidden
18
+ self.bn_bool = True
19
+ self.activation = nn.ReLU()
20
+
21
+ self.encoder = StandardMLP(
22
+ input_dim=args.fun_dim + args.space_dim,
23
+ output_dim=args.n_hidden,
24
+ hidden_dims=[args.n_hidden * 2],
25
+ activation=args.act,
26
+ use_bias=True
27
+ )
28
+
29
+ self.decoder = StandardMLP(
30
+ input_dim=args.n_hidden,
31
+ output_dim=args.out_dim,
32
+ hidden_dims=[args.n_hidden * 2],
33
+ activation=args.act,
34
+ use_bias=True
35
+ )
36
+
37
+ # Graph Layers (Keep PyG implementation for consistency)
38
+ self.in_layer = nng.SAGEConv(
39
+ in_channels=args.n_hidden, out_channels=self.size_hidden_layers
40
+ )
41
+
42
+ self.hidden_layers = nn.ModuleList()
43
+ for n in range(self.nb_hidden_layers - 1):
44
+ self.hidden_layers.append(
45
+ nng.SAGEConv(
46
+ in_channels=self.size_hidden_layers,
47
+ out_channels=self.size_hidden_layers,
48
+ )
49
+ )
50
+
51
+ self.out_layer = nng.SAGEConv(
52
+ in_channels=self.size_hidden_layers, out_channels=self.size_hidden_layers
53
+ )
54
+
55
+ if self.bn_bool:
56
+ self.bn = nn.ModuleList()
57
+ for n in range(self.nb_hidden_layers):
58
+ self.bn.append(
59
+ nn.BatchNorm1d(self.size_hidden_layers, track_running_stats=False)
60
+ )
61
+
62
+ def forward(self, x, fx, T=None, geo=None):
63
+ if x.dim() == 3:
64
+ x = x.squeeze(0) # [1, N, C] → [N, C]
65
+ if fx is not None and fx.dim() == 3:
66
+ fx = fx.squeeze(0) # [1, N, C] → [N, C]
67
+ if geo.dim() == 3:
68
+ edge_index = geo.squeeze(0) # [1, 2, E] → [2, E]
69
+ else:
70
+ edge_index = geo
71
+
72
+ z = torch.cat((x, fx), dim=-1)
73
+ z = self.encoder(z)
74
+ z = self.in_layer(z, edge_index)
75
+ if self.bn_bool:
76
+ z = self.bn[0](z)
77
+ z = self.activation(z)
78
+
79
+ for n in range(self.nb_hidden_layers - 1):
80
+ z = self.hidden_layers[n](z, edge_index)
81
+ if self.bn_bool:
82
+ z = self.bn[n + 1](z)
83
+ z = self.activation(z)
84
+
85
+ z = self.out_layer(z, edge_index)
86
+ z = self.decoder(z)
87
+ return z.unsqueeze(0)
model/Graph_UNet.py ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import torch
3
+ import torch.nn as nn
4
+ import torch_geometric.nn as nng
5
+ import random
6
+ from onescience.modules.mlp.MLP import StandardMLP
7
+ from onescience.modules.sample.SpatialGraphDownsample import SpatialGraphDownsample
8
+ from onescience.modules.sample.SpatialGraphUpsample import SpatialGraphUpsample
9
+
10
+ class Model(nn.Module):
11
+ """
12
+ Graph U-Net 模型。
13
+
14
+ 基于图神经网络的 U-Net 结构,包含 Encoder-Decoder 和 Skip Connections。
15
+ 使用 SpatialGraphDownsample 进行图池化,使用 SpatialGraphUpsample 进行反池化。
16
+ """
17
+ def __init__(
18
+ self,
19
+ args,
20
+ device,
21
+ pool="random",
22
+ scale=5,
23
+ list_r=[0.05, 0.2, 0.5, 1, 10],
24
+ pool_ratio=[0.5, 0.5, 0.5, 0.5, 0.5],
25
+ max_neighbors=64,
26
+ layer="SAGE",
27
+ head=2,
28
+ ):
29
+ super(Model, self).__init__()
30
+ self.__name__ = "Graph_UNet"
31
+
32
+ # 参数绑定
33
+ self.L = scale
34
+ self.layer = layer
35
+ self.pool_type = pool
36
+ self.pool_ratio = pool_ratio
37
+ self.list_r = list_r
38
+ self.size_hidden = args.n_hidden
39
+ self.dim_enc = args.n_hidden
40
+ self.bn_bool = True
41
+ self.res = False
42
+ self.head = head
43
+ self.activation = nn.ReLU()
44
+
45
+ self.encoder = StandardMLP(
46
+ input_dim=args.fun_dim,
47
+ output_dim=args.n_hidden,
48
+ hidden_dims=[args.n_hidden * 2],
49
+ activation=args.act,
50
+ use_bias=True
51
+ )
52
+
53
+ self.decoder = StandardMLP(
54
+ input_dim=args.n_hidden,
55
+ output_dim=args.out_dim,
56
+ hidden_dims=[args.n_hidden * 2],
57
+ activation=args.act,
58
+ use_bias=True
59
+ )
60
+
61
+ # Down Path Layers
62
+ self.down_convs = nn.ModuleList()
63
+ self.down_samples = nn.ModuleList()
64
+ self.down_bns = nn.ModuleList()
65
+
66
+ # Level 0 (Initial)
67
+ self._add_conv_layer(self.down_convs, self.dim_enc, self.size_hidden)
68
+ if self.bn_bool:
69
+ self._add_bn_layer(self.down_bns, self.size_hidden)
70
+
71
+ # Level 1 to L-1
72
+ current_dim = self.size_hidden
73
+ for n in range(self.L - 1):
74
+ self.down_samples.append(
75
+ SpatialGraphDownsample(
76
+ in_channels=current_dim,
77
+ ratio=self.pool_ratio[n],
78
+ r=self.list_r[n],
79
+ max_num_neighbors=max_neighbors,
80
+ pool_method=self.pool_type
81
+ )
82
+ )
83
+
84
+ # Conv Layer
85
+ in_c = current_dim
86
+ out_c = 2 * current_dim if layer == "SAGE" else current_dim
87
+ self._add_conv_layer(self.down_convs, in_c, out_c)
88
+ current_dim = out_c
89
+
90
+ if self.bn_bool:
91
+ self._add_bn_layer(self.down_bns, current_dim)
92
+
93
+ # Up Path Layers
94
+ self.up_convs = nn.ModuleList()
95
+
96
+ # --- 3. Upsample Module ---
97
+ self.up_sampler = SpatialGraphUpsample()
98
+
99
+ self.up_bns = nn.ModuleList()
100
+
101
+ curr_h_init = args.n_hidden
102
+
103
+ # Up Layer 0 (Top Layer)
104
+ if self.layer == "SAGE":
105
+ self.up_convs.append(nng.SAGEConv(3 * curr_h_init, self.dim_enc))
106
+ curr_h_init = 2 * curr_h_init
107
+ elif self.layer == "GAT":
108
+ self.up_convs.append(nng.GATConv(2 * self.head * curr_h_init, self.dim_enc, heads=2, concat=False))
109
+
110
+ if self.bn_bool:
111
+ self.up_bns.append(nng.BatchNorm(self.dim_enc, track_running_stats=False))
112
+
113
+ # Up Layer 1 to L-1 (Middle Layers)
114
+ for n in range(1, self.L - 1):
115
+ if self.layer == "SAGE":
116
+ self.up_convs.append(nng.SAGEConv(3 * curr_h_init, curr_h_init))
117
+ bn_dim = curr_h_init
118
+ curr_h_init = 2 * curr_h_init
119
+ elif self.layer == "GAT":
120
+ self.up_convs.append(nng.GATConv(2 * self.head * curr_h_init, curr_h_init, heads=2, concat=True))
121
+ bn_dim = curr_h_init * 2 # GAT concat=True
122
+
123
+ if self.bn_bool:
124
+ self.up_bns.append(nng.BatchNorm(bn_dim, track_running_stats=False))
125
+
126
+ def _add_conv_layer(self, module_list, in_c, out_c):
127
+ if self.layer == "SAGE":
128
+ module_list.append(nng.SAGEConv(in_c, out_c))
129
+ elif self.layer == "GAT":
130
+ module_list.append(nng.GATConv(in_c, out_c, heads=self.head, concat=True, add_self_loops=False))
131
+
132
+ def _add_bn_layer(self, module_list, in_c):
133
+ dim = in_c * self.head if self.layer == "GAT" else in_c
134
+ module_list.append(nng.BatchNorm(dim, track_running_stats=False))
135
+
136
+ def forward(self, x, fx, T=None, geo=None):
137
+ if geo is None: raise ValueError("Edge index required")
138
+ if fx.dim() == 3: fx = fx.squeeze(0)
139
+ if geo.dim() == 3: edge_index = geo.squeeze(0)
140
+ else: edge_index = geo
141
+
142
+ # Encoder
143
+ z = self.encoder(fx)
144
+ if self.res: z_res = z.clone()
145
+
146
+ # Downsampling Path
147
+ skip_connections = []
148
+ pos_history = []
149
+ edge_index_history = [edge_index.clone()]
150
+
151
+ # Level 0 Conv
152
+ z = self.down_convs[0](z, edge_index)
153
+ if self.bn_bool: z = self.down_bns[0](z)
154
+ z = self.activation(z)
155
+
156
+ skip_connections.append(z.clone())
157
+
158
+ # Assuming x contains coords in first 2 columns as per original code logic
159
+ current_pos = x[:, :2]
160
+ pos_history.append(current_pos.clone())
161
+
162
+ # Levels 1 to L-1
163
+ for n in range(self.L - 1):
164
+ z, current_pos, edge_index, _ = self.down_samples[n](z, current_pos, edge_index)
165
+
166
+ pos_history.append(current_pos.clone())
167
+ edge_index_history.append(edge_index.clone())
168
+
169
+ z = self.down_convs[n+1](z, edge_index)
170
+ if self.bn_bool: z = self.down_bns[n+1](z)
171
+ z = self.activation(z)
172
+
173
+ skip_connections.append(z.clone())
174
+
175
+ # Up Path
176
+ for n in range(self.L - 1, 0, -1):
177
+ layer_idx = n - 1
178
+
179
+ pos_low = pos_history[n]
180
+ pos_high = pos_history[n-1]
181
+ z_skip = skip_connections[n-1]
182
+
183
+ target_edge_index = edge_index_history[n-1]
184
+
185
+ z = self.up_sampler(z, pos_low, pos_high)
186
+
187
+ z = torch.cat([z, z_skip], dim=1)
188
+
189
+ z = self.up_convs[layer_idx](z, target_edge_index)
190
+
191
+ if self.bn_bool:
192
+ z = self.up_bns[layer_idx](z)
193
+
194
+ if n != 1:
195
+ z = self.activation(z)
196
+
197
+ # Decoder
198
+ if self.res: z = z + z_res
199
+ z = self.decoder(z)
200
+ return z.unsqueeze(0)
model/LSM.py ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import torch
3
+ import math
4
+ import torch.nn as nn
5
+ import numpy as np
6
+ import torch.nn.functional as F
7
+ from onescience.modules.mlp.MLP import StandardMLP
8
+ from onescience.modules.transformer.Neural_Spectral_Block import (
9
+ NeuralSpectralBlock1D,
10
+ NeuralSpectralBlock2D,
11
+ NeuralSpectralBlock3D,
12
+ )
13
+ from onescience.modules.embedding import timestep_embedding, unified_pos_embedding
14
+ from onescience.modules.fourier.geo_spectral import GeoSpectralConv2d, IPHI
15
+ from onescience.modules.layer.unet_layer import (
16
+ DoubleConv1D, Down1D, Up1D, OutConv1D,
17
+ DoubleConv2D, Down2D, Up2D, OutConv2D,
18
+ DoubleConv3D, Down3D, Up3D, OutConv3D,
19
+ )
20
+
21
+ ConvList = [None, DoubleConv1D, DoubleConv2D, DoubleConv3D]
22
+ DownList = [None, Down1D, Down2D, Down3D]
23
+ UpList = [None, Up1D, Up2D, Up3D]
24
+ OutList = [None, OutConv1D, OutConv2D, OutConv3D]
25
+ NeuralSpectralBlockList = [
26
+ None,
27
+ NeuralSpectralBlock1D,
28
+ NeuralSpectralBlock2D,
29
+ NeuralSpectralBlock3D,
30
+ ]
31
+
32
+ class Model(nn.Module):
33
+ """
34
+ LSM (Latent Spectral Model) 模型。
35
+ 结合了 U-Net 的多尺度特征提取与 Neural Spectral Block (Latent Transformer) 的全局谱处理能力。
36
+ """
37
+ def __init__(
38
+ self, args, device, bilinear=True, num_token=4, num_basis=12, s1=96, s2=96
39
+ ):
40
+ super(Model, self).__init__()
41
+ self.__name__ = "LSM"
42
+ self.args = args
43
+
44
+ if args.task == "steady":
45
+ normtype = "bn"
46
+ else:
47
+ normtype = "in"
48
+
49
+ # 1. Embedding & Preprocessing
50
+ if args.unified_pos and args.geotype != "unstructured":
51
+ self.pos = unified_pos_embedding(args.shapelist, args.ref, device=device)
52
+ in_dim = args.fun_dim + args.ref ** len(args.shapelist)
53
+ else:
54
+ in_dim = args.fun_dim + args.space_dim
55
+
56
+ self.preprocess = StandardMLP(
57
+ input_dim=in_dim,
58
+ output_dim=args.n_hidden,
59
+ hidden_dims=[args.n_hidden * 2],
60
+ activation=args.act,
61
+ use_bias=True
62
+ )
63
+
64
+ if args.time_input:
65
+ self.time_fc = nn.Sequential(
66
+ nn.Linear(args.n_hidden, args.n_hidden),
67
+ nn.SiLU(),
68
+ nn.Linear(args.n_hidden, args.n_hidden),
69
+ )
70
+ else:
71
+ self.time_fc = None
72
+
73
+ # 2. Geometry Projection
74
+ if self.args.geotype == "unstructured":
75
+ self.fftproject_in = GeoSpectralConv2d(
76
+ in_channels=args.n_hidden,
77
+ out_channels=args.n_hidden,
78
+ modes1=args.modes,
79
+ modes2=args.modes,
80
+ s1=s1,
81
+ s2=s2
82
+ )
83
+ self.fftproject_out = GeoSpectralConv2d(
84
+ in_channels=args.n_hidden,
85
+ out_channels=args.n_hidden,
86
+ modes1=args.modes,
87
+ modes2=args.modes,
88
+ s1=s1,
89
+ s2=s2
90
+ )
91
+ self.iphi = IPHI()
92
+
93
+ patch_size = [(size + (16 - size % 16) % 16) // 16 for size in [s1, s2]]
94
+ self.padding = [(16 - size % 16) % 16 for size in [s1, s2]]
95
+ else:
96
+ patch_size = [
97
+ (size + (16 - size % 16) % 16) // 16 for size in args.shapelist
98
+ ]
99
+ self.padding = [(16 - size % 16) % 16 for size in args.shapelist]
100
+
101
+ # 3. Multiscale U-Net Modules
102
+ # -----------------------------------------------------------
103
+ dim = len(patch_size)
104
+
105
+ self.inc = ConvList[dim](args.n_hidden, args.n_hidden, normtype=normtype)
106
+ self.down1 = DownList[dim](args.n_hidden, args.n_hidden * 2, normtype=normtype)
107
+ self.down2 = DownList[dim](args.n_hidden * 2, args.n_hidden * 4, normtype=normtype)
108
+ self.down3 = DownList[dim](args.n_hidden * 4, args.n_hidden * 8, normtype=normtype)
109
+
110
+ factor = 2 if bilinear else 1
111
+ self.down4 = DownList[dim](args.n_hidden * 8, args.n_hidden * 16 // factor, normtype=normtype)
112
+
113
+ self.up1 = UpList[dim](args.n_hidden * 16, args.n_hidden * 8 // factor, bilinear, normtype=normtype)
114
+ self.up2 = UpList[dim](args.n_hidden * 8, args.n_hidden * 4 // factor, bilinear, normtype=normtype)
115
+ self.up3 = UpList[dim](args.n_hidden * 4, args.n_hidden * 2 // factor, bilinear, normtype=normtype)
116
+ self.up4 = UpList[dim](args.n_hidden * 2, args.n_hidden, bilinear, normtype=normtype)
117
+ self.outc = OutList[dim](args.n_hidden, args.n_hidden)
118
+
119
+ # 4. Patchified Neural Spectral Blocks
120
+ block_class = NeuralSpectralBlockList[dim]
121
+
122
+ self.process1 = block_class(
123
+ width=args.n_hidden,
124
+ num_basis=num_basis,
125
+ patch_size=patch_size,
126
+ num_token=num_token,
127
+ n_heads=args.n_heads
128
+ )
129
+ self.process2 = block_class(
130
+ width=args.n_hidden * 2,
131
+ num_basis=num_basis,
132
+ patch_size=patch_size,
133
+ num_token=num_token,
134
+ n_heads=args.n_heads
135
+ )
136
+ self.process3 = block_class(
137
+ width=args.n_hidden * 4,
138
+ num_basis=num_basis,
139
+ patch_size=patch_size,
140
+ num_token=num_token,
141
+ n_heads=args.n_heads
142
+ )
143
+ self.process4 = block_class(
144
+ width=args.n_hidden * 8,
145
+ num_basis=num_basis,
146
+ patch_size=patch_size,
147
+ num_token=num_token,
148
+ n_heads=args.n_heads
149
+ )
150
+ self.process5 = block_class(
151
+ width=args.n_hidden * 16 // factor,
152
+ num_basis=num_basis,
153
+ patch_size=patch_size,
154
+ num_token=num_token,
155
+ n_heads=args.n_heads
156
+ )
157
+
158
+ # 5. Projectors
159
+ # -----------------------------------------------------------
160
+ self.fc1 = nn.Linear(args.n_hidden, args.n_hidden * 2)
161
+ self.fc2 = nn.Linear(args.n_hidden * 2, args.out_dim)
162
+
163
+ def structured_geo(self, x, fx, T=None):
164
+ B, N, _ = x.shape
165
+
166
+ if self.args.unified_pos:
167
+ x = self.pos.repeat(x.shape[0], 1, 1)
168
+
169
+ if fx is not None:
170
+ fx = torch.cat((x, fx), -1)
171
+ fx = self.preprocess(fx)
172
+ else:
173
+ fx = self.preprocess(x)
174
+
175
+ if T is not None:
176
+ Time_emb = timestep_embedding(T, self.args.n_hidden)
177
+ Time_emb = self.time_fc(Time_emb)
178
+ if Time_emb.ndim == 2:
179
+ Time_emb = Time_emb.unsqueeze(1)
180
+ fx = fx + Time_emb
181
+
182
+ x = fx.permute(0, 2, 1).reshape(B, self.args.n_hidden, *self.args.shapelist)
183
+
184
+ if not all(item == 0 for item in self.padding):
185
+ pad_arg = []
186
+ for p in reversed(self.padding):
187
+ pad_arg.extend([0, p])
188
+ x = F.pad(x, pad_arg)
189
+
190
+ # LSM 核心处理流
191
+ x1 = self.inc(x)
192
+ x2 = self.down1(x1)
193
+ x3 = self.down2(x2)
194
+ x4 = self.down3(x3)
195
+ x5 = self.down4(x4)
196
+
197
+ x = self.up1(self.process5(x5), self.process4(x4))
198
+ x = self.up2(x, self.process3(x3))
199
+ x = self.up3(x, self.process2(x2))
200
+ x = self.up4(x, self.process1(x1))
201
+ x = self.outc(x)
202
+
203
+ if not all(item == 0 for item in self.padding):
204
+ if len(self.args.shapelist) == 1:
205
+ x = x[..., : -self.padding[0]]
206
+ elif len(self.args.shapelist) == 2:
207
+ x = x[..., : -self.padding[0], : -self.padding[1]]
208
+ elif len(self.args.shapelist) == 3:
209
+ x = x[..., : -self.padding[0], : -self.padding[1], : -self.padding[2]]
210
+
211
+ x = x.reshape(B, self.args.n_hidden, -1).permute(0, 2, 1)
212
+ x = self.fc1(x)
213
+ x = F.gelu(x)
214
+ x = self.fc2(x)
215
+ return x
216
+
217
+ def unstructured_geo(self, x, fx, T=None):
218
+ original_pos = x
219
+ if fx is not None:
220
+ fx = torch.cat((x, fx), -1)
221
+ fx = self.preprocess(fx)
222
+ else:
223
+ fx = self.preprocess(x)
224
+
225
+ if T is not None:
226
+ Time_emb = timestep_embedding(T, self.args.n_hidden)
227
+ Time_emb = self.time_fc(Time_emb)
228
+ if Time_emb.ndim == 2:
229
+ Time_emb = Time_emb.unsqueeze(1)
230
+ fx = fx + Time_emb
231
+
232
+ x = self.fftproject_in(
233
+ fx.permute(0, 2, 1), x_in=original_pos, iphi=self.iphi, code=None
234
+ )
235
+
236
+ # LSM 核心处理流
237
+ x1 = self.inc(x)
238
+ x2 = self.down1(x1)
239
+ x3 = self.down2(x2)
240
+ x4 = self.down3(x3)
241
+ x5 = self.down4(x4)
242
+
243
+ x = self.up1(self.process5(x5), self.process4(x4))
244
+ x = self.up2(x, self.process3(x3))
245
+ x = self.up3(x, self.process2(x2))
246
+ x = self.up4(x, self.process1(x1))
247
+ x = self.outc(x)
248
+
249
+ x = self.fftproject_out(
250
+ x, x_out=original_pos, iphi=self.iphi, code=None
251
+ ).permute(0, 2, 1)
252
+
253
+ x = self.fc1(x)
254
+ x = F.gelu(x)
255
+ x = self.fc2(x)
256
+ return x
257
+
258
+ def forward(self, x, fx, T=None, geo=None):
259
+ if self.args.geotype == "unstructured":
260
+ return self.unstructured_geo(x, fx, T)
261
+ else:
262
+ return self.structured_geo(x, fx, T)
model/MWT.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import math
3
+ import torch.nn as nn
4
+ import numpy as np
5
+ import torch.nn.functional as F
6
+ from timm.layers import trunc_normal_
7
+ from onescience.modules.fourier.MultiWaveletTransform import (
8
+ MultiWaveletTransform1D,
9
+ MultiWaveletTransform2D,
10
+ MultiWaveletTransform3D,
11
+ )
12
+ from onescience.modules.mlp.MLP import StandardMLP
13
+ from onescience.modules.embedding import timestep_embedding, unified_pos_embedding
14
+ from onescience.modules.fourier.geo_spectral import GeoSpectralConv2d, IPHI
15
+
16
+ MultiWaveletTransformList = [
17
+ None,
18
+ MultiWaveletTransform1D,
19
+ MultiWaveletTransform2D,
20
+ MultiWaveletTransform3D,
21
+ ]
22
+
23
+ class Model(nn.Module):
24
+ # this model requires H = W = Z and H, W, Z is the power of two
25
+ def __init__(
26
+ self, args, device, alpha=2, L=0, c=1, base="legendre", s1=128, s2=128
27
+ ):
28
+ super(Model, self).__init__()
29
+ self.__name__ = "MWT"
30
+ self.args = args
31
+ self.k = args.mwt_k
32
+ self.WMT_dim = c * self.k**2
33
+ if args.geotype == "structured_1D":
34
+ self.WMT_dim = c * self.k
35
+ self.c = c
36
+ self.s1 = s1
37
+ self.s2 = s2
38
+
39
+ ## embedding
40
+ if (
41
+ args.unified_pos and args.geotype != "unstructured"
42
+ ): # only for structured mesh
43
+ self.pos = unified_pos_embedding(args.shapelist, args.ref, device=device)
44
+ self.preprocess = StandardMLP(
45
+ input_dim=args.fun_dim + args.ref ** len(args.shapelist),
46
+ output_dim=self.WMT_dim,
47
+ hidden_dims=[args.n_hidden * 2],
48
+ activation=args.act,
49
+ use_bias=True
50
+ )
51
+ else:
52
+ self.preprocess = StandardMLP(
53
+ input_dim=args.fun_dim + args.space_dim,
54
+ output_dim=self.WMT_dim,
55
+ hidden_dims=[args.n_hidden * 2],
56
+ activation=args.act,
57
+ use_bias=True
58
+ )
59
+
60
+ if args.time_input:
61
+ self.time_fc = nn.Sequential(
62
+ nn.Linear(self.WMT_dim, args.n_hidden),
63
+ nn.SiLU(),
64
+ nn.Linear(args.n_hidden, self.WMT_dim),
65
+ )
66
+
67
+ # geometry projection
68
+ if self.args.geotype == "unstructured":
69
+ self.fftproject_in = GeoSpectralConv2d(
70
+ in_channels=self.WMT_dim,
71
+ out_channels=self.WMT_dim,
72
+ modes1=args.modes,
73
+ modes2=args.modes,
74
+ s1=s1,
75
+ s2=s2
76
+ )
77
+ self.fftproject_out = GeoSpectralConv2d(
78
+ in_channels=self.WMT_dim,
79
+ out_channels=self.WMT_dim,
80
+ modes1=args.modes,
81
+ modes2=args.modes,
82
+ s1=s1,
83
+ s2=s2
84
+ )
85
+ self.iphi = IPHI()
86
+ self.augmented_resolution = [s1, s2]
87
+ self.padding = [(16 - size % 16) % 16 for size in [s1, s2]]
88
+ else:
89
+ target = 2 ** (math.ceil(np.log2(max(args.shapelist))))
90
+ self.padding = [(target - size) for size in args.shapelist]
91
+ self.augmented_resolution = [target for _ in range(len(self.padding))]
92
+
93
+ dim = len(self.padding)
94
+ transform_class = MultiWaveletTransformList[dim]
95
+
96
+ self.spectral_layers = nn.ModuleList(
97
+ [
98
+ transform_class(
99
+ k=self.k,
100
+ alpha=alpha,
101
+ L=L,
102
+ c=c,
103
+ base=base
104
+ )
105
+ for _ in range(args.n_layers)
106
+ ]
107
+ )
108
+
109
+ # projectors
110
+ self.fc1 = nn.Linear(self.WMT_dim, args.n_hidden)
111
+ self.fc2 = nn.Linear(args.n_hidden, args.out_dim)
112
+
113
+ def structured_geo(self, x, fx, T=None):
114
+ B, N, _ = x.shape
115
+ if self.args.unified_pos:
116
+ x = self.pos.repeat(x.shape[0], 1, 1)
117
+ if fx is not None:
118
+ fx = torch.cat((x, fx), -1)
119
+ fx = self.preprocess(fx)
120
+ else:
121
+ fx = self.preprocess(x)
122
+
123
+ if T is not None:
124
+ Time_emb = timestep_embedding(T, self.WMT_dim)
125
+ Time_emb = self.time_fc(Time_emb)
126
+ if Time_emb.ndim == 2:
127
+ Time_emb = Time_emb.unsqueeze(1)
128
+ fx = fx + Time_emb
129
+
130
+ x = fx.permute(0, 2, 1).reshape(B, self.WMT_dim, *self.args.shapelist)
131
+ if not all(item == 0 for item in self.padding):
132
+ if len(self.args.shapelist) == 2:
133
+ x = F.pad(x, [0, self.padding[1], 0, self.padding[0]])
134
+ elif len(self.args.shapelist) == 3:
135
+ x = F.pad(
136
+ x, [0, self.padding[2], 0, self.padding[1], 0, self.padding[0]]
137
+ )
138
+ x = (
139
+ x.reshape(B, self.WMT_dim, -1)
140
+ .permute(0, 2, 1)
141
+ .contiguous()
142
+ .reshape(
143
+ B,
144
+ *self.augmented_resolution,
145
+ self.c,
146
+ self.k**2 if self.args.geotype != "structured_1D" else self.k
147
+ )
148
+ )
149
+ for i in range(self.args.n_layers):
150
+ x = self.spectral_layers[i](x)
151
+ if i < self.args.n_layers - 1:
152
+ x = F.gelu(x)
153
+ x = (
154
+ x.reshape(B, -1, self.WMT_dim)
155
+ .permute(0, 2, 1)
156
+ .contiguous()
157
+ .reshape(B, self.WMT_dim, *self.augmented_resolution)
158
+ )
159
+ if not all(item == 0 for item in self.padding):
160
+ if len(self.args.shapelist) == 2:
161
+ x = x[..., : -self.padding[0], : -self.padding[1]]
162
+ elif len(self.args.shapelist) == 3:
163
+ x = x[..., : -self.padding[0], : -self.padding[1], : -self.padding[2]]
164
+ x = x.reshape(B, self.WMT_dim, -1).permute(0, 2, 1)
165
+ x = self.fc1(x)
166
+ x = F.gelu(x)
167
+ x = self.fc2(x)
168
+ return x
169
+
170
+ def unstructured_geo(self, x, fx, T=None):
171
+ B, N, _ = x.shape
172
+ original_pos = x
173
+ if fx is not None:
174
+ fx = torch.cat((x, fx), -1)
175
+ fx = self.preprocess(fx)
176
+ else:
177
+ fx = self.preprocess(x)
178
+
179
+ if T is not None:
180
+ Time_emb = timestep_embedding(T, self.WMT_dim)
181
+ Time_emb = self.time_fc(Time_emb)
182
+ if Time_emb.ndim == 2:
183
+ Time_emb = Time_emb.unsqueeze(1)
184
+ fx = fx + Time_emb
185
+
186
+ x = self.fftproject_in(
187
+ fx.permute(0, 2, 1), x_in=original_pos, iphi=self.iphi, code=None
188
+ )
189
+ x = (
190
+ x.reshape(B, self.WMT_dim, -1)
191
+ .permute(0, 2, 1)
192
+ .contiguous()
193
+ .reshape(B, *self.augmented_resolution, self.c, self.k**2)
194
+ )
195
+ for i in range(self.args.n_layers):
196
+ x = self.spectral_layers[i](x)
197
+ if i < self.args.n_layers - 1:
198
+ x = F.gelu(x)
199
+ x = (
200
+ x.reshape(B, -1, self.WMT_dim)
201
+ .permute(0, 2, 1)
202
+ .contiguous()
203
+ .reshape(B, self.WMT_dim, *self.augmented_resolution)
204
+ )
205
+ x = self.fftproject_out(
206
+ x, x_out=original_pos, iphi=self.iphi, code=None
207
+ ).permute(0, 2, 1)
208
+ x = self.fc1(x)
209
+ x = F.gelu(x)
210
+ x = self.fc2(x)
211
+ return x
212
+
213
+ def forward(self, x, fx, T=None, geo=None):
214
+ if self.args.geotype == "unstructured":
215
+ return self.unstructured_geo(x, fx, T)
216
+ else:
217
+ return self.structured_geo(x, fx, T)
model/MeshGraphNet.py ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ from torch import Tensor
4
+
5
+ try:
6
+ import dgl
7
+ from dgl import DGLGraph
8
+ except ImportError:
9
+ pass
10
+
11
+ from dataclasses import dataclass
12
+ from itertools import chain
13
+ from typing import Callable, List, Tuple, Union
14
+
15
+ # --- 引入模块工厂 ---
16
+ from onescience.modules.edge.mesh_edge_block import MeshEdgeBlock
17
+ from onescience.modules.mlp.mesh_graph_mlp import MeshGraphMLP
18
+ from onescience.modules.node.mesh_node_block import MeshNodeBlock
19
+
20
+ # 保持工具类引用
21
+ from onescience.modules.utils.gnnlayer_utils import CuGraphCSC, set_checkpoint_fn
22
+ from onescience.modules.layer.activations import get_activation
23
+ from onescience.modules.meta import ModelMetaData
24
+ from onescience.modules.module import Module
25
+
26
+
27
+ @dataclass
28
+ class MetaData(ModelMetaData):
29
+ name: str = "MeshGraphNet"
30
+ # Optimization
31
+ jit: bool = False
32
+ cuda_graphs: bool = False
33
+ amp_cpu: bool = False
34
+ amp_gpu: bool = True
35
+ torch_fx: bool = False
36
+ # Inference
37
+ onnx: bool = False
38
+ # Physics informed
39
+ func_torch: bool = True
40
+ auto_grad: bool = True
41
+
42
+
43
+ class Model(Module):
44
+ """
45
+ LSMMeshGraphNet 网络架构 (Refactored).
46
+
47
+ 使用网格图的 MLP、边更新和节点更新模块构建。
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ args,
53
+ device,
54
+ processor_size: int = 15,
55
+ mlp_activation_fn: Union[str, List[str]] = "relu",
56
+ num_layers_node_processor: int = 2,
57
+ num_layers_edge_processor: int = 2,
58
+ hidden_dim_processor: int = 128,
59
+ hidden_dim_node_encoder: int = 128,
60
+ num_layers_node_encoder: Union[int, None] = 2,
61
+ hidden_dim_edge_encoder: int = 128,
62
+ num_layers_edge_encoder: Union[int, None] = 2,
63
+ hidden_dim_node_decoder: int = 128,
64
+ num_layers_node_decoder: Union[int, None] = 2,
65
+ aggregation: str = "sum",
66
+ do_concat_trick: bool = False,
67
+ num_processor_checkpoint_segments: int = 0,
68
+ recompute_activation: bool = False,
69
+ ):
70
+ super().__init__(meta=MetaData())
71
+ self.__name__ = "LSMMeshGraphNet"
72
+
73
+ # 参数绑定
74
+ self.input_dim_nodes = args.fun_dim
75
+ self.input_dim_edges = 4
76
+ self.output_dim = args.out_dim
77
+
78
+ activation_fn = get_activation(mlp_activation_fn)
79
+
80
+ # 1. Edge Encoder
81
+ self.edge_encoder = MeshGraphMLP(
82
+ input_dim=self.input_dim_edges,
83
+ output_dim=hidden_dim_processor,
84
+ hidden_dim=hidden_dim_edge_encoder,
85
+ hidden_layers=num_layers_edge_encoder,
86
+ activation_fn=activation_fn,
87
+ norm_type="LayerNorm",
88
+ recompute_activation=recompute_activation,
89
+ )
90
+
91
+ # 2. Node Encoder
92
+ self.node_encoder = MeshGraphMLP(
93
+ input_dim=self.input_dim_nodes,
94
+ output_dim=hidden_dim_processor,
95
+ hidden_dim=hidden_dim_node_encoder,
96
+ hidden_layers=num_layers_node_encoder,
97
+ activation_fn=activation_fn,
98
+ norm_type="LayerNorm",
99
+ recompute_activation=recompute_activation,
100
+ )
101
+
102
+ # 3. Node Decoder
103
+ self.node_decoder = MeshGraphMLP(
104
+ input_dim=hidden_dim_processor,
105
+ output_dim=self.output_dim,
106
+ hidden_dim=hidden_dim_node_decoder,
107
+ hidden_layers=num_layers_node_decoder,
108
+ activation_fn=activation_fn,
109
+ norm_type=None,
110
+ recompute_activation=recompute_activation,
111
+ )
112
+
113
+ # 4. Processor
114
+ self.processor = MeshGraphNetProcessor(
115
+ processor_size=processor_size,
116
+ input_dim_node=hidden_dim_processor,
117
+ input_dim_edge=hidden_dim_processor,
118
+ num_layers_node=num_layers_node_processor,
119
+ num_layers_edge=num_layers_edge_processor,
120
+ aggregation=aggregation,
121
+ norm_type="LayerNorm",
122
+ activation_fn=activation_fn,
123
+ do_concat_trick=do_concat_trick,
124
+ num_processor_checkpoint_segments=num_processor_checkpoint_segments,
125
+ )
126
+
127
+ def forward(
128
+ self,
129
+ node_features: Tensor,
130
+ edge_features: Tensor,
131
+ graph: Union[DGLGraph, List[DGLGraph], CuGraphCSC],
132
+ ) -> Tensor:
133
+ edge_features = self.edge_encoder(edge_features)
134
+ node_features = self.node_encoder(node_features)
135
+ x = self.processor(node_features, edge_features, graph)
136
+ x = self.node_decoder(x)
137
+ return x
138
+
139
+
140
+ class MeshGraphNetProcessor(nn.Module):
141
+ """
142
+ MeshGraphNet processor block constructed from edge and node update modules.
143
+ """
144
+
145
+ def __init__(
146
+ self,
147
+ processor_size: int = 15,
148
+ input_dim_node: int = 128,
149
+ input_dim_edge: int = 128,
150
+ num_layers_node: int = 2,
151
+ num_layers_edge: int = 2,
152
+ aggregation: str = "sum",
153
+ norm_type: str = "LayerNorm",
154
+ activation_fn: nn.Module = nn.ReLU(),
155
+ do_concat_trick: bool = False,
156
+ num_processor_checkpoint_segments: int = 0,
157
+ ):
158
+ super().__init__()
159
+ self.processor_size = processor_size
160
+ self.num_processor_checkpoint_segments = num_processor_checkpoint_segments
161
+
162
+ edge_blocks = []
163
+ node_blocks = []
164
+
165
+ for _ in range(self.processor_size):
166
+ edge_blocks.append(
167
+ MeshEdgeBlock(
168
+ input_dim_nodes=input_dim_node,
169
+ input_dim_edges=input_dim_edge,
170
+ output_dim=input_dim_edge,
171
+ hidden_dim=input_dim_edge,
172
+ hidden_layers=num_layers_edge,
173
+ activation_fn=activation_fn,
174
+ norm_type=norm_type,
175
+ do_concat_trick=do_concat_trick,
176
+ recompute_activation=False
177
+ )
178
+ )
179
+ node_blocks.append(
180
+ MeshNodeBlock(
181
+ aggregation=aggregation,
182
+ input_dim_nodes=input_dim_node,
183
+ input_dim_edges=input_dim_edge,
184
+ output_dim=input_dim_node,
185
+ hidden_dim=input_dim_node,
186
+ hidden_layers=num_layers_node,
187
+ activation_fn=activation_fn,
188
+ norm_type=norm_type,
189
+ recompute_activation=False
190
+ )
191
+ )
192
+
193
+ # 按照 Edge -> Node 的顺序交替排列
194
+ layers = list(chain(*zip(edge_blocks, node_blocks)))
195
+
196
+ self.processor_layers = nn.ModuleList(layers)
197
+ self.num_processor_layers = len(self.processor_layers)
198
+ self.set_checkpoint_segments(self.num_processor_checkpoint_segments)
199
+
200
+ def set_checkpoint_segments(self, checkpoint_segments: int):
201
+ if checkpoint_segments > 0:
202
+ if self.num_processor_layers % checkpoint_segments != 0:
203
+ raise ValueError(
204
+ "Processor layers must be a multiple of checkpoint_segments"
205
+ )
206
+ segment_size = self.num_processor_layers // checkpoint_segments
207
+ self.checkpoint_segments = []
208
+ for i in range(0, self.num_processor_layers, segment_size):
209
+ self.checkpoint_segments.append((i, i + segment_size))
210
+ self.checkpoint_fn = set_checkpoint_fn(True)
211
+ else:
212
+ self.checkpoint_fn = set_checkpoint_fn(False)
213
+ self.checkpoint_segments = [(0, self.num_processor_layers)]
214
+
215
+ def run_function(
216
+ self, segment_start: int, segment_end: int
217
+ ) -> Callable[
218
+ [Tensor, Tensor, Union[DGLGraph, List[DGLGraph]]], Tuple[Tensor, Tensor]
219
+ ]:
220
+ segment = self.processor_layers[segment_start:segment_end]
221
+
222
+ def custom_forward(
223
+ node_features: Tensor,
224
+ edge_features: Tensor,
225
+ graph: Union[DGLGraph, List[DGLGraph]],
226
+ ) -> Tuple[Tensor, Tensor]:
227
+ for module in segment:
228
+ edge_features, node_features = module(
229
+ edge_features, node_features, graph
230
+ )
231
+ return edge_features, node_features
232
+
233
+ return custom_forward
234
+
235
+ @torch.jit.unused
236
+ def forward(
237
+ self,
238
+ node_features: Tensor,
239
+ edge_features: Tensor,
240
+ graph: Union[DGLGraph, List[DGLGraph], CuGraphCSC],
241
+ ) -> Tensor:
242
+ for segment_start, segment_end in self.checkpoint_segments:
243
+ edge_features, node_features = self.checkpoint_fn(
244
+ self.run_function(segment_start, segment_end),
245
+ node_features,
246
+ edge_features,
247
+ graph,
248
+ use_reentrant=False,
249
+ preserve_rng_state=False,
250
+ )
251
+
252
+ return node_features
model/ONO.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ from timm.layers import trunc_normal_
4
+ from onescience.modules.mlp.MLP import StandardMLP
5
+ from onescience.modules.transformer.orthogonal_neural_block import OrthogonalNeuralBlock
6
+ from onescience.modules.embedding import timestep_embedding, unified_pos_embedding
7
+
8
+ class Model(nn.Module):
9
+ """
10
+ Orthogonal Neural Operator (ONO) 模型。
11
+ """
12
+ def __init__(self, args, device):
13
+ super(Model, self).__init__()
14
+ self.__name__ = "ONO"
15
+ self.args = args
16
+
17
+ # Embedding & Preprocessing
18
+ if args.unified_pos and args.geotype != "unstructured":
19
+ self.pos = unified_pos_embedding(args.shapelist, args.ref, device=device)
20
+ dim_x = args.ref ** len(args.shapelist)
21
+ dim_z = args.fun_dim + args.ref ** len(args.shapelist)
22
+ else:
23
+ dim_x = args.fun_dim + args.space_dim
24
+ dim_z = args.fun_dim + args.space_dim
25
+
26
+ self.preprocess_x = StandardMLP(
27
+ input_dim=dim_x,
28
+ output_dim=args.n_hidden,
29
+ hidden_dims=[args.n_hidden * 2],
30
+ activation=args.act,
31
+ use_bias=True
32
+ )
33
+ self.preprocess_z = StandardMLP(
34
+ input_dim=dim_z,
35
+ output_dim=args.n_hidden,
36
+ hidden_dims=[args.n_hidden * 2],
37
+ activation=args.act,
38
+ use_bias=True
39
+ )
40
+
41
+ if args.time_input:
42
+ self.time_fc = nn.Sequential(
43
+ nn.Linear(args.n_hidden, args.n_hidden),
44
+ nn.SiLU(),
45
+ nn.Linear(args.n_hidden, args.n_hidden),
46
+ )
47
+
48
+ # ONO Blocks
49
+ self.blocks = nn.ModuleList([
50
+ OrthogonalNeuralBlock(
51
+ num_heads=args.n_heads,
52
+ hidden_dim=args.n_hidden,
53
+ dropout=args.dropout,
54
+ act=args.act,
55
+ attn_type=args.attn_type,
56
+ mlp_ratio=args.mlp_ratio,
57
+ last_layer=(_ == args.n_layers - 1),
58
+ psi_dim=args.psi_dim,
59
+ out_dim=args.out_dim,
60
+ )
61
+ for _ in range(args.n_layers)
62
+ ])
63
+
64
+ self.placeholder = nn.Parameter(
65
+ (1 / (args.n_hidden)) * torch.rand(args.n_hidden, dtype=torch.float)
66
+ )
67
+ self.initialize_weights()
68
+
69
+ def initialize_weights(self):
70
+ self.apply(self._init_weights)
71
+
72
+ def _init_weights(self, m):
73
+ if isinstance(m, nn.Linear):
74
+ trunc_normal_(m.weight, std=0.02)
75
+ if isinstance(m, nn.Linear) and m.bias is not None:
76
+ nn.init.constant_(m.bias, 0)
77
+ elif isinstance(m, (nn.LayerNorm, nn.BatchNorm1d)):
78
+ nn.init.constant_(m.bias, 0)
79
+ nn.init.constant_(m.weight, 1.0)
80
+
81
+ def forward(self, x, fx, T=None, geo=None):
82
+ if self.args.unified_pos:
83
+ x = self.pos.repeat(x.shape[0], 1, 1)
84
+
85
+ if fx is not None:
86
+ x = torch.cat((x, fx), -1)
87
+ fx = self.preprocess_z(x)
88
+ x = self.preprocess_x(x)
89
+ else:
90
+ fx = self.preprocess_z(x)
91
+ x = self.preprocess_x(x)
92
+
93
+ fx = fx + self.placeholder[None, None, :]
94
+
95
+ if T is not None:
96
+ Time_emb = timestep_embedding(T, self.args.n_hidden)
97
+ Time_emb = self.time_fc(Time_emb)
98
+ if Time_emb.ndim == 2:
99
+ Time_emb = Time_emb.unsqueeze(1)
100
+ fx = fx + Time_emb
101
+
102
+ for block in self.blocks:
103
+ x, fx = block(x, fx)
104
+ return fx
model/PointNet.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch_geometric.nn as nng
4
+ from onescience.modules.embedding import timestep_embedding, unified_pos_embedding
5
+ from onescience.modules.mlp.MLP import StandardMLP
6
+
7
+ class Model(nn.Module):
8
+ """
9
+ PointNet 模型。
10
+
11
+ 用于处理点云数据,通过 MLP 提取局部特征,并使用全局最大池化提取全局特征。
12
+ """
13
+ def __init__(self, args, device):
14
+ super(Model, self).__init__()
15
+ self.__name__ = "PointNet"
16
+
17
+ # 1. Input Block
18
+ self.in_block = StandardMLP(
19
+ input_dim=args.n_hidden,
20
+ output_dim=args.n_hidden * 2,
21
+ hidden_dims=[args.n_hidden * 2],
22
+ activation=args.act,
23
+ use_bias=True
24
+ )
25
+
26
+ # 2. Max Pooling Block
27
+ self.max_block = StandardMLP(
28
+ input_dim=args.n_hidden * 2,
29
+ output_dim=args.n_hidden * 32,
30
+ hidden_dims=[args.n_hidden * 8],
31
+ activation=args.act,
32
+ use_bias=True
33
+ )
34
+
35
+ # 3. Output Block
36
+ self.out_block = StandardMLP(
37
+ input_dim=args.n_hidden * (2 + 32), # 34 * hidden
38
+ output_dim=args.n_hidden * 4,
39
+ hidden_dims=[args.n_hidden * 16],
40
+ activation=args.act,
41
+ use_bias=True
42
+ )
43
+
44
+ # 4. Encoder
45
+ self.encoder = StandardMLP(
46
+ input_dim=args.fun_dim + args.space_dim,
47
+ output_dim=args.n_hidden,
48
+ hidden_dims=[args.n_hidden * 2],
49
+ activation=args.act,
50
+ use_bias=True
51
+ )
52
+
53
+ # 5. Decoder
54
+ self.decoder = StandardMLP(
55
+ input_dim=args.n_hidden,
56
+ output_dim=args.out_dim,
57
+ hidden_dims=[args.n_hidden * 2],
58
+ activation=args.act,
59
+ use_bias=True
60
+ )
61
+
62
+ self.fcfinal = nn.Linear(args.n_hidden * 4, args.n_hidden)
63
+
64
+ def forward(self, x, fx, T=None, geo=None):
65
+ if geo is None:
66
+ raise ValueError("Please provide edge index for Graph Neural Networks")
67
+
68
+ # 兼容 batch_size = 1 输入
69
+ if x.dim() == 3:
70
+ x = x.squeeze(0) # [1, N, C] → [N, C]
71
+ if fx is not None and fx.dim() == 3:
72
+ fx = fx.squeeze(0)
73
+
74
+ assert (
75
+ x.size(0) > 0 # Simple check
76
+ ), "Input cannot be empty"
77
+
78
+ # 构造 batch 索引
79
+ batch = torch.zeros(x.shape[0], dtype=torch.long, device=x.device)
80
+
81
+ # 编码 + 局部特征提取
82
+ z = torch.cat((x, fx), dim=-1).float()
83
+ z = self.encoder(z)
84
+ z = self.in_block(z)
85
+
86
+ # 全局特征(max pooling)
87
+ global_coef = self.max_block(z)
88
+ global_coef = nng.global_max_pool(global_coef, batch=batch)
89
+
90
+ # 重复 global coef 到每个点
91
+ nb_points = torch.tensor([batch.shape[0]], device=z.device)
92
+ global_coef = global_coef.repeat_interleave(nb_points, dim=0)
93
+
94
+ # 拼接全局 + 局部特征
95
+ z = torch.cat([z, global_coef], dim=1)
96
+ z = self.out_block(z)
97
+ z = self.fcfinal(z)
98
+ z = self.decoder(z)
99
+
100
+ return z.unsqueeze(0)
model/RegDGCNN.py ADDED
@@ -0,0 +1,361 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ import os
5
+ import copy
6
+ import math
7
+ import numpy as np
8
+ from torch_geometric.data import Data, DataLoader
9
+ from torch_geometric.nn import (
10
+ GCNConv,
11
+ GATConv,
12
+ global_mean_pool,
13
+ global_max_pool,
14
+ JumpingKnowledge,
15
+ )
16
+ from torch.nn import Sequential, Linear, ReLU, BatchNorm1d, Dropout
17
+ from torch_geometric.nn import BatchNorm
18
+
19
+
20
+ def knn(x, k):
21
+ """
22
+ Computes the k-nearest neighbors for each point in x.
23
+
24
+ Args:
25
+ x (torch.Tensor): The input tensor of shape (batch_size, num_dims, num_points).
26
+ k (int): The number of nearest neighbors to find.
27
+
28
+ Returns:
29
+ torch.Tensor: Indices of the k-nearest neighbors for each point, shape (batch_size, num_points, k).
30
+ """
31
+ # Calculate pairwise distance, shape (batch_size, num_points, num_points)
32
+ inner = -2 * torch.matmul(x.transpose(2, 1), x)
33
+ xx = torch.sum(x**2, dim=1, keepdim=True)
34
+ pairwise_distance = -xx - inner - xx.transpose(2, 1)
35
+
36
+ # Retrieve the indices of the k nearest neighbors
37
+ idx = pairwise_distance.topk(k=k, dim=-1)[1]
38
+ return idx
39
+
40
+
41
+ def get_graph_feature(x, k=20, idx=None):
42
+ """
43
+ Constructs local graph features for each point by finding its k-nearest neighbors and
44
+ concatenating the relative position vectors.
45
+
46
+ Args:
47
+ x (torch.Tensor): The input tensor of shape (batch_size, num_dims, num_points).
48
+ k (int): The number of neighbors to consider for graph construction.
49
+ idx (torch.Tensor, optional): Precomputed k-nearest neighbor indices.
50
+
51
+ Returns:
52
+ torch.Tensor: The constructed graph features of shape (batch_size, 2*num_dims, num_points, k).
53
+ """
54
+ batch_size = x.size(0)
55
+ num_points = x.size(2)
56
+ x = x.view(batch_size, -1, num_points)
57
+
58
+ # Compute k-nearest neighbors if not provided
59
+ if idx is None:
60
+ idx = knn(x, k=k)
61
+
62
+ # Prepare indices for gathering
63
+ device = x.device
64
+ idx_base = torch.arange(0, batch_size, device=device).view(-1, 1, 1) * num_points
65
+ idx = idx + idx_base
66
+ idx = idx.view(-1)
67
+
68
+ _, num_dims, _ = x.size()
69
+ x = x.transpose(2, 1).contiguous()
70
+
71
+ # Gather neighbors for each point to construct local regions
72
+ feature = x.view(batch_size * num_points, -1)[idx, :]
73
+ feature = feature.view(batch_size, num_points, k, num_dims)
74
+
75
+ # Expand x to match the dimensions for broadcasting subtraction
76
+ x = x.view(batch_size, num_points, 1, num_dims).repeat(1, 1, k, 1)
77
+
78
+ # Concatenate the original point features with the relative positions to form the graph features
79
+ feature = torch.cat((feature - x, x), dim=3).permute(0, 3, 1, 2).contiguous()
80
+
81
+ return feature
82
+
83
+
84
+ # class Model(nn.Module):
85
+ # """
86
+ # Deep Graph Convolutional Neural Network for Regression Tasks (RegDGCNN) for processing 3D point cloud data.
87
+
88
+ # This network architecture extracts hierarchical features from point clouds using graph-based convolutions,
89
+ # enabling effective learning of spatial structures.
90
+ # """
91
+
92
+ # def __init__(self, args, device):
93
+ # """
94
+ # Initializes the RegDGCNN model with specified configurations.
95
+
96
+ # Args:
97
+ # args (dict): Configuration parameters including 'k' for the number of neighbors, 'emb_dims' for embedding
98
+ # dimensions, and 'dropout' rate.
99
+ # output_channels (int): Number of output channels (e.g., for drag prediction, this is 1).
100
+ # """
101
+ # super(Model, self).__init__()
102
+ # self.__name__ = 'RegDGCNN'
103
+ # self.args = args
104
+ # self.k = 20 # Number of nearest neighbors
105
+
106
+ # # Batch normalization layers to stabilize and accelerate training
107
+ # self.bn1 = nn.BatchNorm2d(args.n_hidden)
108
+ # self.bn2 = nn.BatchNorm2d(args.n_hidden * 2)
109
+ # self.bn3 = nn.BatchNorm2d(args.n_hidden * 2)
110
+ # self.bn4 = nn.BatchNorm2d(args.n_hidden * 4)
111
+ # self.bn5 = nn.BatchNorm1d(args.emb_dims)
112
+
113
+ # # EdgeConv layers: Convolutional layers leveraging local neighborhood information
114
+ # self.conv1 = nn.Sequential(nn.Conv2d(2*(args.fun_dim + args.space_dim), args.n_hidden, kernel_size=1, bias=False),
115
+ # self.bn1,
116
+ # nn.LeakyReLU(negative_slope=0.2))
117
+ # self.conv2 = nn.Sequential(nn.Conv2d(args.n_hidden * 2, args.n_hidden * 2, kernel_size=1, bias=False),
118
+ # self.bn2,
119
+ # nn.LeakyReLU(negative_slope=0.2))
120
+ # self.conv3 = nn.Sequential(nn.Conv2d(args.n_hidden * 4, args.n_hidden * 2, kernel_size=1, bias=False),
121
+ # self.bn3,
122
+ # nn.LeakyReLU(negative_slope=0.2))
123
+ # self.conv4 = nn.Sequential(nn.Conv2d(args.n_hidden * 4, args.n_hidden * 4, kernel_size=1, bias=False),
124
+ # self.bn4,
125
+ # nn.LeakyReLU(negative_slope=0.2))
126
+ # self.conv5 = nn.Sequential(nn.Conv1d(args.n_hidden * 9, args.emb_dims, kernel_size=1, bias=False),
127
+ # self.bn5,
128
+ # nn.LeakyReLU(negative_slope=0.2))
129
+
130
+ # # Fully connected layers to interpret the extracted features and make predictions
131
+ # self.linear1 = nn.Linear(args.emb_dims*2, 128, bias=False)
132
+ # self.bn6 = nn.LayerNorm(128)
133
+ # self.dp1 = nn.Dropout(p=args.dropout)
134
+
135
+ # self.linear2 = nn.Linear(128, 64)
136
+ # self.bn7 = nn.LayerNorm(64)
137
+ # self.dp2 = nn.Dropout(p=args.dropout)
138
+
139
+ # self.linear3 = nn.Linear(64, 32)
140
+ # self.bn8 = nn.LayerNorm(32)
141
+ # self.dp3 = nn.Dropout(p=args.dropout)
142
+
143
+ # self.linear4 = nn.Linear(32, 16)
144
+ # self.bn9 = nn.LayerNorm(16)
145
+ # self.dp4 = nn.Dropout(p=args.dropout)
146
+
147
+ # self.linear5 = nn.Linear(16, args.out_dim) # The final output layer
148
+
149
+ # def _make_norm_layer(self, channels):
150
+ # """创建归一化层,支持batch size=1"""
151
+ # return nn.GroupNorm(num_groups=min(32, channels), num_channels=channels)
152
+
153
+ # def forward(self, x, fx, T=None, geo=None):
154
+ # """
155
+ # Forward pass of the model to process input data and predict outputs.
156
+
157
+ # Args:
158
+ # x (torch.Tensor): Input tensor representing a batch of point clouds.
159
+
160
+ # Returns:
161
+ # torch.Tensor: Model predictions for the input batch.
162
+ # """
163
+
164
+ # batch_size = x.size(0)
165
+
166
+ # # 兼容 batch_size = 1 输入:去除 batch 维度
167
+ # x = torch.cat([x, fx], dim=-1)
168
+ # x = x.permute(0, 2, 1)
169
+
170
+ # print(f"x:{x.shape}")
171
+ # # Extract graph features and apply EdgeConv blocks
172
+ # x = get_graph_feature(x, k=self.k) # (batch_size, 3, num_points) -> (batch_size, 3*2, num_points, k)
173
+ # print(f"x:{x.shape}")
174
+ # x = self.conv1(x) # (batch_size, 3*2, num_points, k) -> (batch_size, 256, num_points, k)
175
+
176
+ # # Global max pooling
177
+ # x1 = x.max(dim=-1, keepdim=False)[0] # (batch_size, 64, num_points, k) -> (batch_size, 64, num_points)
178
+
179
+ # # Repeat the process for subsequent EdgeConv blocks
180
+ # x = get_graph_feature(x1, k=self.k) # (batch_size, 256, num_points) -> (batch_size, 256*2, num_points, k)
181
+ # x = self.conv2(x) # (batch_size, 256*2, num_points, k) -> (batch_size, 512, num_points, k)
182
+ # x2 = x.max(dim=-1, keepdim=False)[0] # (batch_size, 512, num_points, k) -> (batch_size, 512, num_points)
183
+
184
+ # x = get_graph_feature(x2, k=self.k) # (batch_size, 512, num_points) -> (batch_size, 512*2, num_points, k)
185
+ # x = self.conv3(x) # (batch_size, 512*2, num_points, k) -> (batch_size, 512, num_points, k)
186
+ # x3 = x.max(dim=-1, keepdim=False)[0] # (batch_size, 512, num_points, k) -> (batch_size, 512, num_points)
187
+
188
+ # x = get_graph_feature(x3, k=self.k) # (batch_size, 512, num_points) -> (batch_size, 512*2, num_points, k)
189
+ # x = self.conv4(x) # (batch_size, 512*2, num_points, k) -> (batch_size, 1024, num_points, k)
190
+ # x4 = x.max(dim=-1, keepdim=False)[0] # (batch_size, 1024, num_points, k) -> (batch_size, 1024, num_points)
191
+
192
+ # # Concatenate features from all EdgeConv blocks
193
+ # x = torch.cat((x1, x2, x3, x4), dim=1) # (batch_size, 256+512+512+1024, num_points)
194
+
195
+ # # Apply the final convolutional block
196
+ # x = self.conv5(x) # (batch_size, 256+512+512+1024, num_points) -> (batch_size, emb_dims, num_points)
197
+ # # Combine global max and average pooling features
198
+ # # (batch_size, emb_dims, num_points) -> (batch_size, emb_dims)
199
+ # x1 = F.adaptive_max_pool1d(x, 1).view(batch_size, -1)
200
+ # # (batch_size, emb_dims, num_points) -> (batch_size, emb_dims)
201
+ # x2 = F.adaptive_avg_pool1d(x, 1).view(batch_size, -1)
202
+ # x = torch.cat((x1, x2), 1) # (batch_size, emb_dims*2)
203
+ # print(f"x:{x.shape}")
204
+ # # Process features through fully connected layers with dropout and batch normalization
205
+ # x = F.leaky_relu(self.bn6(self.linear1(x)), negative_slope=0.2) # (batch_size, emb_dims*2) -> (batch_size, 128)
206
+ # x = self.dp1(x)
207
+ # x = F.leaky_relu(self.bn7(self.linear2(x)), negative_slope=0.2) # (batch_size, 128) -> (batch_size, 64)
208
+ # x = self.dp2(x)
209
+ # x = F.leaky_relu(self.bn8(self.linear3(x)), negative_slope=0.2) # (batch_size, 64) -> (batch_size, 32)
210
+ # x = self.dp3(x)
211
+ # x = F.leaky_relu(self.bn9(self.linear4(x)), negative_slope=0.2) # (batch_size, 32) -> (batch_size, 16)
212
+ # x = self.dp4(x)
213
+
214
+ # # Final linear layer to produce the output
215
+ # x = self.linear5(x) # (batch_size, 16) -> (batch_size, 1)
216
+ # print(f"x:{x.shape}")
217
+ # x = x.permute(0, 2, 1)
218
+ # return x
219
+
220
+
221
+ class Model(nn.Module):
222
+ """
223
+ Deep Graph Convolutional Neural Network for Regression Tasks (RegDGCNN) for processing 3D point cloud data.
224
+
225
+ This network architecture extracts hierarchical features from point clouds using graph-based convolutions,
226
+ enabling effective learning of spatial structures.
227
+ """
228
+
229
+ def __init__(self, args, device):
230
+ """
231
+ Initializes the RegDGCNN model with specified configurations.
232
+
233
+ Args:
234
+ args (dict): Configuration parameters including 'k' for the number of neighbors, 'emb_dims' for embedding
235
+ dimensions, and 'dropout' rate.
236
+ output_channels (int): Number of output channels (e.g., for drag prediction, this is 1).
237
+ """
238
+ super(Model, self).__init__()
239
+ self.__name__ = "RegDGCNN"
240
+ self.args = args
241
+ self.k = 40 # Number of nearest neighbors
242
+
243
+ # Batch normalization layers to stabilize and accelerate training
244
+ self.bn1 = nn.BatchNorm2d(args.n_hidden)
245
+ self.bn2 = nn.BatchNorm2d(args.n_hidden * 2)
246
+ self.bn3 = nn.BatchNorm2d(args.n_hidden * 2)
247
+ self.bn4 = nn.BatchNorm2d(args.n_hidden * 4)
248
+ self.bn5 = nn.BatchNorm1d(args.emb_dims)
249
+
250
+ # EdgeConv layers: Convolutional layers leveraging local neighborhood information
251
+ self.conv1 = nn.Sequential(
252
+ nn.Conv2d(
253
+ 2 * (args.fun_dim + args.space_dim),
254
+ args.n_hidden,
255
+ kernel_size=1,
256
+ bias=False,
257
+ ),
258
+ self.bn1,
259
+ nn.LeakyReLU(negative_slope=0.2),
260
+ )
261
+ self.conv2 = nn.Sequential(
262
+ nn.Conv2d(args.n_hidden * 2, args.n_hidden * 2, kernel_size=1, bias=False),
263
+ self.bn2,
264
+ nn.LeakyReLU(negative_slope=0.2),
265
+ )
266
+ self.conv3 = nn.Sequential(
267
+ nn.Conv2d(args.n_hidden * 4, args.n_hidden * 2, kernel_size=1, bias=False),
268
+ self.bn3,
269
+ nn.LeakyReLU(negative_slope=0.2),
270
+ )
271
+ self.conv4 = nn.Sequential(
272
+ nn.Conv2d(args.n_hidden * 4, args.n_hidden * 4, kernel_size=1, bias=False),
273
+ self.bn4,
274
+ nn.LeakyReLU(negative_slope=0.2),
275
+ )
276
+ self.conv5 = nn.Sequential(
277
+ nn.Conv1d(args.n_hidden * 9, args.emb_dims, kernel_size=1, bias=False),
278
+ self.bn5,
279
+ nn.LeakyReLU(negative_slope=0.2),
280
+ )
281
+
282
+ # Fully connected layers to interpret the extracted features and make predictions
283
+ self.point_pred = nn.Sequential(
284
+ nn.Linear(args.emb_dims, 64), nn.ReLU(), nn.Linear(64, args.out_dim)
285
+ )
286
+
287
+ def _make_norm_layer(self, channels):
288
+ return nn.GroupNorm(num_groups=min(32, channels), num_channels=channels)
289
+
290
+ def forward(self, x, fx, T=None, geo=None):
291
+ """
292
+ Forward pass of the model to process input data and predict outputs.
293
+
294
+ Args:
295
+ x (torch.Tensor): Input tensor representing a batch of point clouds.
296
+
297
+ Returns:
298
+ torch.Tensor: Model predictions for the input batch.
299
+ """
300
+
301
+ batch_size = x.size(0)
302
+
303
+ # 兼容 batch_size = 1 输入:去除 batch 维度
304
+ x = torch.cat([x, fx], dim=-1)
305
+ x = x.permute(0, 2, 1)
306
+
307
+ # Extract graph features and apply EdgeConv blocks
308
+ x = get_graph_feature(
309
+ x, k=self.k
310
+ ) # (batch_size, 3, num_points) -> (batch_size, 3*2, num_points, k)
311
+ x = self.conv1(
312
+ x
313
+ ) # (batch_size, 3*2, num_points, k) -> (batch_size, 256, num_points, k)
314
+ # Global max pooling
315
+ x1 = x.max(dim=-1, keepdim=False)[
316
+ 0
317
+ ] # (batch_size, 64, num_points, k) -> (batch_size, 64, num_points)
318
+
319
+ # Repeat the process for subsequent EdgeConv blocks
320
+ x = get_graph_feature(
321
+ x1, k=self.k
322
+ ) # (batch_size, 256, num_points) -> (batch_size, 256*2, num_points, k)
323
+ x = self.conv2(
324
+ x
325
+ ) # (batch_size, 256*2, num_points, k) -> (batch_size, 512, num_points, k)
326
+ x2 = x.max(dim=-1, keepdim=False)[
327
+ 0
328
+ ] # (batch_size, 512, num_points, k) -> (batch_size, 512, num_points)
329
+
330
+ x = get_graph_feature(
331
+ x2, k=self.k
332
+ ) # (batch_size, 512, num_points) -> (batch_size, 512*2, num_points, k)
333
+ x = self.conv3(
334
+ x
335
+ ) # (batch_size, 512*2, num_points, k) -> (batch_size, 512, num_points, k)
336
+ x3 = x.max(dim=-1, keepdim=False)[
337
+ 0
338
+ ] # (batch_size, 512, num_points, k) -> (batch_size, 512, num_points)
339
+
340
+ x = get_graph_feature(
341
+ x3, k=self.k
342
+ ) # (batch_size, 512, num_points) -> (batch_size, 512*2, num_points, k)
343
+ x = self.conv4(
344
+ x
345
+ ) # (batch_size, 512*2, num_points, k) -> (batch_size, 1024, num_points, k)
346
+ x4 = x.max(dim=-1, keepdim=False)[
347
+ 0
348
+ ] # (batch_size, 1024, num_points, k) -> (batch_size, 1024, num_points)
349
+
350
+ # Concatenate features from all EdgeConv blocks
351
+ x = torch.cat(
352
+ (x1, x2, x3, x4), dim=1
353
+ ) # (batch_size, 256+512+512+1024, num_points)
354
+
355
+ # Apply the final convolutional block
356
+ x = self.conv5(
357
+ x
358
+ ) # (batch_size, 256+512+512+1024, num_points) -> (batch_size, emb_dims, num_points)
359
+ x = x.permute(0, 2, 1) # (batch_size, num_points, emb_dims)
360
+ out = self.point_pred(x) # 点级预测层,输出 (batch_size, num_points, out_dim)
361
+ return out
model/Swin_Transformer.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ import torch.utils.checkpoint as checkpoint
5
+ from timm.layers import trunc_normal_
6
+ from onescience.modules.mlp.MLP import StandardMLP
7
+ from onescience.modules.transformer.SwinTransformerBlock import SwinTransformerBlock
8
+ from onescience.modules.embedding import timestep_embedding, unified_pos_embedding
9
+
10
+ class BasicLayer(nn.Module):
11
+ """
12
+ Swin Transformer Layer (Stage).
13
+ 封装了多个 SwinTransformerBlock。
14
+ """
15
+ def __init__(
16
+ self,
17
+ dim,
18
+ input_resolution,
19
+ depth,
20
+ num_heads,
21
+ window_size,
22
+ mlp_ratio=4.0,
23
+ qkv_bias=True,
24
+ qk_scale=None,
25
+ drop=0.0,
26
+ attn_drop=0.0,
27
+ drop_path=0.0,
28
+ norm_layer=nn.LayerNorm,
29
+ downsample=None,
30
+ use_checkpoint=False,
31
+ fused_window_process=False,
32
+ ):
33
+ super().__init__()
34
+ self.dim = dim
35
+ self.input_resolution = input_resolution
36
+ self.depth = depth
37
+ self.use_checkpoint = use_checkpoint
38
+
39
+ # build blocks
40
+ self.blocks = nn.ModuleList([
41
+ SwinTransformerBlock(
42
+ dim=dim,
43
+ input_resolution=input_resolution,
44
+ num_heads=num_heads,
45
+ window_size=window_size,
46
+ shift_size=0 if (i % 2 == 0) else window_size // 2,
47
+ mlp_ratio=mlp_ratio,
48
+ qkv_bias=qkv_bias,
49
+ qk_scale=qk_scale,
50
+ drop=drop,
51
+ attn_drop=attn_drop,
52
+ drop_path=(drop_path[i] if isinstance(drop_path, list) else drop_path),
53
+ norm_layer=norm_layer,
54
+ fused_window_process=fused_window_process,
55
+ )
56
+ for i in range(depth)
57
+ ])
58
+
59
+ # patch merging layer
60
+ if downsample is not None:
61
+ self.downsample = downsample(
62
+ input_resolution, dim=dim, norm_layer=norm_layer
63
+ )
64
+ else:
65
+ self.downsample = None
66
+
67
+ def forward(self, x):
68
+ for blk in self.blocks:
69
+ if self.use_checkpoint:
70
+ x = checkpoint.checkpoint(blk, x)
71
+ else:
72
+ x = blk(x)
73
+ if self.downsample is not None:
74
+ x = self.downsample(x)
75
+ return x
76
+
77
+
78
+ class Model(nn.Module):
79
+ """
80
+ Swin Transformer 主模型。
81
+ """
82
+ def __init__(self, args, device, window_size=4):
83
+ super(Model, self).__init__()
84
+ self.__name__ = "SwinTransformer"
85
+ self.args = args
86
+
87
+ if args.geotype != "structured_2D":
88
+ raise ValueError("Swin Transformer only supports Structured 2D geometry")
89
+
90
+ # 1. Embedding & Preprocessing
91
+ if args.unified_pos:
92
+ self.pos = unified_pos_embedding(args.shapelist, args.ref, device=device)
93
+ in_dim = args.fun_dim + args.ref ** len(args.shapelist)
94
+ else:
95
+ in_dim = args.fun_dim + args.space_dim
96
+
97
+ self.preprocess = StandardMLP(
98
+ input_dim=in_dim,
99
+ output_dim=args.n_hidden,
100
+ hidden_dims=[args.n_hidden * 2],
101
+ activation=args.act,
102
+ use_bias=True
103
+ )
104
+
105
+ if args.time_input:
106
+ self.time_fc = nn.Sequential(
107
+ nn.Linear(args.n_hidden, args.n_hidden),
108
+ nn.SiLU(),
109
+ nn.Linear(args.n_hidden, args.n_hidden),
110
+ )
111
+
112
+ self.placeholder = nn.Parameter(
113
+ (1 / (args.n_hidden)) * torch.rand(args.n_hidden, dtype=torch.float)
114
+ )
115
+
116
+ self.padding = [
117
+ (window_size - size % window_size) % window_size for size in args.shapelist
118
+ ]
119
+ self.augmented_resolution = [
120
+ (self.padding[i] + args.shapelist[i]) for i in range(len(self.padding))
121
+ ]
122
+
123
+ # 3. Swin Layers
124
+ self.blocks = nn.ModuleList([
125
+ BasicLayer(
126
+ dim=args.n_hidden,
127
+ input_resolution=self.augmented_resolution,
128
+ depth=2,
129
+ num_heads=args.n_heads,
130
+ window_size=window_size,
131
+ )
132
+ for _ in range(args.n_layers)
133
+ ])
134
+
135
+ # 4. Projectors
136
+ self.fc1 = nn.Linear(args.n_hidden, args.n_hidden * 2)
137
+ self.fc2 = nn.Linear(args.n_hidden * 2, args.out_dim)
138
+
139
+ self.initialize_weights()
140
+
141
+ def initialize_weights(self):
142
+ self.apply(self._init_weights)
143
+
144
+ def _init_weights(self, m):
145
+ if isinstance(m, nn.Linear):
146
+ trunc_normal_(m.weight, std=0.02)
147
+ if isinstance(m, nn.Linear) and m.bias is not None:
148
+ nn.init.constant_(m.bias, 0)
149
+ elif isinstance(m, (nn.LayerNorm, nn.BatchNorm1d)):
150
+ nn.init.constant_(m.bias, 0)
151
+ nn.init.constant_(m.weight, 1.0)
152
+
153
+ def structured_geo(self, x, fx, T=None):
154
+ B, N, _ = x.shape
155
+ if self.args.unified_pos:
156
+ x = self.pos.repeat(x.shape[0], 1, 1)
157
+
158
+ if fx is not None:
159
+ fx = torch.cat((x, fx), -1)
160
+ fx = self.preprocess(fx)
161
+ else:
162
+ fx = self.preprocess(x)
163
+
164
+ fx = fx + self.placeholder[None, None, :]
165
+
166
+ if T is not None:
167
+ Time_emb = timestep_embedding(T, self.args.n_hidden)
168
+ Time_emb = self.time_fc(Time_emb)
169
+ if Time_emb.ndim == 2:
170
+ Time_emb = Time_emb.unsqueeze(1)
171
+ fx = fx + Time_emb
172
+
173
+ ## aug shape
174
+ fx = fx.permute(0, 2, 1).reshape(B, self.args.n_hidden, *self.args.shapelist)
175
+
176
+ if not all(item == 0 for item in self.padding):
177
+ if len(self.args.shapelist) == 2:
178
+ fx = F.pad(fx, [0, self.padding[1], 0, self.padding[0]])
179
+ elif len(self.args.shapelist) == 3:
180
+ fx = F.pad(fx, [0, self.padding[2], 0, self.padding[1], 0, self.padding[0]])
181
+
182
+ fx = fx.reshape(B, self.args.n_hidden, -1).permute(0, 2, 1)
183
+
184
+ ## swin transformer
185
+ for block in self.blocks:
186
+ fx = block(fx)
187
+
188
+ ## back to original shape
189
+ fx = fx.permute(0, 2, 1).reshape(
190
+ B, self.args.n_hidden, *self.augmented_resolution
191
+ )
192
+
193
+ if not all(item == 0 for item in self.padding):
194
+ if len(self.args.shapelist) == 2:
195
+ fx = fx[..., : -self.padding[0], : -self.padding[1]]
196
+ elif len(self.args.shapelist) == 3:
197
+ fx = fx[..., : -self.padding[0], : -self.padding[1], : -self.padding[2]]
198
+
199
+ fx = fx.reshape(B, self.args.n_hidden, -1).permute(0, 2, 1)
200
+
201
+ ## projection
202
+ fx = self.fc1(fx)
203
+ fx = F.gelu(fx)
204
+ fx = self.fc2(fx)
205
+ return fx
206
+
207
+ def forward(self, x, fx, T=None, geo=None):
208
+ if self.args.geotype == "structured_2D":
209
+ return self.structured_geo(x, fx, T)
210
+ else:
211
+ raise ValueError("Swin Transformer only supports Structured 2D geometry")
model/Transformer.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ import numpy as np
5
+ from timm.layers import trunc_normal_
6
+ from onescience.modules.attention.flashattention import FlashAttention
7
+ from onescience.modules.mlp.MLP import StandardMLP
8
+ from onescience.modules.embedding import timestep_embedding, unified_pos_embedding
9
+ from einops import rearrange, repeat
10
+
11
+ class Transformer_block(nn.Module):
12
+ """
13
+ Transformer encoder block.
14
+ 包含多头自注意力和前馈神经网络,最后一层可选是否直接输出预测维度。
15
+ """
16
+
17
+ def __init__(
18
+ self,
19
+ num_heads: int,
20
+ hidden_dim: int,
21
+ dropout: float,
22
+ act="gelu",
23
+ mlp_ratio=4,
24
+ last_layer=False,
25
+ out_dim=1,
26
+ ):
27
+ super().__init__()
28
+ self.last_layer = last_layer
29
+ self.ln_1 = nn.LayerNorm(hidden_dim)
30
+
31
+ self.Attn = FlashAttention(
32
+ dim=hidden_dim,
33
+ heads=num_heads,
34
+ dim_head=hidden_dim // num_heads,
35
+ dropout=dropout,
36
+ )
37
+
38
+ self.ln_2 = nn.LayerNorm(hidden_dim)
39
+
40
+ self.mlp = StandardMLP(
41
+ input_dim=hidden_dim,
42
+ output_dim=hidden_dim,
43
+ hidden_dims=[hidden_dim * mlp_ratio],
44
+ activation=act,
45
+ use_bias=True
46
+ )
47
+
48
+ if self.last_layer:
49
+ self.ln_3 = nn.LayerNorm(hidden_dim)
50
+ self.mlp2 = nn.Linear(hidden_dim, out_dim)
51
+
52
+ def forward(self, fx):
53
+ fx = self.Attn(self.ln_1(fx)) + fx
54
+ fx = self.mlp(self.ln_2(fx)) + fx
55
+ if self.last_layer:
56
+ return self.mlp2(self.ln_3(fx))
57
+ else:
58
+ return fx
59
+
60
+
61
+ class Model(nn.Module):
62
+ """
63
+ 标准的 Transformer 模型架构。
64
+ 通过堆叠 Transformer_block 处理物理场数据。
65
+ """
66
+ def __init__(self, args, device):
67
+ super(Model, self).__init__()
68
+ self.__name__ = "Transformer"
69
+ self.args = args
70
+
71
+ ## embedding
72
+ if (
73
+ args.unified_pos and args.geotype != "unstructured"
74
+ ): # only for structured mesh
75
+ self.pos = unified_pos_embedding(args.shapelist, args.ref, device=device)
76
+ input_dim = args.fun_dim + args.ref ** len(args.shapelist)
77
+ else:
78
+ input_dim = args.fun_dim + args.space_dim
79
+
80
+ self.preprocess = StandardMLP(
81
+ input_dim=input_dim,
82
+ output_dim=args.n_hidden,
83
+ hidden_dims=[args.n_hidden * 2],
84
+ activation=args.act,
85
+ use_bias=True
86
+ )
87
+
88
+ if args.time_input:
89
+ self.time_fc = nn.Sequential(
90
+ nn.Linear(args.n_hidden, args.n_hidden),
91
+ nn.SiLU(),
92
+ nn.Linear(args.n_hidden, args.n_hidden),
93
+ )
94
+
95
+ self.blocks = nn.ModuleList(
96
+ [
97
+ Transformer_block(
98
+ num_heads=args.n_heads,
99
+ hidden_dim=args.n_hidden,
100
+ dropout=args.dropout,
101
+ act=args.act,
102
+ mlp_ratio=args.mlp_ratio,
103
+ out_dim=args.out_dim,
104
+ last_layer=(_ == args.n_layers - 1),
105
+ )
106
+ for _ in range(args.n_layers)
107
+ ]
108
+ )
109
+
110
+ self.placeholder = nn.Parameter(
111
+ (1 / (args.n_hidden)) * torch.rand(args.n_hidden, dtype=torch.float)
112
+ )
113
+ self.initialize_weights()
114
+
115
+ def initialize_weights(self):
116
+ self.apply(self._init_weights)
117
+
118
+ def _init_weights(self, m):
119
+ if isinstance(m, nn.Linear):
120
+ trunc_normal_(m.weight, std=0.02)
121
+ if isinstance(m, nn.Linear) and m.bias is not None:
122
+ nn.init.constant_(m.bias, 0)
123
+ elif isinstance(m, (nn.LayerNorm, nn.BatchNorm1d)):
124
+ nn.init.constant_(m.bias, 0)
125
+ nn.init.constant_(m.weight, 1.0)
126
+
127
+ def forward(self, x, fx, T=None, geo=None):
128
+ if self.args.unified_pos:
129
+ x = self.pos.repeat(x.shape[0], 1, 1)
130
+
131
+ if fx is not None:
132
+ fx = torch.cat((x, fx), -1)
133
+ fx = self.preprocess(fx)
134
+ else:
135
+ fx = self.preprocess(x)
136
+
137
+ fx = fx + self.placeholder[None, None, :]
138
+
139
+ if T is not None:
140
+ Time_emb = timestep_embedding(T, self.args.n_hidden)
141
+ Time_emb = self.time_fc(Time_emb)
142
+ if Time_emb.ndim == 2:
143
+ Time_emb = Time_emb.unsqueeze(1)
144
+ fx = fx + Time_emb
145
+
146
+ for block in self.blocks:
147
+ fx = block(fx)
148
+
149
+ return fx
model/Transolver.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import numpy as np
4
+ from timm.layers import trunc_normal_
5
+ from onescience.modules.mlp.MLP import StandardMLP
6
+ from onescience.modules.transformer.Transolver_block import Transolver_block
7
+ from onescience.modules.embedding import timestep_embedding, unified_pos_embedding
8
+
9
+ class Model(nn.Module):
10
+ """
11
+ Transolver 模型。
12
+ 通过物理启发的切片机制 (Slicing) 解决 PDE 和物理场预测问题。
13
+ """
14
+ def __init__(self, args, device):
15
+ super(Model, self).__init__()
16
+ self.__name__ = "Transolver"
17
+ self.args = args
18
+
19
+ ## embedding
20
+ if (
21
+ args.unified_pos and args.geotype != "unstructured"
22
+ ):
23
+ self.pos = unified_pos_embedding(args.shapelist, args.ref, device=device)
24
+ self.preprocess = StandardMLP(
25
+ input_dim=args.fun_dim + args.ref ** len(args.shapelist),
26
+ output_dim=args.n_hidden,
27
+ hidden_dims=[args.n_hidden * 2],
28
+ activation=args.act,
29
+ use_bias=True
30
+ )
31
+ else:
32
+ self.preprocess = StandardMLP(
33
+ input_dim=args.fun_dim + args.space_dim,
34
+ output_dim=args.n_hidden,
35
+ hidden_dims=[args.n_hidden * 2],
36
+ activation=args.act,
37
+ use_bias=True
38
+ )
39
+
40
+ if args.time_input:
41
+ self.time_fc = nn.Sequential(
42
+ nn.Linear(args.n_hidden, args.n_hidden),
43
+ nn.SiLU(),
44
+ nn.Linear(args.n_hidden, args.n_hidden),
45
+ )
46
+
47
+ ## models
48
+ self.blocks = nn.ModuleList(
49
+ [
50
+ Transolver_block(
51
+ num_heads=args.n_heads,
52
+ hidden_dim=args.n_hidden,
53
+ dropout=args.dropout,
54
+ act=args.act,
55
+ mlp_ratio=args.mlp_ratio,
56
+ out_dim=args.out_dim,
57
+ slice_num=args.slice_num,
58
+ last_layer=(_ == args.n_layers - 1),
59
+ geotype=args.geotype,
60
+ shapelist=args.shapelist,
61
+ )
62
+ for _ in range(args.n_layers)
63
+ ]
64
+ )
65
+
66
+ self.placeholder = nn.Parameter(
67
+ (1 / (args.n_hidden)) * torch.rand(args.n_hidden, dtype=torch.float)
68
+ )
69
+ self.initialize_weights()
70
+
71
+ def initialize_weights(self):
72
+ self.apply(self._init_weights)
73
+
74
+ def _init_weights(self, m):
75
+ if isinstance(m, nn.Linear):
76
+ trunc_normal_(m.weight, std=0.02)
77
+ if isinstance(m, nn.Linear) and m.bias is not None:
78
+ nn.init.constant_(m.bias, 0)
79
+ elif isinstance(m, (nn.LayerNorm, nn.BatchNorm1d)):
80
+ nn.init.constant_(m.bias, 0)
81
+ nn.init.constant_(m.weight, 1.0)
82
+
83
+ def structured_geo(self, x, fx, T=None):
84
+ if self.args.unified_pos:
85
+ x = self.pos.repeat(x.shape[0], 1, 1)
86
+
87
+ if fx is not None:
88
+ fx = torch.cat((x, fx), -1)
89
+ fx = self.preprocess(fx)
90
+ else:
91
+ fx = self.preprocess(x)
92
+
93
+ fx = fx + self.placeholder[None, None, :]
94
+
95
+ if T is not None:
96
+ Time_emb = timestep_embedding(T, self.args.n_hidden)
97
+ Time_emb = self.time_fc(Time_emb)
98
+ if Time_emb.ndim == 2:
99
+ Time_emb = Time_emb.unsqueeze(1)
100
+ fx = fx + Time_emb
101
+
102
+ for block in self.blocks:
103
+ fx = block(fx)
104
+ return fx
105
+
106
+ def unstructured_geo(self, x, fx, T=None):
107
+ if fx is not None:
108
+ fx = torch.cat((x, fx), -1)
109
+ fx = self.preprocess(fx)
110
+ else:
111
+ fx = self.preprocess(x)
112
+
113
+ fx = fx + self.placeholder[None, None, :]
114
+
115
+ if T is not None:
116
+ Time_emb = timestep_embedding(T, self.args.n_hidden)
117
+ Time_emb = self.time_fc(Time_emb)
118
+ if Time_emb.ndim == 2:
119
+ Time_emb = Time_emb.unsqueeze(1)
120
+ fx = fx + Time_emb
121
+
122
+ for block in self.blocks:
123
+ fx = block(fx)
124
+ return fx
125
+
126
+ def forward(self, x, fx, T=None, geo=None):
127
+ if self.args.geotype == "unstructured":
128
+ return self.unstructured_geo(x, fx, T)
129
+ else:
130
+ return self.structured_geo(x, fx, T)
model/U_FNO.py ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ import numpy as np
5
+ from onescience.modules.fourier.fno_layers import (
6
+ SpectralConv1d,
7
+ SpectralConv2d,
8
+ SpectralConv3d,
9
+ )
10
+ from onescience.modules.mlp.MLP import StandardMLP
11
+ from onescience.modules.embedding import timestep_embedding, unified_pos_embedding
12
+ from onescience.modules.fourier.geo_spectral import GeoSpectralConv2d, IPHI
13
+
14
+ SpectralConvList = [None, SpectralConv1d, SpectralConv2d, SpectralConv3d]
15
+
16
+ from .U_Net import Model as U_Net
17
+
18
+ class Model(nn.Module):
19
+ """
20
+ U-FNO 模型。
21
+
22
+ 结合了 U-Net (用于多尺度特征提取) 和 FNO (用于全局谱特征提取)。
23
+ U-Net 作为 FNO 层的并联分支,增强了局部特征捕捉能力。
24
+
25
+ """
26
+ def __init__(self, args, device, s1=96, s2=96):
27
+ super(Model, self).__init__()
28
+ self.__name__ = "U-FNO"
29
+ self.args = args
30
+ self.device = device
31
+
32
+ # 1. Embedding & Preprocessing
33
+ # -----------------------------------------------------------
34
+ input_dim = args.fun_dim
35
+ if args.unified_pos and args.geotype != "unstructured":
36
+ self.pos = unified_pos_embedding(args.shapelist, args.ref, device=device)
37
+ input_dim += args.ref ** len(args.shapelist)
38
+ else:
39
+ input_dim += args.space_dim
40
+
41
+ self.preprocess = StandardMLP(
42
+ input_dim=input_dim,
43
+ output_dim=args.n_hidden,
44
+ hidden_dims=[args.n_hidden * 2],
45
+ activation=args.act,
46
+ use_bias=True
47
+ )
48
+
49
+ if args.time_input:
50
+ self.time_fc = nn.Sequential(
51
+ nn.Linear(args.n_hidden, args.n_hidden),
52
+ nn.SiLU(),
53
+ nn.Linear(args.n_hidden, args.n_hidden),
54
+ )
55
+
56
+ # 2. Geometry Projection & Padding Logic
57
+ # -----------------------------------------------------------
58
+ if self.args.geotype == "unstructured":
59
+ self.fftproject_in = GeoSpectralConv2d(
60
+ in_channels=args.n_hidden,
61
+ out_channels=args.n_hidden,
62
+ modes1=args.modes,
63
+ modes2=args.modes,
64
+ s1=s1,
65
+ s2=s2
66
+ )
67
+ self.fftproject_out = GeoSpectralConv2d(
68
+ in_channels=args.n_hidden,
69
+ out_channels=args.n_hidden,
70
+ modes1=args.modes,
71
+ modes2=args.modes,
72
+ s1=s1,
73
+ s2=s2
74
+ )
75
+ self.iphi = IPHI()
76
+
77
+ self.padding = [(16 - size % 16) % 16 for size in [s1, s2]]
78
+ else:
79
+ self.padding = [(16 - size % 16) % 16 for size in args.shapelist]
80
+
81
+ # 3. FNO Blocks
82
+ dim = len(self.padding)
83
+
84
+ # 辅助函数:构建 FNO 参数
85
+ def get_fno_layer(in_c, out_c):
86
+ kwargs = {
87
+ "in_channels": in_c,
88
+ "out_channels": out_c
89
+ }
90
+ # 动态添加 modes1, modes2, modes3
91
+ mode_names = ["modes1", "modes2", "modes3"]
92
+ for i in range(dim):
93
+ if i < len(mode_names):
94
+ kwargs[mode_names[i]] = args.modes
95
+ return SpectralConvList[dim](**kwargs)
96
+
97
+ self.conv0 = get_fno_layer(args.n_hidden, args.n_hidden)
98
+ self.conv1 = get_fno_layer(args.n_hidden, args.n_hidden)
99
+ self.conv2 = get_fno_layer(args.n_hidden, args.n_hidden)
100
+ self.conv3 = get_fno_layer(args.n_hidden, args.n_hidden)
101
+
102
+ ConvClass = [None, nn.Conv1d, nn.Conv2d, nn.Conv3d][dim]
103
+ self.w0 = ConvClass(args.n_hidden, args.n_hidden, 1)
104
+ self.w1 = ConvClass(args.n_hidden, args.n_hidden, 1)
105
+ self.w2 = ConvClass(args.n_hidden, args.n_hidden, 1)
106
+ self.w3 = ConvClass(args.n_hidden, args.n_hidden, 1)
107
+
108
+ # 4. U-Net Branches (Parallel)
109
+ # -----------------------------------------------------------
110
+ self.u_net2 = U_Net(args, device)
111
+ self.u_net3 = U_Net(args, device)
112
+
113
+ # 5. Projectors
114
+ self.fc1 = nn.Linear(args.n_hidden, args.n_hidden)
115
+ self.fc2 = nn.Linear(args.n_hidden, args.out_dim)
116
+
117
+ def structured_geo(self, x, fx, T=None):
118
+ B, N, _ = x.shape
119
+
120
+ if self.args.unified_pos:
121
+ x = self.pos.repeat(x.shape[0], 1, 1)
122
+
123
+ if fx is not None:
124
+ fx = torch.cat((x, fx), -1)
125
+ fx = self.preprocess(fx)
126
+ else:
127
+ fx = self.preprocess(x)
128
+
129
+ if T is not None:
130
+ Time_emb = timestep_embedding(T, self.args.n_hidden) # (B, C)
131
+ Time_emb = self.time_fc(Time_emb)
132
+ if Time_emb.ndim == 2:
133
+ Time_emb = Time_emb.unsqueeze(1) # (B, 1, C)
134
+ fx = fx + Time_emb
135
+
136
+ x = fx.permute(0, 2, 1).reshape(B, self.args.n_hidden, *self.args.shapelist)
137
+
138
+ if not all(item == 0 for item in self.padding):
139
+ pad_arg = []
140
+ for p in reversed(self.padding):
141
+ pad_arg.extend([0, p])
142
+ x = F.pad(x, pad_arg)
143
+
144
+ # Layer 0
145
+ x1 = self.conv0(x)
146
+ x2 = self.w0(x)
147
+ x = x1 + x2
148
+ x = F.gelu(x)
149
+
150
+ # Layer 1
151
+ x1 = self.conv1(x)
152
+ x2 = self.w1(x)
153
+ x = x1 + x2
154
+ x = F.gelu(x)
155
+
156
+ # Layer 2 (with U-Net)
157
+ x1 = self.conv2(x)
158
+ x2 = self.w2(x)
159
+ x3 = self.u_net2.multiscale(x)
160
+ x = x1 + x2 + x3
161
+ x = F.gelu(x)
162
+
163
+ # Layer 3 (with U-Net)
164
+ x1 = self.conv3(x)
165
+ x2 = self.w3(x)
166
+ x3 = self.u_net3.multiscale(x)
167
+ x = x1 + x2 + x3
168
+
169
+ if not all(item == 0 for item in self.padding):
170
+ if len(self.args.shapelist) == 1:
171
+ x = x[..., :-self.padding[0]]
172
+ elif len(self.args.shapelist) == 2:
173
+ x = x[..., :-self.padding[0], :-self.padding[1]]
174
+ elif len(self.args.shapelist) == 3:
175
+ x = x[..., :-self.padding[0], :-self.padding[1], :-self.padding[2]]
176
+
177
+ x = x.reshape(B, self.args.n_hidden, -1).permute(0, 2, 1)
178
+ x = self.fc1(x)
179
+ x = F.gelu(x)
180
+ x = self.fc2(x)
181
+ return x
182
+
183
+ def unstructured_geo(self, x, fx, T=None):
184
+ original_pos = x
185
+
186
+ if fx is not None:
187
+ fx = torch.cat((x, fx), -1)
188
+ fx = self.preprocess(fx)
189
+ else:
190
+ fx = self.preprocess(x)
191
+
192
+ if T is not None:
193
+ Time_emb = timestep_embedding(T, self.args.n_hidden)
194
+ Time_emb = self.time_fc(Time_emb)
195
+ if Time_emb.ndim == 2:
196
+ Time_emb = Time_emb.unsqueeze(1)
197
+ fx = fx + Time_emb
198
+
199
+ x = self.fftproject_in(
200
+ fx.permute(0, 2, 1), x_in=original_pos, iphi=self.iphi, code=None
201
+ )
202
+
203
+ # Layer 0
204
+ x1 = self.conv0(x)
205
+ x2 = self.w0(x)
206
+ x = x1 + x2
207
+ x = F.gelu(x)
208
+
209
+ # Layer 1
210
+ x1 = self.conv1(x)
211
+ x2 = self.w1(x)
212
+ x = x1 + x2
213
+ x = F.gelu(x)
214
+
215
+ # Layer 2
216
+ x1 = self.conv2(x)
217
+ x2 = self.w2(x)
218
+ x3 = self.u_net2.multiscale(x)
219
+ x = x1 + x2 + x3
220
+ x = F.gelu(x)
221
+
222
+ # Layer 3
223
+ x1 = self.conv3(x)
224
+ x2 = self.w3(x)
225
+ x3 = self.u_net3.multiscale(x)
226
+ x = x1 + x2 + x3
227
+
228
+ x = self.fftproject_out(
229
+ x, x_out=original_pos, iphi=self.iphi, code=None
230
+ ).permute(0, 2, 1)
231
+
232
+ x = self.fc1(x)
233
+ x = F.gelu(x)
234
+ x = self.fc2(x)
235
+ return x
236
+
237
+ def forward(self, x, fx, T=None, geo=None):
238
+ if self.args.geotype == "unstructured":
239
+ return self.unstructured_geo(x, fx, T)
240
+ else:
241
+ return self.structured_geo(x, fx, T)
model/U_NO.py ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import torch
3
+ import torch.nn as nn
4
+ import torch.nn.functional as F
5
+ import numpy as np
6
+
7
+ from onescience.modules.fourier.fno_layers import (
8
+ SpectralConv1d,
9
+ SpectralConv2d,
10
+ SpectralConv3d,
11
+ )
12
+ from onescience.modules.mlp.MLP import StandardMLP
13
+ from onescience.modules.embedding import timestep_embedding, unified_pos_embedding
14
+ from onescience.modules.fourier.geo_spectral import GeoSpectralConv2d, IPHI
15
+
16
+ # --- 引入 U-Net 基础组件 (替代 UNet_Blocks) ---
17
+ from onescience.modules.layer.unet_layer import (
18
+ DoubleConv1D, Down1D, Up1D, OutConv1D,
19
+ DoubleConv2D, Down2D, Up2D, OutConv2D,
20
+ DoubleConv3D, Down3D, Up3D, OutConv3D,
21
+ )
22
+
23
+ ConvList = [None, DoubleConv1D, DoubleConv2D, DoubleConv3D]
24
+ DownList = [None, Down1D, Down2D, Down3D]
25
+ UpList = [None, Up1D, Up2D, Up3D]
26
+ OutList = [None, OutConv1D, OutConv2D, OutConv3D]
27
+ SpectralConvList = [None, SpectralConv1d, SpectralConv2d, SpectralConv3d]
28
+
29
+ class Model(nn.Module):
30
+ """
31
+ U-NO (U-Net Neural Operator) 模型。
32
+
33
+ 结合了 U-Net 的多尺度结构和 FNO 的谱卷积能力。
34
+ 在 U-Net 的每个 Encoder 和 Decoder 层级之间插入了 FNO Block (SpectralConv) 和 1x1 卷积残差。
35
+ """
36
+ def __init__(self, args, device, bilinear=True, s1=96, s2=96):
37
+ super(Model, self).__init__()
38
+ self.__name__ = "U_NO"
39
+ self.args = args
40
+
41
+ if args.task == "steady":
42
+ normtype = "bn"
43
+ else:
44
+ normtype = "in"
45
+
46
+ # 1. Embedding & Preprocessing
47
+ # -----------------------------------------------------------
48
+ input_dim = args.fun_dim
49
+ if args.unified_pos and args.geotype != "unstructured":
50
+ self.pos = unified_pos_embedding(args.shapelist, args.ref, device=device)
51
+ input_dim += args.ref ** len(args.shapelist)
52
+ else:
53
+ input_dim += args.space_dim
54
+
55
+ self.preprocess = StandardMLP(
56
+ input_dim=input_dim,
57
+ output_dim=args.n_hidden,
58
+ hidden_dims=[args.n_hidden * 2],
59
+ activation=args.act,
60
+ use_bias=True
61
+ )
62
+
63
+ if args.time_input:
64
+ self.time_fc = nn.Sequential(
65
+ nn.Linear(args.n_hidden, args.n_hidden),
66
+ nn.SiLU(),
67
+ nn.Linear(args.n_hidden, args.n_hidden),
68
+ )
69
+
70
+ # 2. Geometry Projection (GeoFNO for unstructured)
71
+ # -----------------------------------------------------------
72
+ if self.args.geotype == "unstructured":
73
+ self.fftproject_in = GeoSpectralConv2d(
74
+ in_channels=args.n_hidden,
75
+ out_channels=args.n_hidden,
76
+ modes1=args.modes,
77
+ modes2=args.modes,
78
+ s1=s1,
79
+ s2=s2
80
+ )
81
+ self.fftproject_out = GeoSpectralConv2d(
82
+ in_channels=args.n_hidden,
83
+ out_channels=args.n_hidden,
84
+ modes1=args.modes,
85
+ modes2=args.modes,
86
+ s1=s1,
87
+ s2=s2
88
+ )
89
+ self.iphi = IPHI()
90
+
91
+ patch_size = [(size + (16 - size % 16) % 16) // 16 for size in [s1, s2]]
92
+ self.padding = [(16 - size % 16) % 16 for size in [s1, s2]]
93
+ self.augmented_resolution = [s1, s2]
94
+ else:
95
+ patch_size = [
96
+ (size + (16 - size % 16) % 16) // 16 for size in args.shapelist
97
+ ]
98
+ self.padding = [(16 - size % 16) % 16 for size in args.shapelist]
99
+ self.augmented_resolution = [
100
+ shape + padding for shape, padding in zip(args.shapelist, self.padding)
101
+ ]
102
+
103
+ dim = len(patch_size)
104
+
105
+ # 3. Multiscale U-Net Modules
106
+ self.inc = ConvList[dim](args.n_hidden, args.n_hidden, normtype=normtype)
107
+
108
+ self.down1 = DownList[dim](args.n_hidden, args.n_hidden * 2, normtype=normtype)
109
+ self.down2 = DownList[dim](args.n_hidden * 2, args.n_hidden * 4, normtype=normtype)
110
+ self.down3 = DownList[dim](args.n_hidden * 4, args.n_hidden * 8, normtype=normtype)
111
+
112
+ factor = 2 if bilinear else 1
113
+ self.down4 = DownList[dim](args.n_hidden * 8, args.n_hidden * 16 // factor, normtype=normtype)
114
+
115
+ self.up1 = UpList[dim](args.n_hidden * 16, args.n_hidden * 8 // factor, bilinear, normtype=normtype)
116
+ self.up2 = UpList[dim](args.n_hidden * 8, args.n_hidden * 4 // factor, bilinear, normtype=normtype)
117
+ self.up3 = UpList[dim](args.n_hidden * 4, args.n_hidden * 2 // factor, bilinear, normtype=normtype)
118
+ self.up4 = UpList[dim](args.n_hidden * 2, args.n_hidden, bilinear, normtype=normtype)
119
+
120
+ self.outc = OutList[dim](args.n_hidden, args.n_hidden)
121
+
122
+ # 4. FNO Blocks
123
+ def get_fno_layer(in_c, out_c, res_list, divisor):
124
+ modes_list = [
125
+ max(1, min(args.modes, res // divisor))
126
+ for res in res_list
127
+ ]
128
+
129
+ kwargs = {
130
+ "in_channels": in_c,
131
+ "out_channels": out_c
132
+ }
133
+ mode_names = ["modes1", "modes2", "modes3"]
134
+ for i, m in enumerate(modes_list):
135
+ if i < len(mode_names):
136
+ kwargs[mode_names[i]] = m
137
+ return SpectralConvList[dim](**kwargs)
138
+
139
+ # Down Path FNOs
140
+ self.process1_down = get_fno_layer(args.n_hidden, args.n_hidden, self.augmented_resolution, 2)
141
+ self.process2_down = get_fno_layer(args.n_hidden * 2, args.n_hidden * 2, self.augmented_resolution, 4)
142
+ self.process3_down = get_fno_layer(args.n_hidden * 4, args.n_hidden * 4, self.augmented_resolution, 8)
143
+ self.process4_down = get_fno_layer(args.n_hidden * 8, args.n_hidden * 8, self.augmented_resolution, 16)
144
+ self.process5_down = get_fno_layer(args.n_hidden * 16 // factor, args.n_hidden * 16 // factor, self.augmented_resolution, 32)
145
+
146
+ # Residual Weights (1x1 Conv)
147
+ self.w1_down = ConvList[dim](args.n_hidden, args.n_hidden, 1) # kernel_size=1
148
+ self.w2_down = ConvList[dim](args.n_hidden * 2, args.n_hidden * 2, 1)
149
+ self.w3_down = ConvList[dim](args.n_hidden * 4, args.n_hidden * 4, 1)
150
+ self.w4_down = ConvList[dim](args.n_hidden * 8, args.n_hidden * 8, 1)
151
+ self.w5_down = ConvList[dim](args.n_hidden * 16 // factor, args.n_hidden * 16 // factor, 1)
152
+
153
+ # Up Path FNOs
154
+ self.process1_up = get_fno_layer(args.n_hidden, args.n_hidden, self.augmented_resolution, 2)
155
+ self.process2_up = get_fno_layer(args.n_hidden * 2 // factor, args.n_hidden * 2 // factor, self.augmented_resolution, 4)
156
+ self.process3_up = get_fno_layer(args.n_hidden * 4 // factor, args.n_hidden * 4 // factor, self.augmented_resolution, 8)
157
+ self.process4_up = get_fno_layer(args.n_hidden * 8 // factor, args.n_hidden * 8 // factor, self.augmented_resolution, 16)
158
+ self.process5_up = get_fno_layer(args.n_hidden * 16 // factor, args.n_hidden * 16 // factor, self.augmented_resolution, 32)
159
+
160
+ self.w1_up = ConvList[dim](args.n_hidden, args.n_hidden, 1)
161
+ self.w2_up = ConvList[dim](args.n_hidden * 2 // factor, args.n_hidden * 2 // factor, 1)
162
+ self.w3_up = ConvList[dim](args.n_hidden * 4 // factor, args.n_hidden * 4 // factor, 1)
163
+ self.w4_up = ConvList[dim](args.n_hidden * 8 // factor, args.n_hidden * 8 // factor, 1)
164
+ self.w5_up = ConvList[dim](args.n_hidden * 16 // factor, args.n_hidden * 16 // factor, 1)
165
+
166
+ # 5. Projectors
167
+ self.fc1 = nn.Linear(args.n_hidden, args.n_hidden * 2)
168
+ self.fc2 = nn.Linear(args.n_hidden * 2, args.out_dim)
169
+
170
+ def structured_geo(self, x, fx, T=None):
171
+ B, N, _ = x.shape
172
+ if self.args.unified_pos:
173
+ x = self.pos.repeat(x.shape[0], 1, 1)
174
+
175
+ if fx is not None:
176
+ fx = torch.cat((x, fx), -1)
177
+ fx = self.preprocess(fx)
178
+ else:
179
+ fx = self.preprocess(x)
180
+
181
+ if T is not None:
182
+ Time_emb = timestep_embedding(T, self.args.n_hidden)
183
+ Time_emb = self.time_fc(Time_emb)
184
+ if Time_emb.ndim == 2:
185
+ Time_emb = Time_emb.unsqueeze(1)
186
+ fx = fx + Time_emb # Broadcast
187
+
188
+ # Reshape to Grid (B, C, H, W...)
189
+ x = fx.permute(0, 2, 1).reshape(B, self.args.n_hidden, *self.args.shapelist)
190
+
191
+ # Padding
192
+ if not all(item == 0 for item in self.padding):
193
+ pad_arg = []
194
+ for p in reversed(self.padding):
195
+ pad_arg.extend([0, p])
196
+ x = F.pad(x, pad_arg)
197
+
198
+ # === U-NO Body ===
199
+ # Level 1 Down
200
+ x1 = self.inc(x)
201
+ # FNO Processing + Residual
202
+ x1 = F.gelu(self.process1_down(x1) + self.w1_down(x1))
203
+
204
+ # Level 2 Down
205
+ x2 = self.down1(x1)
206
+ x2 = F.gelu(self.process2_down(x2) + self.w2_down(x2))
207
+
208
+ # Level 3 Down
209
+ x3 = self.down2(x2)
210
+ x3 = F.gelu(self.process3_down(x3) + self.w3_down(x3))
211
+
212
+ # Level 4 Down
213
+ x4 = self.down3(x3)
214
+ x4 = F.gelu(self.process4_down(x4) + self.w4_down(x4))
215
+
216
+ # Level 5 (Bottleneck)
217
+ x5 = self.down4(x4)
218
+ x5 = F.gelu(self.process5_down(x5) + self.w5_down(x5))
219
+ # Bottleneck Up Process
220
+ x5 = F.gelu(self.process5_up(x5) + self.w5_up(x5))
221
+
222
+ # Level 4 Up
223
+ x = self.up1(x5, x4)
224
+ x = F.gelu(self.process4_up(x) + self.w4_up(x))
225
+
226
+ # Level 3 Up
227
+ x = self.up2(x, x3)
228
+ x = F.gelu(self.process3_up(x) + self.w3_up(x))
229
+
230
+ # Level 2 Up
231
+ x = self.up3(x, x2)
232
+ x = F.gelu(self.process2_up(x) + self.w2_up(x))
233
+
234
+ # Level 1 Up
235
+ x = self.up4(x, x1)
236
+ x = F.gelu(self.process1_up(x) + self.w1_up(x))
237
+
238
+ x = self.outc(x)
239
+
240
+ # Un-padding
241
+ if not all(item == 0 for item in self.padding):
242
+ if len(self.args.shapelist) == 1:
243
+ x = x[..., : -self.padding[0]]
244
+ elif len(self.args.shapelist) == 2:
245
+ x = x[..., : -self.padding[0], : -self.padding[1]]
246
+ elif len(self.args.shapelist) == 3:
247
+ x = x[..., : -self.padding[0], : -self.padding[1], : -self.padding[2]]
248
+
249
+ # Output Projection
250
+ x = x.reshape(B, self.args.n_hidden, -1).permute(0, 2, 1)
251
+ x = self.fc1(x)
252
+ x = F.gelu(x)
253
+ x = self.fc2(x)
254
+ return x
255
+
256
+ def unstructured_geo(self, x, fx, T=None):
257
+ original_pos = x
258
+ if fx is not None:
259
+ fx = torch.cat((x, fx), -1)
260
+ fx = self.preprocess(fx)
261
+ else:
262
+ fx = self.preprocess(x)
263
+
264
+ if T is not None:
265
+ Time_emb = timestep_embedding(T, self.args.n_hidden)
266
+ Time_emb = self.time_fc(Time_emb)
267
+ if Time_emb.ndim == 2:
268
+ Time_emb = Time_emb.unsqueeze(1)
269
+ fx = fx + Time_emb
270
+
271
+ # Projection to Grid
272
+ x = self.fftproject_in(
273
+ fx.permute(0, 2, 1), x_in=original_pos, iphi=self.iphi, code=None
274
+ )
275
+
276
+ # === U-NO Body===
277
+ x1 = self.inc(x)
278
+ x1 = F.gelu(self.process1_down(x1) + self.w1_down(x1))
279
+
280
+ x2 = self.down1(x1)
281
+ x2 = F.gelu(self.process2_down(x2) + self.w2_down(x2))
282
+
283
+ x3 = self.down2(x2)
284
+ x3 = F.gelu(self.process3_down(x3) + self.w3_down(x3))
285
+
286
+ x4 = self.down3(x3)
287
+ x4 = F.gelu(self.process4_down(x4) + self.w4_down(x4))
288
+
289
+ x5 = self.down4(x4)
290
+ x5 = F.gelu(self.process5_down(x5) + self.w5_down(x5))
291
+ x5 = F.gelu(self.process5_up(x5) + self.w5_up(x5))
292
+
293
+ x = self.up1(x5, x4)
294
+ x = F.gelu(self.process4_up(x) + self.w4_up(x))
295
+
296
+ x = self.up2(x, x3)
297
+ x = F.gelu(self.process3_up(x) + self.w3_up(x))
298
+
299
+ x = self.up3(x, x2)
300
+ x = F.gelu(self.process2_up(x) + self.w2_up(x))
301
+
302
+ x = self.up4(x, x1)
303
+ x = F.gelu(self.process1_up(x) + self.w1_up(x))
304
+
305
+ x = self.outc(x)
306
+
307
+ # Projection Back to Points
308
+ x = self.fftproject_out(
309
+ x, x_out=original_pos, iphi=self.iphi, code=None
310
+ ).permute(0, 2, 1)
311
+
312
+ x = self.fc1(x)
313
+ x = F.gelu(x)
314
+ x = self.fc2(x)
315
+ return x
316
+
317
+ def forward(self, x, fx, T=None, geo=None):
318
+ if self.args.geotype == "unstructured":
319
+ return self.unstructured_geo(x, fx, T)
320
+ else:
321
+ return self.structured_geo(x, fx, T)
model/U_Net.py ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import math
3
+ import torch.nn as nn
4
+ import numpy as np
5
+ import torch.nn.functional as F
6
+ from onescience.modules.decoder.unet_decoder import (
7
+ UNetDecoder1D,
8
+ UNetDecoder2D,
9
+ UNetDecoder3D,
10
+ )
11
+ from onescience.modules.encoder.unet_encoder import (
12
+ UNetEncoder1D,
13
+ UNetEncoder2D,
14
+ UNetEncoder3D,
15
+ )
16
+ from onescience.modules.fourier.geo_spectral import GeoSpectralConv2d, IPHI
17
+ from onescience.modules.head.unet_head import UNetHead1D, UNetHead2D, UNetHead3D
18
+ from onescience.modules.mlp.MLP import StandardMLP
19
+ from onescience.modules.embedding import timestep_embedding, unified_pos_embedding
20
+
21
+ EncoderList = [None, UNetEncoder1D, UNetEncoder2D, UNetEncoder3D]
22
+ DecoderList = [None, UNetDecoder1D, UNetDecoder2D, UNetDecoder3D]
23
+ HeadList = [None, UNetHead1D, UNetHead2D, UNetHead3D]
24
+
25
+ class Model(nn.Module):
26
+ """
27
+ 多尺度物理场 U-Net 模型。
28
+
29
+ 该模型支持结构化网格(1D/2D/3D)和非结构化网格(通过 GeoFNO 的几何投影)的物理场预测。
30
+ 利用编码器和解码器实现多尺度特征提取与融合。
31
+
32
+ Args:
33
+ args: 包含模型配置的参数命名空间 (如 task, geotype, n_hidden 等)。
34
+ device: 运行设备。
35
+ bilinear (bool, optional): U-Net 上采样是否使用双线性插值。默认值: True。
36
+ s1 (int, optional): 非结构化网格投影的潜在空间高度。默认值: 96。
37
+ s2 (int, optional): 非结构化网格投影的潜在空间宽度。默认值: 96。
38
+
39
+ 形状:
40
+ 输入 x: 坐标张量 (B, N, space_dim)。
41
+ 输入 fx: 物理场特征张量 (B, N, fun_dim)。
42
+ 输入 T: 可选的时间步张量。
43
+ 输出: (B, N, out_dim)
44
+ """
45
+ def __init__(self, args, device, bilinear=True, s1=96, s2=96):
46
+ super(Model, self).__init__()
47
+ self.__name__ = "U-Net"
48
+ self.args = args
49
+ self.bilinear = bilinear
50
+
51
+ normtype = "bn" if args.task == "steady" else "in"
52
+
53
+ # ==========================================
54
+ # 1. 位置编码与预处理 (Embedding)
55
+ # ==========================================
56
+ if args.unified_pos and args.geotype != "unstructured": # structured mesh
57
+ self.pos = unified_pos_embedding(args.shapelist, args.ref, device=device)
58
+ input_dim = args.fun_dim + args.ref ** len(args.shapelist)
59
+ else:
60
+ input_dim = args.fun_dim + args.space_dim
61
+
62
+ self.preprocess = StandardMLP(
63
+ input_dim=input_dim,
64
+ hidden_dims=[args.n_hidden * 2],
65
+ output_dim=args.n_hidden,
66
+ activation=args.act,
67
+ )
68
+
69
+ if args.time_input:
70
+ self.time_fc = nn.Sequential(
71
+ nn.Linear(args.n_hidden, args.n_hidden),
72
+ nn.SiLU(),
73
+ nn.Linear(args.n_hidden, args.n_hidden),
74
+ )
75
+
76
+ # ==========================================
77
+ # 2. 几何投影 (Geometry Projection)
78
+ # ==========================================
79
+ if self.args.geotype == "unstructured":
80
+ self.fftproject_in = GeoSpectralConv2d(
81
+ in_channels=args.n_hidden,
82
+ out_channels=args.n_hidden,
83
+ modes1=args.modes,
84
+ modes2=args.modes,
85
+ s1=s1,
86
+ s2=s2
87
+ )
88
+ self.fftproject_out = GeoSpectralConv2d(
89
+ in_channels=args.n_hidden,
90
+ out_channels=args.n_hidden,
91
+ modes1=args.modes,
92
+ modes2=args.modes,
93
+ s1=s1,
94
+ s2=s2
95
+ )
96
+ self.iphi = IPHI()
97
+
98
+ patch_size = [(size + (16 - size % 16) % 16) // 16 for size in [s1, s2]]
99
+ self.padding = [(16 - size % 16) % 16 for size in [s1, s2]]
100
+ else:
101
+ patch_size = [(size + (16 - size % 16) % 16) // 16 for size in args.shapelist]
102
+ self.padding = [(16 - size % 16) % 16 for size in args.shapelist]
103
+
104
+ # ==========================================
105
+ # 3. U-Net 核心 (Multiscale modules)
106
+ # ==========================================
107
+ dim = len(patch_size)
108
+ num_stages = 4
109
+
110
+ self.encoder = EncoderList[dim](
111
+ in_channels=args.n_hidden,
112
+ base_channels=args.n_hidden,
113
+ num_stages=num_stages,
114
+ bilinear=bilinear,
115
+ normtype=normtype
116
+ )
117
+
118
+ self.decoder = DecoderList[dim](
119
+ base_channels=args.n_hidden,
120
+ num_stages=num_stages,
121
+ bilinear=bilinear,
122
+ normtype=normtype
123
+ )
124
+
125
+ self.outc = HeadList[dim](
126
+ in_channels=args.n_hidden,
127
+ out_channels=args.n_hidden
128
+ )
129
+
130
+ # 最终投影
131
+ self.fc1 = nn.Linear(args.n_hidden, args.n_hidden)
132
+ self.fc2 = nn.Linear(args.n_hidden, args.out_dim)
133
+
134
+ def multiscale(self, x):
135
+ """完全解耦的 U-Net ���向计算"""
136
+ # 提取多尺度特征
137
+ features = self.encoder(x)
138
+ # 融合上采样
139
+ x = self.decoder(features)
140
+ # 预测头输出
141
+ return self.outc(x)
142
+
143
+ def structured_geo(self, x, fx, T=None):
144
+ B, N, _ = x.shape
145
+ if self.args.unified_pos:
146
+ x = self.pos.repeat(x.shape[0], 1, 1)
147
+
148
+ if fx is not None:
149
+ fx = torch.cat((x, fx), -1)
150
+ fx = self.preprocess(fx)
151
+ else:
152
+ fx = self.preprocess(x)
153
+
154
+ if T is not None:
155
+ Time_emb = timestep_embedding(T, self.args.n_hidden).repeat(1, x.shape[1], 1)
156
+ Time_emb = self.time_fc(Time_emb)
157
+ fx = fx + Time_emb
158
+
159
+ x = fx.permute(0, 2, 1).reshape(B, self.args.n_hidden, *self.args.shapelist)
160
+
161
+ # Padding
162
+ if not all(item == 0 for item in self.padding):
163
+ if len(self.args.shapelist) == 2:
164
+ x = F.pad(x, [0, self.padding[1], 0, self.padding[0]])
165
+ elif len(self.args.shapelist) == 3:
166
+ x = F.pad(x, [0, self.padding[2], 0, self.padding[1], 0, self.padding[0]])
167
+
168
+ # 极简调用多尺度 U-Net
169
+ x = self.multiscale(x)
170
+
171
+ # Unpadding
172
+ if not all(item == 0 for item in self.padding):
173
+ if len(self.args.shapelist) == 2:
174
+ x = x[..., : -self.padding[0], : -self.padding[1]]
175
+ elif len(self.args.shapelist) == 3:
176
+ x = x[..., : -self.padding[0], : -self.padding[1], : -self.padding[2]]
177
+
178
+ x = x.reshape(B, self.args.n_hidden, -1).permute(0, 2, 1)
179
+ x = self.fc1(x)
180
+ x = F.gelu(x)
181
+ x = self.fc2(x)
182
+ return x
183
+
184
+ def unstructured_geo(self, x, fx, T=None):
185
+ original_pos = x
186
+ if fx is not None:
187
+ fx = torch.cat((x, fx), -1)
188
+ fx = self.preprocess(fx)
189
+ else:
190
+ fx = self.preprocess(x)
191
+
192
+ if T is not None:
193
+ Time_emb = timestep_embedding(T, self.args.n_hidden).repeat(1, x.shape[1], 1)
194
+ Time_emb = self.time_fc(Time_emb)
195
+ fx = fx + Time_emb
196
+
197
+ # GeoFNO: 坐标映射与入场变换
198
+ x = self.fftproject_in(
199
+ fx.permute(0, 2, 1), x_in=original_pos, iphi=self.iphi, code=None
200
+ )
201
+
202
+ # 极简调用多尺度 U-Net
203
+ x = self.multiscale(x)
204
+
205
+ # GeoFNO: 出场逆变换
206
+ x = self.fftproject_out(
207
+ x, x_out=original_pos, iphi=self.iphi, code=None
208
+ ).permute(0, 2, 1)
209
+
210
+ x = self.fc1(x)
211
+ x = F.gelu(x)
212
+ x = self.fc2(x)
213
+ return x
214
+
215
+ def forward(self, x, fx, T=None, geo=None):
216
+ if self.args.geotype == "unstructured":
217
+ return self.unstructured_geo(x, fx, T)
218
+ else:
219
+ return self.structured_geo(x, fx, T)
model/model_factory.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from . import Transolver
2
+ from . import LSM
3
+ from . import FNO
4
+ from . import U_Net
5
+ from . import Transformer
6
+ from . import Factformer
7
+ from . import Swin_Transformer
8
+ from . import Galerkin_Transformer
9
+ from . import GNOT
10
+ from . import U_NO
11
+ from . import U_FNO
12
+ from . import F_FNO
13
+ from . import ONO
14
+ from . import MWT
15
+ from . import GraphSAGE
16
+ from . import Graph_UNet
17
+ from . import PointNet
18
+ from . import DeepONet
19
+ from . import MeshGraphNet
20
+ from . import RegDGCNN
21
+ from . import GFNO
22
+
23
+
24
+ def get_model(args, device):
25
+ model_dict = {
26
+ "PointNet": PointNet,
27
+ "Graph_UNet": Graph_UNet,
28
+ "GraphSAGE": GraphSAGE,
29
+ "MWT": MWT,
30
+ "ONO": ONO,
31
+ "F_FNO": F_FNO,
32
+ "U_FNO": U_FNO,
33
+ "U_NO": U_NO,
34
+ "GNOT": GNOT,
35
+ "Galerkin_Transformer": Galerkin_Transformer,
36
+ "Swin_Transformer": Swin_Transformer,
37
+ "Factformer": Factformer,
38
+ "Transformer": Transformer,
39
+ "U_Net": U_Net,
40
+ "FNO": FNO,
41
+ "Transolver": Transolver,
42
+ "LSM": LSM,
43
+ "DeepONet": DeepONet,
44
+ "MeshGraphNet": MeshGraphNet,
45
+ "RegDGCNN": RegDGCNN,
46
+ "GFNO": GFNO,
47
+ }
48
+ return model_dict[args.model].Model(args, device)
scripts/fake_data.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ import numpy as np
6
+ import yaml
7
+
8
+
9
+ PROJECT_ROOT = Path(__file__).resolve().parents[1]
10
+ sys.path.insert(0, str(PROJECT_ROOT / "model"))
11
+
12
+
13
+ def load_config():
14
+ with open(PROJECT_ROOT / "config" / "config.yaml", "r", encoding="utf-8") as f:
15
+ return yaml.safe_load(f)
16
+
17
+
18
+ def main():
19
+ os.chdir(PROJECT_ROOT)
20
+ cfg = load_config()
21
+ data_dir = PROJECT_ROOT / cfg["paths"]["data_dir"]
22
+ data_dir.mkdir(parents=True, exist_ok=True)
23
+
24
+ n_total = cfg["data"]["ntrain"] + cfg["data"]["ntest"] + 1
25
+ height, width = 221, 51
26
+ rng = np.random.default_rng(42)
27
+
28
+ y_axis = np.linspace(-1.0, 1.0, height, dtype=np.float32)
29
+ x_axis = np.linspace(0.0, 1.0, width, dtype=np.float32)
30
+ grid_y, grid_x = np.meshgrid(y_axis, x_axis, indexing="ij")
31
+
32
+ input_x = np.broadcast_to(grid_x, (n_total, height, width)).copy()
33
+ input_y = np.broadcast_to(grid_y, (n_total, height, width)).copy()
34
+ q = np.zeros((n_total, 5, height, width), dtype=np.float32)
35
+
36
+ for i in range(n_total):
37
+ phase = 0.15 * i
38
+ field = np.sin(np.pi * (grid_x + phase)) * np.cos(np.pi * grid_y)
39
+ q[i, 4] = field + 0.01 * rng.standard_normal((height, width))
40
+
41
+ np.save(data_dir / "NACA_Cylinder_X.npy", input_x.astype(np.float32))
42
+ np.save(data_dir / "NACA_Cylinder_Y.npy", input_y.astype(np.float32))
43
+ np.save(data_dir / "NACA_Cylinder_Q.npy", q.astype(np.float32))
44
+
45
+ print(f"Fake airfoil data written to {data_dir}")
46
+ print(f"Samples={n_total}, X/Y shape={input_x.shape}, Q shape={q.shape}")
47
+
48
+
49
+ if __name__ == "__main__":
50
+ main()
scripts/inference.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from pathlib import Path
4
+
5
+ import train as train_runtime
6
+
7
+
8
+ PROJECT_ROOT = Path(__file__).resolve().parents[1]
9
+
10
+
11
+ def load_model(args, device):
12
+ model = train_runtime.get_model(args, device).to(device)
13
+ checkpoint_path = PROJECT_ROOT / "checkpoints" / f"{args.save_name}.pt"
14
+ state = train_runtime.torch.load(
15
+ checkpoint_path, map_location=device, weights_only=False
16
+ )
17
+ model_state = state["model_state"] if isinstance(state, dict) and "model_state" in state else state
18
+ model.load_state_dict(model_state)
19
+ model.eval()
20
+ return model
21
+
22
+
23
+ def evaluate(model, test_loader, dataset, args, device):
24
+ loss_func = train_runtime.L2Loss(size_average=False)
25
+ totals = {
26
+ "rel_err": 0.0,
27
+ "abs_err": 0.0,
28
+ "mse": 0.0,
29
+ "mae": 0.0,
30
+ "maxae": 0.0,
31
+ "r2": 0.0,
32
+ }
33
+ count = 0
34
+ with train_runtime.torch.no_grad():
35
+ for pos, fx, y in test_loader:
36
+ x, fx, y = pos.to(device), fx.to(device), y.to(device)
37
+ if args.fun_dim == 0:
38
+ fx = None
39
+ out = model(x, fx)
40
+ if args.normalize:
41
+ out = dataset.y_normalizer.decode(out)
42
+
43
+ batch_size = y.shape[0]
44
+ totals["rel_err"] += loss_func.rel(out, y).item()
45
+ totals["abs_err"] += loss_func.abs(out, y).item()
46
+ totals["mse"] += loss_func.MSE(out, y).item()
47
+ totals["mae"] += loss_func.MAE(out, y).item()
48
+ totals["maxae"] += loss_func.MaxAE(out, y).item()
49
+ totals["r2"] += loss_func.R2Score(out, y).item()
50
+ count += batch_size
51
+
52
+ return {name: value / args.ntest for name, value in totals.items()}
53
+
54
+
55
+ def main():
56
+ os.chdir(PROJECT_ROOT)
57
+ cfg = train_runtime.load_config()
58
+ if cfg.get("runtime", {}).get("device", "cpu") == "cpu":
59
+ os.environ["CUDA_VISIBLE_DEVICES"] = ""
60
+
61
+ weight_path = PROJECT_ROOT / cfg["paths"]["weight_path"]
62
+ if not weight_path.exists():
63
+ raise FileNotFoundError(
64
+ f"Checkpoint not found: {weight_path}. Run python scripts/train.py first "
65
+ "or update paths.weight_path/save_name in config/config.yaml."
66
+ )
67
+
68
+ args = train_runtime.build_args(cfg)
69
+ args.eval = cfg["inference"]["eval"]
70
+ train_runtime.load_onescience()
71
+ train_runtime.DistributedManager.initialize()
72
+ dist = train_runtime.DistributedManager()
73
+ dataset, _, test_loader, args.shapelist = train_runtime.get_data(args, dist)
74
+ device = train_runtime.get_device(args, dist)
75
+ if hasattr(dataset, "x_normalizer"):
76
+ dataset.x_normalizer = dataset.x_normalizer.to(device)
77
+ if hasattr(dataset, "y_normalizer"):
78
+ dataset.y_normalizer = dataset.y_normalizer.to(device)
79
+
80
+ model = load_model(args, device)
81
+ metrics = evaluate(model, test_loader, dataset, args, device)
82
+
83
+ result_dir = PROJECT_ROOT / cfg["paths"]["result_dir"] / args.save_name
84
+ result_dir.mkdir(parents=True, exist_ok=True)
85
+ with open(result_dir / "metrics.json", "w", encoding="utf-8") as f:
86
+ json.dump(metrics, f, indent=2, ensure_ascii=False)
87
+
88
+ print("\n===== 测试结果 =====")
89
+ print(f"平均相对误差: {metrics['rel_err']:.6e}")
90
+ print(f"平均绝对误差: {metrics['abs_err']:.6e}")
91
+ print(f"平均MSE: {metrics['mse']:.6e}")
92
+ print(f"平均MAE: {metrics['mae']:.6e}")
93
+ print(f"平均MaxAE: {metrics['maxae']:.6e}")
94
+ print(f"平均R2分数: {metrics['r2']:.6f}")
95
+ print("====================")
96
+
97
+
98
+ if __name__ == "__main__":
99
+ main()
scripts/result.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from pathlib import Path
3
+
4
+ import torch
5
+ import yaml
6
+
7
+
8
+ PROJECT_ROOT = Path(__file__).resolve().parents[1]
9
+
10
+
11
+ def load_config():
12
+ with open(PROJECT_ROOT / "config" / "config.yaml", "r", encoding="utf-8") as f:
13
+ return yaml.safe_load(f)
14
+
15
+
16
+ def main():
17
+ cfg = load_config()
18
+ checkpoint = PROJECT_ROOT / cfg["paths"]["weight_path"]
19
+ result_dir = PROJECT_ROOT / cfg["paths"]["result_dir"] / cfg["train"]["save_name"]
20
+ metrics_path = result_dir / "metrics.json"
21
+
22
+ summary = {
23
+ "checkpoint": str(checkpoint),
24
+ "checkpoint_exists": checkpoint.exists(),
25
+ "result_dir": str(result_dir),
26
+ "result_dir_exists": result_dir.exists(),
27
+ "metrics": str(metrics_path),
28
+ "metrics_exists": metrics_path.exists(),
29
+ }
30
+ if checkpoint.exists():
31
+ state = torch.load(checkpoint, map_location="cpu", weights_only=False)
32
+ if isinstance(state, dict):
33
+ summary["epoch"] = state.get("epoch")
34
+ summary["best_epoch"] = state.get("best_epoch")
35
+ summary["best_test_loss"] = state.get("best_test_loss")
36
+ summary["has_model_state"] = "model_state" in state
37
+ if metrics_path.exists():
38
+ with open(metrics_path, "r", encoding="utf-8") as f:
39
+ summary["metrics_values"] = json.load(f)
40
+ print(json.dumps(summary, indent=2, ensure_ascii=False))
41
+
42
+
43
+ if __name__ == "__main__":
44
+ main()
scripts/train.py ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from pathlib import Path
3
+ from types import SimpleNamespace
4
+
5
+ import yaml
6
+
7
+
8
+ PROJECT_ROOT = Path(__file__).resolve().parents[1]
9
+ torch = None
10
+ get_data = None
11
+ DistributedManager = None
12
+ replace_function = None
13
+ get_model = None
14
+ DerivLoss = None
15
+ L2Loss = None
16
+
17
+
18
+ def load_config():
19
+ with open(PROJECT_ROOT / "config" / "config.yaml", "r", encoding="utf-8") as f:
20
+ return yaml.safe_load(f)
21
+
22
+
23
+ def load_onescience():
24
+ global torch
25
+ global get_data
26
+ global DistributedManager
27
+ global replace_function
28
+ global get_model
29
+ global DerivLoss
30
+ global L2Loss
31
+
32
+ if torch is not None:
33
+ return
34
+
35
+ import torch as torch_module
36
+ from onescience.datapipes.cfd_benchmark.data_factory import get_data as get_data_fn
37
+ from onescience.distributed.manager import DistributedManager as DistributedManager_cls
38
+ from onescience.memory.checkpoint import replace_function as replace_function_fn
39
+ from onescience.models.cfd_benchmark.model_factory import get_model as get_model_fn
40
+ from onescience.utils.cfd_benchmark.loss import DerivLoss as DerivLoss_cls
41
+ from onescience.utils.cfd_benchmark.loss import L2Loss as L2Loss_cls
42
+
43
+ torch = torch_module
44
+ get_data = get_data_fn
45
+ DistributedManager = DistributedManager_cls
46
+ replace_function = replace_function_fn
47
+ get_model = get_model_fn
48
+ DerivLoss = DerivLoss_cls
49
+ L2Loss = L2Loss_cls
50
+
51
+
52
+ def build_args(cfg):
53
+ data = cfg["data"]
54
+ model = cfg["model"]
55
+ train = cfg["train"]
56
+ return SimpleNamespace(
57
+ lr=train["lr"],
58
+ epochs=train["epochs"],
59
+ weight_decay=train["weight_decay"],
60
+ pct_start=train["pct_start"],
61
+ batch_size=data["batch_size"],
62
+ gpu=0,
63
+ max_grad_norm=train["max_grad_norm"],
64
+ derivloss=train["derivloss"],
65
+ optimizer=train["optimizer"],
66
+ scheduler=train["scheduler"],
67
+ step_size=100,
68
+ gamma=0.5,
69
+ find_unused_parameters=False,
70
+ use_checkpoint=train["use_checkpoint"],
71
+ checkpoint_layers=train["checkpoint_layers"],
72
+ resume=False,
73
+ data_path=cfg["paths"]["data_dir"],
74
+ loader=data["loader"],
75
+ config_name="config_name",
76
+ train_ratio=0.8,
77
+ ntrain=data["ntrain"],
78
+ ntest=data["ntest"],
79
+ normalize=data["normalize"],
80
+ norm_type=data["norm_type"],
81
+ geotype=data["geotype"],
82
+ time_input=False,
83
+ space_dim=data["space_dim"],
84
+ fun_dim=data["fun_dim"],
85
+ out_dim=data["out_dim"],
86
+ shapelist=None,
87
+ downsamplex=data["downsamplex"],
88
+ downsampley=data["downsampley"],
89
+ downsamplez=1,
90
+ radius=0.2,
91
+ task=data["task"],
92
+ T_in=10,
93
+ T_out=10,
94
+ model=model["name"],
95
+ n_hidden=model["n_hidden"],
96
+ n_layers=model["n_layers"],
97
+ n_heads=model["n_heads"],
98
+ act=model["act"],
99
+ mlp_ratio=model["mlp_ratio"],
100
+ dropout=model["dropout"],
101
+ unified_pos=model["unified_pos"],
102
+ ref=model["ref"],
103
+ slice_num=model["slice_num"],
104
+ modes=12,
105
+ psi_dim=8,
106
+ attn_type="nystrom",
107
+ mwt_k=3,
108
+ branch_depth=5,
109
+ trunk_depth=6,
110
+ hidden_channels=[],
111
+ kernel_size=5,
112
+ emb_dims=128,
113
+ eval=0,
114
+ save_name=train["save_name"],
115
+ vis_num=cfg["inference"]["vis_num"],
116
+ vis_bound=None,
117
+ )
118
+
119
+
120
+ def get_device(args, dist):
121
+ if dist.world_size == 1 and torch.cuda.is_available():
122
+ return torch.device(f"cuda:{args.gpu}")
123
+ if dist.world_size > 1 and torch.cuda.is_available():
124
+ return dist.device
125
+ return torch.device("cpu")
126
+
127
+
128
+ def make_optimizer(args, model):
129
+ if args.optimizer == "AdamW":
130
+ return torch.optim.AdamW(
131
+ model.parameters(), lr=args.lr, weight_decay=args.weight_decay
132
+ )
133
+ if args.optimizer == "Adam":
134
+ return torch.optim.Adam(
135
+ model.parameters(), lr=args.lr, weight_decay=args.weight_decay
136
+ )
137
+ raise ValueError("Optimizer only AdamW or Adam")
138
+
139
+
140
+ def make_scheduler(args, optimizer, train_loader):
141
+ if args.scheduler == "OneCycleLR":
142
+ return torch.optim.lr_scheduler.OneCycleLR(
143
+ optimizer,
144
+ max_lr=args.lr,
145
+ epochs=args.epochs,
146
+ steps_per_epoch=len(train_loader),
147
+ pct_start=args.pct_start,
148
+ )
149
+ if args.scheduler == "CosineAnnealingLR":
150
+ return torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=args.epochs)
151
+ if args.scheduler == "StepLR":
152
+ return torch.optim.lr_scheduler.StepLR(
153
+ optimizer, step_size=args.step_size, gamma=args.gamma
154
+ )
155
+ raise ValueError("Scheduler only OneCycleLR, CosineAnnealingLR or StepLR")
156
+
157
+
158
+ def evaluate(model, test_loader, dataset, args, device):
159
+ myloss = L2Loss(size_average=False)
160
+ model.eval()
161
+ rel_err = 0.0
162
+ with torch.no_grad():
163
+ for pos, fx, y in test_loader:
164
+ x, fx, y = pos.to(device), fx.to(device), y.to(device)
165
+ if args.fun_dim == 0:
166
+ fx = None
167
+ out = model(x, fx)
168
+ if args.normalize:
169
+ out = dataset.y_normalizer.decode(out)
170
+ rel_err += myloss(out, y).item()
171
+ return rel_err / args.ntest
172
+
173
+
174
+ def train(args):
175
+ load_onescience()
176
+ DistributedManager.initialize()
177
+ dist = DistributedManager()
178
+ dataset, train_loader, test_loader, args.shapelist = get_data(args, dist)
179
+ device = get_device(args, dist)
180
+ model = get_model(args, device).to(device)
181
+ if hasattr(dataset, "x_normalizer"):
182
+ dataset.x_normalizer = dataset.x_normalizer.to(device)
183
+ if hasattr(dataset, "y_normalizer"):
184
+ dataset.y_normalizer = dataset.y_normalizer.to(device)
185
+
186
+ optimizer = make_optimizer(args, model)
187
+ scheduler = make_scheduler(args, optimizer, train_loader)
188
+ checkpoint_dir = PROJECT_ROOT / "checkpoints"
189
+ checkpoint_dir.mkdir(parents=True, exist_ok=True)
190
+ checkpoint_path = checkpoint_dir / f"{args.save_name}.pt"
191
+ checkpoint_layers = (
192
+ [layer.strip() for layer in args.checkpoint_layers.split(",") if layer.strip()]
193
+ if args.use_checkpoint and args.checkpoint_layers
194
+ else []
195
+ )
196
+ myloss = L2Loss(size_average=False)
197
+ regloss = DerivLoss(size_average=False, shapelist=args.shapelist) if args.derivloss else None
198
+ best_test_loss = float("inf")
199
+ best_epoch = 0
200
+
201
+ print(args)
202
+ print(model)
203
+ print(f"Use device: {device}")
204
+
205
+ for epoch in range(args.epochs):
206
+ model.train()
207
+ train_loss = 0.0
208
+ for pos, fx, y in train_loader:
209
+ x, fx, y = pos.to(device), fx.to(device), y.to(device)
210
+ if args.fun_dim == 0:
211
+ fx = None
212
+ with replace_function(
213
+ module=model,
214
+ replace_layers_list=checkpoint_layers,
215
+ ddp_flag=(dist.world_size > 1),
216
+ ):
217
+ out = model(x, fx)
218
+ if args.normalize:
219
+ out = dataset.y_normalizer.decode(out)
220
+ y = dataset.y_normalizer.decode(y)
221
+ loss = myloss(out, y)
222
+ if regloss is not None:
223
+ loss = loss + 0.1 * regloss(out, y)
224
+
225
+ train_loss += loss.item()
226
+ optimizer.zero_grad()
227
+ loss.backward()
228
+ if args.max_grad_norm is not None:
229
+ torch.nn.utils.clip_grad_norm_(model.parameters(), args.max_grad_norm)
230
+ optimizer.step()
231
+ if args.scheduler == "OneCycleLR":
232
+ scheduler.step()
233
+
234
+ if args.scheduler in {"CosineAnnealingLR", "StepLR"}:
235
+ scheduler.step()
236
+
237
+ train_loss = train_loss / args.ntrain
238
+ rel_err = evaluate(model, test_loader, dataset, args, device)
239
+ if rel_err < best_test_loss:
240
+ best_test_loss = rel_err
241
+ best_epoch = epoch
242
+ torch.save(
243
+ {
244
+ "epoch": epoch,
245
+ "model_state": model.state_dict(),
246
+ "optimizer_state": optimizer.state_dict(),
247
+ "scheduler_state": scheduler.state_dict(),
248
+ "best_test_loss": best_test_loss,
249
+ "best_epoch": best_epoch,
250
+ "args": vars(args),
251
+ },
252
+ checkpoint_path,
253
+ )
254
+ if epoch % 10 == 0:
255
+ print("Epoch {} Train loss : {:.5f}".format(epoch, train_loss))
256
+ print("rel_err:{}".format(rel_err))
257
+
258
+ print(
259
+ "Training completed. Best model saved at epoch {} with rel_err: {:.5f}".format(
260
+ best_epoch, best_test_loss
261
+ )
262
+ )
263
+
264
+
265
+ def main():
266
+ os.chdir(PROJECT_ROOT)
267
+ cfg = load_config()
268
+ if cfg.get("runtime", {}).get("device", "cpu") == "cpu":
269
+ os.environ["CUDA_VISIBLE_DEVICES"] = ""
270
+ train(build_args(cfg))
271
+
272
+
273
+ if __name__ == "__main__":
274
+ main()
weight/.gitkeep ADDED
File without changes