跳转至

Extended Physics-Informed Neural Networks (XPINNs)

# linux
wget -nc https://paddle-org.bj.bcebos.com/paddlescience/datasets/XPINN/XPINN_2D_PoissonEqn.mat -P ./data/
# windows
# curl https://paddle-org.bj.bcebos.com/paddlescience/datasets/XPINN/XPINN_2D_PoissonEqn.mat --create-dirs -o ./data/XPINN_2D_PoissonEqn.mat
python xpinn.py
# linux
wget -nc https://paddle-org.bj.bcebos.com/paddlescience/datasets/XPINN/XPINN_2D_PoissonEqn.mat -P ./data/
# windows
# curl https://paddle-org.bj.bcebos.com/paddlescience/datasets/XPINN/XPINN_2D_PoissonEqn.mat --create-dirs -o ./data/XPINN_2D_PoissonEqn.mat
python xpinn.py mode=eval EVAL.pretrained_model_path=https://paddle-org.bj.bcebos.com/paddlescience/models/XPINN/xpinn_pretrained.pdparams
预训练模型 指标
xpinn_pretrained.pdparams L2Rel.l2_error: 0.04226

1. 背景简介

求解偏微分方程(PDE)是一类基础的物理问题,随着人工智能技术的高速发展,利用深度学习求解偏微分方程成为新的研究趋势。XPINNs(Extended Physics-Informed Neural Networks)是一种适用于物理信息神经网络(PINNs)的广义时空域分解方法,以求解任意复杂几何域上的非线性偏微分方程。

XPINNs 通过广义时空区域分解,有效地提高了模型的并行能力,并且支持高度不规则的、凸/非凸的时空域分解,界面条件是简单的。XPINNs 可扩展到任意类型的偏微分方程,而不论方程是何种物理性质。

精确求解高维复杂的方程已经成为科学计算的最大挑战之一,XPINNs 的优点使其成为模拟复杂方程的适用方法。

2. 问题定义

二维泊松方程:

\[ \Delta u = f(x, y), x,y \in \Omega \subset R^2\]

3. 问题求解

接下来开始讲解如何将问题一步一步地转化为 PaddleScience 代码,用深度学习的方法求解该问题。 为了快速理解 PaddleScience,接下来仅对模型构建、方程构建、计算域构建等关键步骤进行阐述,而其余细节请参考 API文档

3.1 数据集下载

如下图所示,数据集包含计算域的三个子区域的数据:红色区域的边界和残差点;黄色区域的界面;以及绿色区域的界面。

二维泊松方程的三个子区域

计算域的边界表达式如下。

\[ \gamma =1.5+0.14 sin(4θ)+0.12 cos(6θ)+0.09 cos(5θ), θ \in [0,2π) \]

红色区域和黄色区域的界面的表达式如下。

\[ \gamma_1 =0.5+0.18 sin(3θ)+0.08 cos(2θ)+0.2 cos(5θ), θ \in [0,2π)\]
\[ \gamma_2 =0.34+0.04 sin(5θ)+0.18 cos(3θ)+0.1 cos(6θ), θ \in [0,2π) \]

执行以下命令,下载并解压数据集。

wget -nc https://paddle-org.bj.bcebos.com/paddlescience/datasets/XPINN/XPINN_2D_PoissonEqn.mat -P ./data/

3.2 模型构建

在本问题中,我们使用神经网络 MLP 作为模型,在模型代码中定义三个 MLP ,分别作为三个子区域的模型。

# set model
custom_model = model.Model(layer_list)

模型训练时,我们将使用 XPINN 方法分别计算每个子区域的模型损失。

XPINN子网络的训练过程

3.3 约束构建

在本案例中,我们使用监督数据集对模型进行训练,因此需要构建监督约束。

在定义约束之前,我们需要指定数据集的路径等相关配置,将这些信息存放到对应的 YAML 文件中,如下所示。

# set training data file
DATA_FILE: "./data/XPINN_2D_PoissonEqn.mat"

