ML: PINN代码实例-顶盖驱动流

本文尝试使用PINN求解2D顶盖驱动流。据岳子所知,在2024年10月,本代码是国际上首次开源的基于OpenFOAM/libtorch的顶盖驱动流开源代码。在阅读本文之前,请先阅读:

要不然不会理解代码的含义。相应的代码如下。由于代码量过大,不对其进行介绍。建议系统学习OpenFOAM以及libtorch后再来理解。

#include <torch/torch.h>

using namespace std;

//创建神经网络
struct NN
:
    torch::nn::Cloneable<NN>
{
    torch::nn::Sequential net_;
public:
    NN()
    {
        reset();
    }
    void reset() override
    {
        int neu = 100;
        net_ = register_module
        (
            "net", 
            torch::nn::Sequential
            (
                torch::nn::Linear(2,neu),
                torch::nn::Tanh(),
                torch::nn::Linear(neu,neu),
                torch::nn::Tanh(),
                torch::nn::Linear(neu,neu),
                torch::nn::Tanh(),
                torch::nn::Linear(neu,3)
            )
        );
    }
    auto forward(torch::Tensor x)
    {
        return net_->forward(x);
    }
};

int main()
{
    int iter = 1;
    double learningRate = 0.0005;
    int IterationNum = 10000;
    auto crit = torch::nn::MSELoss();
    auto net = std::make_shared<NN>();

    auto opti = 
       torch::optim::Adam
       (
           net->parameters(),
           torch::optim::AdamOptions(learningRate)
       );   

    torch::manual_seed(0); 
    int pixel = 70;//网格数
    double nu = 0.1;//粘度
    double vel = 3.0;//顶盖速度
    
    auto bottom = torch::full({pixel, 2}, 0.0);
    auto top = torch::full({pixel, 2}, 1.0);
    auto left = torch::full({pixel, 2}, 0.0);
    auto right = torch::full({pixel, 2}, 1.0);
    auto u_top = torch::full({pixel}, vel);
    auto v_top = torch::full({pixel}, 0.0);
    
    for (int i = 0; i < pixel; i++)
    {
        int min = 1;
        int max = 100;
        int numb = min + rand() % (max - min + 1);
        bottom[i][0] = numb/100.0;
    }
    
    for (int i = 0; i < pixel; i++)
    {
        int min = 1;
        int max = 100;
        int numb = min + rand() % (max - min + 1);
        top[i][0] = numb/100.0;
    }
    
    for (int i = 0; i < pixel; i++)
    {
        int min = 1;
        int max = 100;
        int numb = min + rand() % (max - min + 1);
        left[i][1] = numb/100.0;
    }
    
    for (int i = 0; i < pixel; i++)
    {
        int min = 1;
        int max = 100;
        int numb = min + rand() % (max - min + 1);
        right[i][1] = numb/100.0;
    }
    
    auto mesh = torch::rand({pixel*pixel, 2});
    
    mesh.requires_grad_(true);
    top.requires_grad_(true);
    left.requires_grad_(true);
    right.requires_grad_(true);
    bottom.requires_grad_(true);

    for (int i = 0; i < IterationNum; i++) 
    {
        //#include "toCUDA.H" 

        opti.zero_grad();

        auto UP = net->forward(mesh);
        auto UPtop = net->forward(top);
        auto UPright = net->forward(right);
        auto UPbottom = net->forward(bottom);
        auto UPleft = net->forward(left);

        auto u_pred = UP.index({torch::indexing::Slice(), 0});
        auto v_pred = UP.index({torch::indexing::Slice(), 1});
        auto p_pred = UP.index({torch::indexing::Slice(), 2});

        auto u_pred_top = UPtop.index({torch::indexing::Slice(), 0});
        auto v_pred_top = UPtop.index({torch::indexing::Slice(), 1}); 
        auto p_pred_top = UPtop.index({torch::indexing::Slice(), 2});  

        auto u_pred_left = UPleft.index({torch::indexing::Slice(), 0});
        auto v_pred_left = UPleft.index({torch::indexing::Slice(), 1}); 
        auto p_pred_left = UPleft.index({torch::indexing::Slice(), 2});  
        
        auto u_pred_bottom = UPbottom.index({torch::indexing::Slice(), 0});
        auto v_pred_bottom = UPbottom.index({torch::indexing::Slice(), 1}); 
        auto p_pred_bottom = UPbottom.index({torch::indexing::Slice(), 2});  

        auto u_pred_right = UPright.index({torch::indexing::Slice(), 0});
        auto v_pred_right = UPright.index({torch::indexing::Slice(), 1}); 
        auto p_pred_right = UPright.index({torch::indexing::Slice(), 2});  

        auto dpdMesh = 
            torch::autograd::grad
            (
                {p_pred},
                {mesh},
                {torch::ones_like(p_pred)},
                true,
                true
            )[0];

        auto dpdx = dpdMesh.index({torch::indexing::Slice(), 0});
        auto dpdy = dpdMesh.index({torch::indexing::Slice(), 1});

        auto dpdxMesh = 
            torch::autograd::grad
            (
                {dpdx},
                {mesh},
                {torch::ones_like(dpdx)},
                true,
                true
            )[0];
        auto dpdyMesh = 
            torch::autograd::grad
            (
                {dpdy},
                {mesh},
                {torch::ones_like(dpdy)},
                true,
                true
            )[0];
        auto dpdxx = dpdxMesh.index({torch::indexing::Slice(), 0});
        auto dpdyy = dpdyMesh.index({torch::indexing::Slice(), 1});

        auto dpdLeft = 
            torch::autograd::grad
            (
                {p_pred_left},
                {left},
                {torch::ones_like(p_pred_left)},
                true,
                true
            )[0];

        auto dpdx_left = dpdLeft.index({torch::indexing::Slice(), 0});
        auto dpdy_left = dpdLeft.index({torch::indexing::Slice(), 1});

        auto dpdBottom = 
            torch::autograd::grad
            (
                {p_pred_bottom},
                {bottom},
                {torch::ones_like(p_pred_bottom)},
                true,
                true
            )[0];

        auto dpdx_bottom = dpdBottom.index({torch::indexing::Slice(), 0});
        auto dpdy_bottom = dpdBottom.index({torch::indexing::Slice(), 1});

        auto dpdRight = 
            torch::autograd::grad
            (
                {p_pred_right},
                {right},
                {torch::ones_like(p_pred_right)},
                true,
                true
            )[0];

        auto dpdx_right = dpdRight.index({torch::indexing::Slice(), 0});
        auto dpdy_right = dpdRight.index({torch::indexing::Slice(), 1});

        auto dpdTop = 
            torch::autograd::grad
            (
                {p_pred_top},
                {top},
                {torch::ones_like(p_pred_top)},
                true,
                true
            )[0];

        auto dpdx_top = dpdTop.index({torch::indexing::Slice(), 0});
        auto dpdy_top = dpdTop.index({torch::indexing::Slice(), 1});

        auto dudMesh = 
            torch::autograd::grad
            (
                {u_pred},
                {mesh},
                {torch::ones_like(u_pred)},
                true,
                true
            )[0];
        auto dudx = dudMesh.index({torch::indexing::Slice(), 0});
        auto dudy = dudMesh.index({torch::indexing::Slice(), 1});
 
        auto dvdMesh = 
            torch::autograd::grad
            (
                {v_pred},
                {mesh},
                {torch::ones_like(v_pred)},
                true,
                true
            )[0];
        auto dvdx = dvdMesh.index({torch::indexing::Slice(), 0});
        auto dvdy = dvdMesh.index({torch::indexing::Slice(), 1});

        auto dudxMesh = 
            torch::autograd::grad
            (
                {dudx},
                {mesh},
                {torch::ones_like(dudx)},
                true,
                true
            )[0];
        auto dudxx = dudxMesh.index({torch::indexing::Slice(), 0});

        auto dudyMesh = 
            torch::autograd::grad
            (
                {dudy},
                {mesh},
                {torch::ones_like(dudy)},
                true,
                true
            )[0];
        auto dudyy = dudyMesh.index({torch::indexing::Slice(), 1});
        auto dudxy = dudyMesh.index({torch::indexing::Slice(), 0});

        auto dvdxMesh = 
            torch::autograd::grad
            (
                {dvdx},
                {mesh},
                {torch::ones_like(dvdx)},
                true,
                true
            )[0];
        auto dvdyMesh = 
            torch::autograd::grad
            (
                {dvdy},
                {mesh},
                {torch::ones_like(dvdy)},
                true,
                true
            )[0];
        auto dvdxx = dvdxMesh.index({torch::indexing::Slice(), 0});
        auto dvdxy = dvdxMesh.index({torch::indexing::Slice(), 1});
        auto dvdyy = dvdyMesh.index({torch::indexing::Slice(), 1});
    
        auto cont1 = dudx + dvdy;
        auto mom1 = 2.0*u_pred*dudx + v_pred*dudy + u_pred*dvdy 
          - nu*(dudxx + dudyy) - nu*(dudxx + dvdxy) + dpdx;
        auto mom2 = u_pred*dvdx + v_pred*dudx + 2.0*v_pred*dvdy 
          - nu*(dvdxx + dvdyy) - nu*(dudxy + dvdyy) + dpdy;
        auto loss_cont1 = crit(cont1, 0*cont1);
        auto loss_mom1 = crit(mom1, 0*mom1);
        auto loss_mom2 = crit(mom2, 0*mom2);
    
        auto loss_top1 = crit(u_pred_top, u_top); 
        auto loss_top2 = crit(v_pred_top, v_top); 
        auto loss_top3 = crit(dpdy_top, 0*dpdy_top);
        auto loss_top = loss_top1 + loss_top2 + loss_top3;
        auto loss_left1 = crit(u_pred_left, 0*u_pred_left);
        auto loss_left2 = crit(v_pred_left, 0*v_pred_left);
        auto loss_left3 = crit(dpdx_left, 0*dpdx_left);
        auto loss_left = loss_left1 + loss_left2 + loss_left3;
        auto loss_right1 = crit(u_pred_right, 0*u_pred_right);
        auto loss_right2 = crit(v_pred_right, 0*v_pred_right);
        auto loss_right3 = crit(dpdx_right, 0*dpdx_right);
        auto loss_right = loss_right1 + loss_right2 + loss_right3;
        auto loss_bottom1 = crit(u_pred_bottom, 0*u_pred_bottom);
        auto loss_bottom2 = crit(v_pred_bottom, 0*v_pred_bottom);
        auto loss_bottom3 = crit(dpdy_bottom, 0*dpdy_bottom);
        auto loss_bottom = loss_bottom1 + loss_bottom2 + loss_bottom3;

        auto loss_dataP = crit(p_pred[0], 0*p_pred[0]);
	    auto loss_data = 100*(loss_dataP);
        auto loss_boundary = 
            10.0*(loss_top + loss_left + loss_right + loss_bottom);
        auto loss_pde = 1.0*(loss_cont1 + loss_mom1 + loss_mom2); 

        auto loss = loss_pde + loss_boundary + loss_data;
        
        loss.backward();
        opti.step();
        iter++;
        
        if (iter % 50 == 0)
        {
            cout<< iter<< ": cont: " << loss_cont1.item<double>()
                << ", pde: " << loss_pde.item<double>()
                << ", boundary: " << loss_boundary.item<double>()
                << ", All: " << loss.item<double>() << endl;

            std::ofstream meshfile("meshfile");
            meshfile << mesh << "\n";
            std::ofstream ufile("U");
            ufile << UP << "\n";
            
            auto device = torch::kCPU;
            net->to(device);
            mesh = mesh.to(device);
            top = top.to(device);
            bottom = bottom.to(device);
            right = right.to(device);
            left = left.to(device);
            u_top = u_top.to(device);
            v_top = v_top.to(device);
            torch::save(net, "net.pth");
        }
    }

    return 0;
}

