ML: PINN代码-pitzDaily流动 (自适配OpenFOAM网格)

本文在ML: PINN代码实例-顶盖驱动流 (NS方程) 的基础上,增加OpenFOAM网格适配功能。主要是代码可以自动读取OpenFOAM网格。因此对于任意的OpenFOAM算例,可以直接进行训练。但是需要注意:

  • 目前的代码只支持2D,也就是说NS方程只是植入的二维方程;

  • 数据损失部分是硬植入,因此需要用户去更改代码自定义;

  • PINN训练的时候,PINN所需要的网格可能跟FVM的有区别,在这种情况下,PINN网格与FVM网格不一致,则不适配与本代码(本代码PINN网格与FVM网格完全一致);

由于代码量过大,不对其进行介绍。建议系统学习OpenFOAM以及libtorch后再来理解。

// pitzDaily flow predicted by PINN, by dyfluid.com

#include <torch/torch.h>
#include "fvCFD.H"

// 神经网络头文件
struct ANN
:
    torch::nn::Cloneable<ANN>
{

    torch::nn::Sequential net_;

    int n_;
    int l_;
    int ipNum_;
    int opNum_;

public:

    ANN(int n, int l, int ipNum, int opNum)
    : 
        n_(n),//neuron number
        l_(l),//layer number
        ipNum_(ipNum),//input number
        opNum_(opNum)//output number
    {
        reset();
    }

    void reset() override
    {
        net_ = torch::nn::Sequential();

        // input layers
        net_->push_back(torch::nn::Linear(ipNum_, n_));
        net_->push_back(torch::nn::Tanh());

        // hidden layers
        for (int i = 0; i < l_; i++) 
        {
            net_->push_back(torch::nn::Linear(n_, n_));
            net_->push_back(torch::nn::Tanh());
        }

        // output layer
        net_->push_back(torch::nn::Linear(n_, opNum_));

        register_module("net", net_);
    }

    auto forward(torch::Tensor x)
    {
        return net_->forward(x);
    }
};