接着定义训练损失函数的计算过程,调用 XPINN 方法计算损失,如下所示。

def loss_fun(
    output_dict: Dict[str, paddle.Tensor],
    label_dict: Dict[str, paddle.Tensor],
    *args,
) -> float:
    def residual_func(output_der: paddle.Tensor, input: paddle.Tensor) -> paddle.Tensor:
        return paddle.add_n(output_der) - paddle.add_n(
            [paddle.exp(_in) for _in in input]
        )

    # subdomain 1
    loss1 = _xpinn_loss(
        training_pres=[output_dict["boundary_u"]],
        training_exacts=[label_dict["boundary_u_exact"]],
        training_weight=20,
        residual_inputs=[[output_dict["residual1_x"], output_dict["residual1_y"]]],
        residual_pres=[output_dict["residual1_u"]],
        residual_weight=1,
        interface_inputs=[
            [output_dict["interface1_x"], output_dict["interface1_y"]],
            [output_dict["interface2_x"], output_dict["interface2_y"]],
        ],
        interface_pres=[
            output_dict["interface1_u_sub1"],
            output_dict["interface2_u_sub1"],
        ],
        interface_weight=20,
        interface_neigh_pres=[
            [output_dict["interface1_u_sub2"]],
            [output_dict["interface2_u_sub3"]],
        ],
        interface_neigh_weight=1,
        residual_func=residual_func,
    )

    # subdomain 2
    loss2 = _xpinn_loss(
        residual_inputs=[[output_dict["residual2_x"], output_dict["residual2_y"]]],
        residual_pres=[output_dict["residual2_u"]],
        residual_weight=1,
        interface_inputs=[[output_dict["interface1_x"], output_dict["interface1_y"]]],
        interface_pres=[output_dict["interface1_u_sub1"]],
        interface_weight=20,
        interface_neigh_pres=[[output_dict["interface1_u_sub2"]]],
        interface_neigh_weight=1,
        residual_func=residual_func,
    )

    # subdomain 3
    loss3 = _xpinn_loss(
        residual_inputs=[[output_dict["residual3_x"], output_dict["residual3_y"]]],
        residual_pres=[output_dict["residual3_u"]],
        residual_weight=1,
        interface_inputs=[[output_dict["interface2_x"], output_dict["interface2_y"]]],
        interface_pres=[output_dict["interface2_u_sub1"]],
        interface_weight=20,
        interface_neigh_pres=[[output_dict["interface2_u_sub3"]]],
        interface_neigh_weight=1,
        residual_func=residual_func,
    )

    return {"residuals": loss1 + loss2 + loss3}

最后构建监督约束,如下所示。

# set constraint
sup_constraint = ppsci.constraint.SupervisedConstraint(
    train_dataloader_cfg,
    ppsci.loss.FunctionalLoss(loss_fun),
    {"residual1_u": lambda out: out["residual1_u"]},
    name="sup_constraint",
)
constraint = {sup_constraint.name: sup_constraint}

3.4 超参数设定

设置训练轮数等参数,如下所示。

epochs: 501
iters_per_epoch: 1
save_freq: 50
eval_during_train: true
eval_freq: 50
learning_rate: 0.0008

3.5 优化器构建

训练过程会调用优化器来更新模型参数,此处选择较为常用的 Adam 优化器。

# set optimizer
optimizer = ppsci.optimizer.Adam(cfg.TRAIN.learning_rate)(custom_model)

3.6 评估器构建

在训练过程中通常会按一定轮数间隔,用验证集(测试集)评估当前模型的训练情况,因此使用 ppsci.validate.SupervisedValidator 构建评估器。

sup_validator = ppsci.validate.SupervisedValidator(
    eval_dataloader_cfg,
    loss=ppsci.loss.FunctionalLoss(loss_fun),
    output_expr={
        "residual1_u": lambda out: out["residual1_u"],
        "residual2_u": lambda out: out["residual2_u"],
        "residual3_u": lambda out: out["residual3_u"],
    },
    metric={"L2Rel": ppsci.metric.FunctionalMetric(eval_l2_rel_func)},
    name="sup_validator",
)
validator = {sup_validator.name: sup_validator}