该代码运行后,会自动输出结果文件。可以通过下面的代码来出图:

#!/bin/bash

paste meshfile U > data

gnuplot<<EOF
#set terminal pngcairo size 2800,700 enhanced font 'Verdana,40' transparent
#set output 'cavity.png'
set terminal jpeg size 2800,700 enhanced font 'Verdana,40' 
set output 'cavity.jpeg'

set title 'Cavity flow predicted by PINN in the env of OpenFOAM + libtorch'

set pm3d map
set palette rgb 33,13,10
set colorbox
set dgrid3d 20,20
set xtics scale 0
set ytics scale 0
set format x ""
set format y ""
set style data points
set pointsize 2

# Start multiplot mode
set multiplot layout 1,4 title 'Cavity flow predicted by PINN in the env of OpenFOAM + libtorch'

# First plot
set title 'Ux'
#set cbrange [-0.2:2]
plot 'data' using 1:2:3 with points pointtype 7  palette notitle
unset cbrange 

# Second plot
set title 'Uy'
plot 'data' using 1:2:4 with points pointtype 7  palette notitle

# Third plot
set title 'Umag'
#set cbrange [0:2]
plot 'data' using 1:2:(sqrt(column(3)*column(3)+column(4)*column(4))) with points pointtype 7  palette notitle
unset cbrange 

# Fourth plot
set title 'p'
plot 'data' using 1:2:5 with points pointtype 7  palette notitle

# End multiplot mode
unset multiplot
EOF

相应的结果如下:

_images/cavity.jpeg