int main(int argc, char *argv[])
{
    #include "setRootCaseLists.H"
    #include "createTime.H"
    #include "createMesh.H"

    // 必要的神经网络参数
    IOdictionary trainingProperties
    (
        IOobject
        (
            "trainingProperties",
            runTime.constant(),
            mesh,
            IOobject::MUST_READ_IF_MODIFIED,
            IOobject::NO_WRITE
        )
    );
    
    // network
    int iter = trainingProperties.lookup<int>("iter");
    double lr = trainingProperties.lookup<double>("learningRate");
    int NNnum = trainingProperties.lookup<int>("numberOfNeu");
    int layers = trainingProperties.lookup<int>("numberOfLayers");
    int ipNum = trainingProperties.lookup<int>("inputNumber");
    int opNum = trainingProperties.lookup<int>("outputNumber");
    
    // Outer iterations
    bool CFD = trainingProperties.lookupOrDefault<bool>("CFD", false);
    bool training = trainingProperties.lookupOrDefault<bool>("training", false);
    int BATCH = trainingProperties.lookupOrDefault<int>("batch", 0);
    int CHANNEL = trainingProperties.lookupOrDefault<int>("channel", 0);
    
    torch::manual_seed(0); 
    auto net = std::make_shared<ANN>(NNnum, layers, ipNum, opNum);
    auto crit = torch::nn::MSELoss();
    
    torch::optim::AdamW opti(net->parameters(), torch::optim::AdamWOptions(lr));
    
    ifstream file;
    file.open("net.pth");
    if (file)
    {
        Info<< "Netword exists, reading..." << endl;
        torch::load(net, "net.pth");
    }
    else
    {
        Info<< "File does not exists, train from scratch" 
            << endl;
    }
    
    // 流体流动相关参数,训练权重等
    const dictionary& pinnProperties = trainingProperties.subDict("pinnProperties");

    double nu = readScalar(pinnProperties.lookup("nu"));
    double vel = readScalar(pinnProperties.lookup("inletU"));
    double w_data = readScalar(pinnProperties.lookup("weightsDATA"));
    double w_bd = readScalar(pinnProperties.lookup("weightsBD"));
    double w_pde = readScalar(pinnProperties.lookup("weightsPDE"));

    // OpenFOAM网格与libtorch适配
    vectorField ofMesh = mesh.C();
    auto meshTorch = torch::full({ofMesh.size(),1,2}, 0.0);
    forAll(ofMesh, i)
    {
        double x = ofMesh[i].x();
        double y = ofMesh[i].y();
        auto posi = torch::tensor({x,y}).reshape({1,2});
        meshTorch[i] = posi;
    }

    const surfaceVectorField& Cf = mesh.Cf(); 
    const surfaceVectorField& Sf = mesh.Sf(); 
    const surfaceScalarField& magSf = mesh.magSf(); 

    label inletID = mesh.boundaryMesh().findPatchID("inlet");
    label outletID = mesh.boundaryMesh().findPatchID("outlet");
    label wallID = mesh.boundaryMesh().findPatchID("wall");

    vectorField cfInlet = Cf.boundaryField()[inletID];
    vectorField cfOutlet = Cf.boundaryField()[outletID];
    vectorField cfWall = Cf.boundaryField()[wallID];

    vectorField nWall = 
        Sf.boundaryField()[wallID]/magSf.boundaryField()[wallID];

    auto inlet = torch::full({cfInlet.size(),1,2}, 0.0);
    auto outlet = torch::full({cfOutlet.size(),1,2}, 0.0);
    auto wall = torch::full({cfWall.size(),1,2}, 0.0);

    auto nlx = torch::full({cfWall.size()}, 0.0);
    auto nly = torch::full({cfWall.size()}, 0.0);

    forAll(cfInlet, i)
    {
        double x = cfInlet[i].x();
        double y = cfInlet[i].y();
        auto posi = torch::tensor({x,y}).reshape({1,2});
        inlet[i] = posi;
    }

    forAll(cfWall, i)
    {
        double x = cfWall[i].x();
        double y = cfWall[i].y();
        auto posi = torch::tensor({x,y}).reshape({1,2});
        wall[i] = posi;
    }

    forAll(cfOutlet, i)
    {
        double x = cfOutlet[i].x();
        double y = cfOutlet[i].y();
        auto posi = torch::tensor({x,y}).reshape({1,2});
        outlet[i] = posi;
    }

    forAll(cfWall, i)
    {
        nlx[i] = nWall[i].x();
        nly[i] = nWall[i].y();
    }
    
    // PINN代码正文在这里开始

    meshTorch = meshTorch.reshape({-1,2});
    inlet = inlet.reshape({-1,2});
    outlet = outlet.reshape({-1,2});
    wall = wall.reshape({-1,2});

    auto u_inlet = torch::full({cfInlet.size()}, vel);
    auto v_inlet = torch::full({cfInlet.size()}, 0.0);

    meshTorch.requires_grad_(true);
    inlet.requires_grad_(true);
    outlet.requires_grad_(true);
    wall.requires_grad_(true);

    // 数据损失点1
    auto dataPosi = torch::full({9,1,2}, 0.0);
    dataPosi[0] = torch::tensor({-0.02 , 0.012}).reshape({1,2});
    dataPosi[1] = torch::tensor({-0.018, 0.012}).reshape({1,2});
    dataPosi[2] = torch::tensor({-0.016, 0.012}).reshape({1,2});
    dataPosi[3] = torch::tensor({-0.014, 0.012}).reshape({1,2});
    dataPosi[4] = torch::tensor({-0.012, 0.012}).reshape({1,2});
    dataPosi[5] = torch::tensor({-0.01 , 0.012}).reshape({1,2});
    dataPosi[6] = torch::tensor({0.0   , 0.012}).reshape({1,2});
    dataPosi[7] = torch::tensor({0.1   , 0.012}).reshape({1,2});
    dataPosi[8] = torch::tensor({0.2   , 0.012}).reshape({1,2});
    dataPosi = dataPosi.reshape({-1,2});

    if (torch::cuda::is_available())
    {
        std::cout<< "Running on GPU" << std::endl;
        //
    }
    else
    {
        std::cout<< "Running on CPU" << std::endl;
    }

    for (int i = 0; i < iter; i++) 
    {
        opti.zero_grad();

        auto UP = net->forward(meshTorch);
        auto UPinlet = net->forward(inlet);
        auto UPoutlet = net->forward(outlet);
        auto UPwall = net->forward(wall);
        auto UPdata = net->forward(dataPosi);

        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_inlet = UPinlet.index({torch::indexing::Slice(), 0});
        auto v_pred_inlet = UPinlet.index({torch::indexing::Slice(), 1}); 
        auto p_pred_inlet = UPinlet.index({torch::indexing::Slice(), 2});  

        auto u_pred_outlet = UPoutlet.index({torch::indexing::Slice(), 0});
        auto v_pred_outlet = UPoutlet.index({torch::indexing::Slice(), 1}); 
        auto p_pred_outlet = UPoutlet.index({torch::indexing::Slice(), 2});  
        
        auto u_pred_wall = UPwall.index({torch::indexing::Slice(), 0});
        auto v_pred_wall = UPwall.index({torch::indexing::Slice(), 1}); 
        auto p_pred_wall = UPwall.index({torch::indexing::Slice(), 2});  

        auto u_pred_data = UPdata.index({torch::indexing::Slice(), 0});
        auto v_pred_data = UPdata.index({torch::indexing::Slice(), 1}); 
        auto p_pred_data = UPdata.index({torch::indexing::Slice(), 2});  

        auto dpdMesh = 
            torch::autograd::grad
            (
                {p_pred},
                {meshTorch},
                {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},
                {meshTorch},
                {torch::ones_like(dpdx)},
                true,
                true
            )[0];
        auto dpdyMesh = 
            torch::autograd::grad
            (
                {dpdy},
                {meshTorch},
                {torch::ones_like(dpdy)},
                true,
                true
            )[0];
        auto dpdxx = dpdxMesh.index({torch::indexing::Slice(), 0});
        auto dpdyy = dpdyMesh.index({torch::indexing::Slice(), 1});

        auto dudMesh = 
            torch::autograd::grad
            (
                {u_pred},
                {meshTorch},
                {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},
                {meshTorch},
                {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},
                {meshTorch},
                {torch::ones_like(dudx)},
                true,
                true
            )[0];
        auto dudxx = dudxMesh.index({torch::indexing::Slice(), 0});

        auto dudyMesh = 
            torch::autograd::grad
            (
                {dudy},
                {meshTorch},
                {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},
                {meshTorch},
                {torch::ones_like(dvdx)},
                true,
                true
            )[0];
        auto dvdyMesh = 
            torch::autograd::grad
            (
                {dvdy},
                {meshTorch},
                {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 dpdWall = 
            torch::autograd::grad
            (
                {p_pred_wall},
                {wall},
                {torch::ones_like(p_pred_wall)},
                true,
                true
            )[0];
        auto dpdx_wall = dpdWall.index({torch::indexing::Slice(), 0});
        auto dpdy_wall = dpdWall.index({torch::indexing::Slice(), 1});

        auto dpdInlet = 
            torch::autograd::grad
            (
                {p_pred_inlet},
                {inlet},
                {torch::ones_like(p_pred_inlet)},
                true,
                true
            )[0];
        auto dpdx_inlet = dpdInlet.index({torch::indexing::Slice(), 0});

        auto dudOutlet = 
            torch::autograd::grad
            (
                {u_pred_outlet},
                {outlet},
                {torch::ones_like(u_pred_outlet)},
                true,
                true
            )[0];
        auto dudx_outlet = dudOutlet.index({torch::indexing::Slice(), 0});

        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 = 100*crit(cont1, 0*cont1);
        auto loss_mom1 = crit(mom1, 0*mom1);
        auto loss_mom2 = crit(mom2, 0*mom2);
    
        auto loss_wall = 
            crit(u_pred_wall, 0*u_pred_wall)
          + crit(v_pred_wall, 0*v_pred_wall)
          + crit(dpdx_wall*nlx + dpdy_wall*nly, 0*dpdx_wall);

        auto loss_inlet = 
            crit(u_pred_inlet, u_inlet) 
          + crit(v_pred_inlet, v_inlet)
          + crit(dpdx_inlet, 0*dpdx_inlet);

        auto loss_outlet = 
            crit(p_pred_outlet, 0*p_pred_outlet) 
          + crit(dudx_outlet, 0*dudx_outlet);

        // 数据损失点2
        auto u_data = torch::clone(u_pred_data);
        u_data[0] = 0.100576; 
        u_data[1] = 0.105416; 
        u_data[2] = 0.119608;
        u_data[3] = 0.126239;
        u_data[4] = 0.136212;
        u_data[5] = 0.139621;
        u_data[6] = 0.144101;
        u_data[7] = 0.059066;
        u_data[8] = 0.059812;

        auto v_data = torch::clone(v_pred_data);
        v_data[0] = 0.00104886;
        v_data[1] = 0.00234075;
        v_data[2] = 0.00260724;
        v_data[3] = 0.00198026;
        v_data[4] = 6.03697e-05;
        v_data[5] = -0.00107153;
        v_data[6] = -0.0160208;
        v_data[7] = -0.00029105;
        v_data[8] = -0.00081997;

        auto p_data = torch::clone(p_pred_data);
        p_data[0] = 0.136748;
        p_data[1] = 0.138309;
        p_data[2] = 0.138134;
        p_data[3] = 0.136637;
        p_data[4] = 0.132631;
        p_data[5] = 0.130444;
        p_data[6] = 0.112201;
        p_data[7] = 0.066805;
        p_data[8] = 0.042315;

        auto loss_data = 
            w_data*(crit(u_pred_data, u_data) + crit(v_pred_data, v_data));
        auto loss_boundary = w_bd*(loss_inlet + loss_wall + loss_outlet);
        auto loss_pde = w_pde*(loss_cont1 + loss_mom1 + loss_mom2); 

        auto loss = loss_pde + loss_boundary + loss_data;
        
        loss.backward();
        opti.step();
        
        if (i % 100 == 0)
        {
            std::cout<< i
                << ": pde: " << loss_pde.item<double>()
                << ": data: " << loss_data.item<double>()
                << ", boundary: " << loss_boundary.item<double>()
                << ", cont: " << loss_cont1.item<double>()
                << ", momx: " << loss_mom1.item<double>()
                << ", momy: " << loss_mom2.item<double>()
                << ", All: " << loss.item<double>() 
                << std::endl;

            std::ofstream meshTorchfile("mesh");
            meshTorchfile << meshTorch << "\n";

            std::ofstream ufile("up");
            ufile << UP << "\n";
            torch::save(net, "net.pth");
        }
    }

    return 0;
}

该代码运行后,会自动输出结果文件。可以参考ML: PINN代码实例-顶盖驱动流 (NS方程) 来生成图片。相应的结果如下:

_images/cavity1.jpeg

这个算例训练了非常长的时间,几点特征:

  • 如果采用原生的NS方程,且采用FVM同样的网格点,FVM大概2秒算完,PINN训练大约需要30小时(GPU),且还没有训练结束。如果采用CPU训练,会特别慢;

  • 建议调用拓展的NS方程,例如将连续性方程转化为流动的势函数,研究能否加速训练;

  • 建议生成适配pitzDaily网格的配点,研究能否加速训练;