评估指标为预测结果和真实结果的 L2 相对误差值,这里需自定义指标计算函数,如下所示。

def eval_l2_rel_func(
    output_dict: Dict[str, paddle.Tensor],
    label_dict: Dict[str, paddle.Tensor],
    *args,
) -> Dict[str, paddle.Tensor]:
    u_pred = paddle.concat(
        [
            output_dict["residual1_u"],
            output_dict["residual2_u"],
            output_dict["residual3_u"],
        ]
    )

    # the shape of label_dict["residual_u_exact"] is [22387, 1], and be cut into [18211, 1] `_eval_by_dataset`(ppsci/solver/eval.py).
    u_exact = paddle.concat(
        [
            label_dict["residual_u_exact"],
            label_dict["residual2_u_exact"],
            label_dict["residual3_u_exact"],
        ]
    )

    error_total = paddle.linalg.norm(
        u_exact.flatten() - u_pred.flatten(), 2
    ) / paddle.linalg.norm(u_exact.flatten(), 2)
    return {"l2_error": error_total}

3.7 模型训练评估

完成上述设置之后,只需要将上述实例化的对象按顺序传递给 ppsci.solver.Solver,然后启动训练、评估。

# initialize solver
solver = ppsci.solver.Solver(
    custom_model,
    constraint,
    optimizer=optimizer,
    validator=validator,
    cfg=cfg,
)

solver.train()
solver.eval()

3.8 结果可视化

训练完毕之后程序会对测试集中的数据进行预测,并以图片的形式对结果进行可视化,如下所示。

# visualize prediction
with solver.no_grad_context_manager(True):
    for index, (_input, _label, _) in enumerate(sup_validator.data_loader):
        u_exact = _label["residual_u_exact"]
        output_ = custom_model(_input)
        u_pred = paddle.concat(
            [output_["residual1_u"], output_["residual2_u"], output_["residual3_u"]]
        )

        plotting.log_image(
            residual1_x=_input["residual1_x"],
            residual1_y=_input["residual1_y"],
            residual2_x=_input["residual2_x"],
            residual2_y=_input["residual2_y"],
            residual3_x=_input["residual3_x"],
            residual3_y=_input["residual3_y"],
            interface1_x=_input["interface1_x"],
            interface1_y=_input["interface1_y"],
            interface2_x=_input["interface2_x"],
            interface2_y=_input["interface2_y"],
            boundary_x=_input["boundary_x"],
            boundary_y=_input["boundary_y"],
            residual_u_pred=u_pred,
            residual_u_exact=u_exact,
        )

4. 完整代码

xpinn.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
# Copyright (c) 2024 PaddlePaddle Authors. All Rights Reserved.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Callable
from typing import Dict
from typing import List
from typing import Tuple

import hydra
import model
import numpy as np
import paddle
import plotting
from omegaconf import DictConfig

import ppsci

# For the use of the second derivative: paddle.cos
paddle.framework.core.set_prim_eager_enabled(True)


def _xpinn_loss(
    training_pres: List[List[paddle.Tensor]] = None,
    training_exacts: List[paddle.Tensor] = None,
    training_weight: float = 1,
    residual_inputs: List[List[paddle.Tensor]] = None,
    residual_pres: List[paddle.Tensor] = None,
    residual_weight: float = 1,
    interface_inputs: List[List[paddle.Tensor]] = None,
    interface_pres: List[paddle.Tensor] = None,
    interface_weight: float = 1,
    interface_neigh_pres: List[List[paddle.Tensor]] = None,
    interface_neigh_weight: float = 1,
    residual_func: Callable = lambda x, y: x - y,
) -> float:
    """XPINNs loss function for subdomain

        `loss = W_u_q * MSE_u_q + W_F_q * MSE_F_q + W_I_q * MSE_avg_q + W_I_F_q * MSE_R`

        `W_u_q * MSE_u_q` is data mismatch item.
        `W_F_q * MSE_F_q` is residual item.
        `W_I_q * MSE_avg_q` is interface item.
        `W_I_F_q * MSE_R` is interface residual item.

    Args:
        training_pres (List[List[paddle.Tensor]], optional): the prediction result for training points input. Defaults to None.
        training_exacts (List[paddle.Tensor], optional): the exact result for training points input. Defaults to None.
        training_weight (float, optional): the weight of data mismatch item. Defaults to 1.
        residual_inputs (List[List[paddle.Tensor]], optional): residual points input. Defaults to None.
        residual_pres (List[paddle.Tensor], optional): the prediction result for residual points input. Defaults to None.
        residual_weight (float, optional): the weight of residual item. Defaults to 1.
        interface_inputs (List[List[paddle.Tensor]], optional): the prediction result for interface points input. Defaults to None.
        interface_pres (List[paddle.Tensor], optional): the prediction result for interface points input. Defaults to None.
        interface_weight (float, optional): the weight of iinterface item. Defaults to 1.
        interface_neigh_pres (List[List[paddle.Tensor]], optional): the prediction result of neighbouring subdomain model for interface points input. Defaults to None.
        interface_neigh_weight (float, optional): the weight of interface residual term. Defaults to 1.
        residual_func (Callable, optional): residual calculation  function. Defaults to lambda x,y : x - y.
    """

    def _get_grad(outputs: paddle.Tensor, inputs: paddle.Tensor) -> paddle.Tensor:
        grad = paddle.grad(outputs, inputs, retain_graph=True, create_graph=True)
        return grad[0]

    def _get_second_derivatives(
        outputs_list: List[paddle.Tensor],
        inputs_list: List[List[paddle.Tensor]],
    ) -> Tuple[List[List[paddle.Tensor]], List[List[paddle.Tensor]]]:
        d1_list = [
            [_get_grad(_out, _in) for _in in _ins]
            for _out, _ins in zip(outputs_list, inputs_list)
        ]
        d2_list = [
            [_get_grad(_d1, _in) for _d1, _in in zip(d1s_, _ins)]
            for d1s_, _ins in zip(d1_list, inputs_list)
        ]
        return d2_list

    residual_u_d2_list = _get_second_derivatives(residual_pres, residual_inputs)
    interface_u_d2_list = _get_second_derivatives(interface_pres, interface_inputs)
    interface_neigh_u_d2_list = _get_second_derivatives(
        interface_neigh_pres, interface_inputs
    )

    MSE_u_q = 0

    if training_pres is not None:
        for _pre, _exact in zip(training_pres, training_exacts):
            MSE_u_q += training_weight * paddle.mean(paddle.square(_pre - _exact))

    MSE_F_q = 0

    if residual_inputs is not None:
        for _ins, _d2 in zip(residual_inputs, residual_u_d2_list):
            MSE_F_q += residual_weight * paddle.mean(
                paddle.square(residual_func(_d2, _ins))
            )

    MSE_avg_q = 0
    MSE_R = 0

    if interface_inputs is not None:
        for _ins, _pre, _n_pres in zip(
            interface_inputs, interface_pres, interface_neigh_pres
        ):
            pre_list = [_pre] + _n_pres
            pre_avg = paddle.add_n(pre_list) / len(pre_list)
            MSE_avg_q += interface_weight * paddle.mean(paddle.square(_pre - pre_avg))

        for _ins, _d2, _n_d2 in zip(
            interface_inputs, interface_u_d2_list, interface_neigh_u_d2_list
        ):
            MSE_R += interface_neigh_weight * paddle.mean(
                paddle.square(residual_func(_d2, _ins) - residual_func(_n_d2, _ins))
            )

    return MSE_u_q + MSE_F_q + MSE_avg_q + MSE_R


def loss_fun(
    output_dict: Dict[str, paddle.Tensor],
    label_dict: Dict[str, paddle.Tensor],
    *args,
) -> float:
    def residual_func(output_der: paddle.Tensor, input: paddle.Tensor) -> paddle.Tensor:
        return paddle.add_n(output_der) - paddle.add_n(
            [paddle.exp(_in) for _in in input]
        )

    # subdomain 1
    loss1 = _xpinn_loss(
        training_pres=[output_dict["boundary_u"]],
        training_exacts=[label_dict["boundary_u_exact"]],
        training_weight=20,
        residual_inputs=[[output_dict["residual1_x"], output_dict["residual1_y"]]],
        residual_pres=[output_dict["residual1_u"]],
        residual_weight=1,
        interface_inputs=[
            [output_dict["interface1_x"], output_dict["interface1_y"]],
            [output_dict["interface2_x"], output_dict["interface2_y"]],
        ],
        interface_pres=[
            output_dict["interface1_u_sub1"],
            output_dict["interface2_u_sub1"],
        ],
        interface_weight=20,
        interface_neigh_pres=[
            [output_dict["interface1_u_sub2"]],
            [output_dict["interface2_u_sub3"]],
        ],
        interface_neigh_weight=1,
        residual_func=residual_func,
    )

    # subdomain 2
    loss2 = _xpinn_loss(
        residual_inputs=[[output_dict["residual2_x"], output_dict["residual2_y"]]],
        residual_pres=[output_dict["residual2_u"]],
        residual_weight=1,
        interface_inputs=[[output_dict["interface1_x"], output_dict["interface1_y"]]],
        interface_pres=[output_dict["interface1_u_sub1"]],
        interface_weight=20,
        interface_neigh_pres=[[output_dict["interface1_u_sub2"]]],
        interface_neigh_weight=1,
        residual_func=residual_func,
    )

    # subdomain 3
    loss3 = _xpinn_loss(
        residual_inputs=[[output_dict["residual3_x"], output_dict["residual3_y"]]],
        residual_pres=[output_dict["residual3_u"]],
        residual_weight=1,
        interface_inputs=[[output_dict["interface2_x"], output_dict["interface2_y"]]],
        interface_pres=[output_dict["interface2_u_sub1"]],
        interface_weight=20,
        interface_neigh_pres=[[output_dict["interface2_u_sub3"]]],
        interface_neigh_weight=1,
        residual_func=residual_func,
    )

    return {"residuals": loss1 + loss2 + loss3}


def eval_l2_rel_func(
    output_dict: Dict[str, paddle.Tensor],
    label_dict: Dict[str, paddle.Tensor],
    *args,
) -> Dict[str, paddle.Tensor]:
    u_pred = paddle.concat(
        [
            output_dict["residual1_u"],
            output_dict["residual2_u"],
            output_dict["residual3_u"],
        ]
    )

    # the shape of label_dict["residual_u_exact"] is [22387, 1], and be cut into [18211, 1] `_eval_by_dataset`(ppsci/solver/eval.py).
    u_exact = paddle.concat(
        [
            label_dict["residual_u_exact"],
            label_dict["residual2_u_exact"],
            label_dict["residual3_u_exact"],
        ]
    )

    error_total = paddle.linalg.norm(
        u_exact.flatten() - u_pred.flatten(), 2
    ) / paddle.linalg.norm(u_exact.flatten(), 2)
    return {"l2_error": error_total}


def train(cfg: DictConfig):
    # set training dataset transformation
    def train_dataset_transform_func(
        _input: Dict[str, np.ndarray],
        _label: Dict[str, np.ndarray],
        weight_: Dict[str, np.ndarray],
    ) -> Dict[str, np.ndarray]:
        # Randomly select the residual points from sub-domains
        id_x1 = np.random.choice(
            _input["residual1_x"].shape[0],
            cfg.MODEL.num_residual1_points,
            replace=False,
        )
        _input["residual1_x"] = _input["residual1_x"][id_x1, :]
        _input["residual1_y"] = _input["residual1_y"][id_x1, :]

        id_x2 = np.random.choice(
            _input["residual2_x"].shape[0],
            cfg.MODEL.num_residual2_points,
            replace=False,
        )
        _input["residual2_x"] = _input["residual2_x"][id_x2, :]
        _input["residual2_y"] = _input["residual2_y"][id_x2, :]

        id_x3 = np.random.choice(
            _input["residual3_x"].shape[0],
            cfg.MODEL.num_residual3_points,
            replace=False,
        )
        _input["residual3_x"] = _input["residual3_x"][id_x3, :]
        _input["residual3_y"] = _input["residual3_y"][id_x3, :]

        # Randomly select boundary points
        id_x4 = np.random.choice(
            _input["boundary_x"].shape[0], cfg.MODEL.num_boundary_points, replace=False
        )
        _input["boundary_x"] = _input["boundary_x"][id_x4, :]
        _input["boundary_y"] = _input["boundary_y"][id_x4, :]
        _label["boundary_u_exact"] = _label["boundary_u_exact"][id_x4, :]

        # Randomly select the interface points along two interfaces
        id_xi1 = np.random.choice(
            _input["interface1_x"].shape[0], cfg.MODEL.num_interface1, replace=False
        )
        _input["interface1_x"] = _input["interface1_x"][id_xi1, :]
        _input["interface1_y"] = _input["interface1_y"][id_xi1, :]

        id_xi2 = np.random.choice(
            _input["interface2_x"].shape[0], cfg.MODEL.num_interface2, replace=False
        )
        _input["interface2_x"] = _input["interface2_x"][id_xi2, :]
        _input["interface2_y"] = _input["interface2_y"][id_xi2, :]

        return _input, _label, weight_

    # set dataloader config
    train_dataloader_cfg = {
        "dataset": {
            "name": "IterableMatDataset",
            "file_path": cfg.DATA_FILE,
            "input_keys": cfg.TRAIN.input_keys,
            "label_keys": cfg.TRAIN.label_keys,
            "alias_dict": cfg.TRAIN.alias_dict,
            "transforms": (
                {
                    "FunctionalTransform": {
                        "transform_func": train_dataset_transform_func,
                    },
                },
            ),
        }
    }

    layer_list = (
        cfg.MODEL.layers1,
        cfg.MODEL.layers2,
        cfg.MODEL.layers3,
    )

    # set model
    custom_model = model.Model(layer_list)

    # set constraint
    sup_constraint = ppsci.constraint.SupervisedConstraint(
        train_dataloader_cfg,
        ppsci.loss.FunctionalLoss(loss_fun),
        {"residual1_u": lambda out: out["residual1_u"]},
        name="sup_constraint",
    )
    constraint = {sup_constraint.name: sup_constraint}

    # set validator
    eval_dataloader_cfg = {
        "dataset": {
            "name": "IterableMatDataset",
            "file_path": cfg.DATA_FILE,
            "input_keys": cfg.TRAIN.input_keys,
            "label_keys": cfg.EVAL.label_keys,
            "alias_dict": cfg.EVAL.alias_dict,
        }
    }

    sup_validator = ppsci.validate.SupervisedValidator(
        eval_dataloader_cfg,
        loss=ppsci.loss.FunctionalLoss(loss_fun),
        output_expr={
            "residual1_u": lambda out: out["residual1_u"],
            "residual2_u": lambda out: out["residual2_u"],
            "residual3_u": lambda out: out["residual3_u"],
        },
        metric={"L2Rel": ppsci.metric.FunctionalMetric(eval_l2_rel_func)},
        name="sup_validator",
    )
    validator = {sup_validator.name: sup_validator}

    # set optimizer
    optimizer = ppsci.optimizer.Adam(cfg.TRAIN.learning_rate)(custom_model)

    # initialize solver
    solver = ppsci.solver.Solver(
        custom_model,
        constraint,
        optimizer=optimizer,
        validator=validator,
        cfg=cfg,
    )

    solver.train()
    solver.eval()

    # visualize prediction
    with solver.no_grad_context_manager(True):
        for index, (_input, _label, _) in enumerate(sup_validator.data_loader):
            u_exact = _label["residual_u_exact"]
            output_ = custom_model(_input)
            u_pred = paddle.concat(
                [output_["residual1_u"], output_["residual2_u"], output_["residual3_u"]]
            )

            plotting.log_image(
                residual1_x=_input["residual1_x"],
                residual1_y=_input["residual1_y"],
                residual2_x=_input["residual2_x"],
                residual2_y=_input["residual2_y"],
                residual3_x=_input["residual3_x"],
                residual3_y=_input["residual3_y"],
                interface1_x=_input["interface1_x"],
                interface1_y=_input["interface1_y"],
                interface2_x=_input["interface2_x"],
                interface2_y=_input["interface2_y"],
                boundary_x=_input["boundary_x"],
                boundary_y=_input["boundary_y"],
                residual_u_pred=u_pred,
                residual_u_exact=u_exact,
            )


def evaluate(cfg: DictConfig):
    layer_list = (
        cfg.MODEL.layers1,
        cfg.MODEL.layers2,
        cfg.MODEL.layers3,
    )

    custom_model = model.Model(layer_list)

    # set validator
    eval_dataloader_cfg = {
        "dataset": {
            "name": "IterableMatDataset",
            "file_path": cfg.DATA_FILE,
            "input_keys": cfg.TRAIN.input_keys,
            "label_keys": cfg.EVAL.label_keys,
            "alias_dict": cfg.EVAL.alias_dict,
        }
    }

    sup_validator = ppsci.validate.SupervisedValidator(
        eval_dataloader_cfg,
        loss=ppsci.loss.FunctionalLoss(loss_fun),
        output_expr={
            "residual1_u": lambda out: out["residual1_u"],
            "residual2_u": lambda out: out["residual2_u"],
            "residual3_u": lambda out: out["residual3_u"],
        },
        metric={"L2Rel": ppsci.metric.FunctionalMetric(eval_l2_rel_func)},
        name="sup_validator",
    )
    validator = {sup_validator.name: sup_validator}

    # initialize solver
    solver = ppsci.solver.Solver(
        custom_model,
        validator=validator,
        cfg=cfg,
    )

    solver.eval()

    # visualize prediction
    with solver.no_grad_context_manager(True):
        for index, (_input, _label, _) in enumerate(sup_validator.data_loader):
            u_exact = _label["residual_u_exact"]
            _output = custom_model(_input)
            u_pred = paddle.concat(
                [_output["residual1_u"], _output["residual2_u"], _output["residual3_u"]]
            )

            plotting.log_image(
                residual1_x=_input["residual1_x"],
                residual1_y=_input["residual1_y"],
                residual2_x=_input["residual2_x"],
                residual2_y=_input["residual2_y"],
                residual3_x=_input["residual3_x"],
                residual3_y=_input["residual3_y"],
                interface1_x=_input["interface1_x"],
                interface1_y=_input["interface1_y"],
                interface2_x=_input["interface2_x"],
                interface2_y=_input["interface2_y"],
                boundary_x=_input["boundary_x"],
                boundary_y=_input["boundary_y"],
                residual_u_pred=u_pred,
                residual_u_exact=u_exact,
            )


@hydra.main(version_base=None, config_path="./conf", config_name="xpinn.yaml")
def main(cfg: DictConfig):
    if cfg.mode == "train":
        train(cfg)
    elif cfg.mode == "eval":
        evaluate(cfg)
    else:
        raise ValueError(f"cfg.mode should in ['train', 'eval'], but got '{cfg.mode}'")


if __name__ == "__main__":
    main()

5. 结果展示

下方展示了对计算域中每个点的预测值结果、参考结果和相对误差。

预测结果和参考结果的对比

可以看到模型预测结果与真实结果相近,若增大训练轮数,模型精度会进一步提高。

6. 参考文献