Files
mfem/fem/integ/bilininteg_diffusion_pa.cpp
T

415 lines
12 KiB
C++

// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "../bilininteg.hpp"
#include "../gridfunc.hpp"
#include "../qfunction.hpp"
#include "../../mesh/nurbs.hpp"
#include "../ceed/integrators/diffusion/diffusion.hpp"
#include "bilininteg_diffusion_kernels.hpp"
namespace mfem
{
void DiffusionIntegrator::AssembleDiagonalPA(Vector &diag)
{
if (DeviceCanUseCeed())
{
ceedOp->GetDiagonal(diag);
}
else
{
if (pa_data.Size() == 0) { AssemblePA(*fespace); }
const Array<real_t> &B = maps->B;
const Array<real_t> &G = maps->G;
const Vector &Dv = pa_data;
DiagonalPAKernels::Run(dim, dofs1D, quad1D, ne, symmetric, B, G, Dv,
diag, dofs1D, quad1D);
}
}
// PA Diffusion Apply kernel
void DiffusionIntegrator::AddMultPA(const Vector &x, Vector &y) const
{
if (DeviceCanUseCeed())
{
ceedOp->AddMult(x, y);
}
else
{
const Array<real_t> &B = maps->B;
const Array<real_t> &G = maps->G;
const Array<real_t> &Bt = maps->Bt;
const Array<real_t> &Gt = maps->Gt;
const Vector &Dv = pa_data;
#ifdef MFEM_USE_OCCA
if (DeviceCanUseOcca())
{
if (dim == 2)
{
internal::OccaPADiffusionApply2D(dofs1D,quad1D,ne,B,G,Bt,Gt,Dv,x,y);
return;
}
if (dim == 3)
{
internal::OccaPADiffusionApply3D(dofs1D,quad1D,ne,B,G,Bt,Gt,Dv,x,y);
return;
}
MFEM_ABORT("OCCA PADiffusionApply unknown kernel!");
}
#endif // MFEM_USE_OCCA
ApplyPAKernels::Run(dim, dofs1D, quad1D, ne, symmetric, B, G, Bt,
Gt, Dv, x, y, dofs1D, quad1D);
}
}
void DiffusionIntegrator::AddMultTransposePA(const Vector &x, Vector &y) const
{
if (symmetric)
{
AddMultPA(x, y);
}
else
{
MFEM_ABORT("DiffusionIntegrator::AddMultTransposePA only implemented in "
"the symmetric case.")
}
}
void DiffusionIntegrator::AssemblePA(const FiniteElementSpace &fes)
{
const MemoryType mt = (pa_mt == MemoryType::DEFAULT) ?
Device::GetDeviceMemoryType() : pa_mt;
// Assuming the same element type
fespace = &fes;
Mesh *mesh = fes.GetMesh();
const FiniteElement &el = *fes.GetTypicalFE();
const IntegrationRule *ir = IntRule ? IntRule : &GetRule(el, el);
if (DeviceCanUseCeed())
{
delete ceedOp;
MFEM_VERIFY(!VQ && !MQ,
"Only scalar coefficient supported for DiffusionIntegrator"
" with libCEED");
const bool mixed = mesh->GetNumGeometries(mesh->Dimension()) > 1 ||
fes.IsVariableOrder();
if (mixed)
{
ceedOp = new ceed::MixedPADiffusionIntegrator(*this, fes, Q);
}
else
{
ceedOp = new ceed::PADiffusionIntegrator(fes, *ir, Q);
}
return;
}
const int dims = el.GetDim();
const int symmDims = (dims * (dims + 1)) / 2; // 1x1: 1, 2x2: 3, 3x3: 6
const int nq = ir->GetNPoints();
dim = mesh->Dimension();
ne = fes.GetNE();
geom = mesh->GetGeometricFactors(*ir, GeometricFactors::JACOBIANS, mt);
const int sdim = mesh->SpaceDimension();
maps = &el.GetDofToQuad(*ir, DofToQuad::TENSOR);
dofs1D = maps->ndof;
quad1D = maps->nqpt;
QuadratureSpace qs(*mesh, *ir);
CoefficientVector coeff(qs, CoefficientStorage::COMPRESSED);
if (MQ) { coeff.ProjectTranspose(*MQ); }
else if (VQ) { coeff.Project(*VQ); }
else if (Q) { coeff.Project(*Q); }
else { coeff.SetConstant(1.0); }
const int coeff_dim = coeff.GetVDim();
symmetric = (coeff_dim != dims*dims);
const int pa_size = symmetric ? symmDims : dims*dims;
pa_data.SetSize(pa_size * nq * ne, mt);
internal::PADiffusionSetup(dim, sdim, dofs1D, quad1D, coeff_dim, ne,
ir->GetWeights(), geom->J, coeff, pa_data);
}
void DiffusionIntegrator::AssembleNURBSPA(const FiniteElementSpace &fes)
{
fespace = &fes;
Mesh *mesh = fes.GetMesh();
dim = mesh->Dimension();
MFEM_VERIFY(3 == dim, "Only 3D so far");
numPatches = mesh->NURBSext->GetNP();
for (int p=0; p<numPatches; ++p)
{
AssemblePatchPA(p, fes);
}
}
void DiffusionIntegrator::AssemblePatchPA(const int patch,
const FiniteElementSpace &fes)
{
Mesh *mesh = fes.GetMesh();
SetupPatchBasisData(mesh, patch);
SetupPatchPA(patch, mesh); // For full quadrature, unitWeights = false
}
void DiffusionIntegrator::AddAbsMultPA(const Vector &x, Vector &y) const
{
if (DeviceCanUseCeed())
{
MFEM_ABORT("Ceed AbsMult not implemented yet");
}
Vector abs_pa_data(pa_data);
abs_pa_data.Abs();
auto abs_maps = maps->Abs();
ApplyPAKernels::Run(dim, dofs1D, quad1D, ne, symmetric,
abs_maps.B, abs_maps.G, abs_maps.Bt, abs_maps.Gt,
abs_pa_data, x, y, dofs1D, quad1D);
}
void DiffusionIntegrator::AddAbsMultTransposePA(const Vector &x,
Vector &y) const
{
if (symmetric)
{
AddAbsMultPA(x, y);
}
else
{
MFEM_ABORT("DiffusionIntegrator::AddAbsMultTransposePA only implemented "
"in the symmetric case.")
}
}
// This version uses full 1D quadrature rules, taking into account the
// minimum interaction between basis functions and integration points.
void DiffusionIntegrator::AddMultPatchPA(const int patch, const Vector &x,
Vector &y) const
{
MFEM_VERIFY(3 == dim, "Only 3D so far");
const Array<int>& Q1D = pQ1D[patch];
const Array<int>& D1D = pD1D[patch];
const std::vector<Array2D<real_t>>& B = pB[patch];
const std::vector<Array2D<real_t>>& G = pG[patch];
const IntArrayVar2D& minD = pminD[patch];
const IntArrayVar2D& maxD = pmaxD[patch];
const IntArrayVar2D& minQ = pminQ[patch];
const IntArrayVar2D& maxQ = pmaxQ[patch];
auto X = Reshape(x.Read(), D1D[0], D1D[1], D1D[2]);
auto Y = Reshape(y.ReadWrite(), D1D[0], D1D[1], D1D[2]);
const auto qd = Reshape(pa_data.Read(), Q1D[0]*Q1D[1]*Q1D[2],
(symmetric ? 6 : 9));
// NOTE: the following is adapted from AssemblePatchMatrix_fullQuadrature
std::vector<Array3D<real_t>> grad(dim);
// TODO: Can an optimal order of dimensions be determined, for each patch?
Array3D<real_t> gradXY(3, std::max(Q1D[0], D1D[0]), std::max(Q1D[1], D1D[1]));
Array2D<real_t> gradX(3, std::max(Q1D[0], D1D[0]));
for (int d=0; d<dim; ++d)
{
grad[d].SetSize(Q1D[0], Q1D[1], Q1D[2]);
for (int qz = 0; qz < Q1D[2]; ++qz)
{
for (int qy = 0; qy < Q1D[1]; ++qy)
{
for (int qx = 0; qx < Q1D[0]; ++qx)
{
grad[d](qx,qy,qz) = 0.0;
}
}
}
}
for (int dz = 0; dz < D1D[2]; ++dz)
{
for (int qy = 0; qy < Q1D[1]; ++qy)
{
for (int qx = 0; qx < Q1D[0]; ++qx)
{
for (int d=0; d<dim; ++d)
{
gradXY(d,qx,qy) = 0.0;
}
}
}
for (int dy = 0; dy < D1D[1]; ++dy)
{
for (int qx = 0; qx < Q1D[0]; ++qx)
{
gradX(0,qx) = 0.0;
gradX(1,qx) = 0.0;
}
for (int dx = 0; dx < D1D[0]; ++dx)
{
const real_t s = X(dx,dy,dz);
for (int qx = minD[0][dx]; qx <= maxD[0][dx]; ++qx)
{
gradX(0,qx) += s * B[0](qx,dx);
gradX(1,qx) += s * G[0](qx,dx);
}
}
for (int qy = minD[1][dy]; qy <= maxD[1][dy]; ++qy)
{
const real_t wy = B[1](qy,dy);
const real_t wDy = G[1](qy,dy);
// This full range of qx values is generally necessary.
for (int qx = 0; qx < Q1D[0]; ++qx)
{
const real_t wx = gradX(0,qx);
const real_t wDx = gradX(1,qx);
gradXY(0,qx,qy) += wDx * wy;
gradXY(1,qx,qy) += wx * wDy;
gradXY(2,qx,qy) += wx * wy;
}
}
}
for (int qz = minD[2][dz]; qz <= maxD[2][dz]; ++qz)
{
const real_t wz = B[2](qz,dz);
const real_t wDz = G[2](qz,dz);
for (int qy = 0; qy < Q1D[1]; ++qy)
{
for (int qx = 0; qx < Q1D[0]; ++qx)
{
grad[0](qx,qy,qz) += gradXY(0,qx,qy) * wz;
grad[1](qx,qy,qz) += gradXY(1,qx,qy) * wz;
grad[2](qx,qy,qz) += gradXY(2,qx,qy) * wDz;
}
}
}
}
for (int qz = 0; qz < Q1D[2]; ++qz)
{
for (int qy = 0; qy < Q1D[1]; ++qy)
{
for (int qx = 0; qx < Q1D[0]; ++qx)
{
const int q = qx + ((qy + (qz * Q1D[1])) * Q1D[0]);
const real_t O00 = qd(q,0);
const real_t O01 = qd(q,1);
const real_t O02 = qd(q,2);
const real_t O10 = symmetric ? O01 : qd(q,3);
const real_t O11 = symmetric ? qd(q,3) : qd(q,4);
const real_t O12 = symmetric ? qd(q,4) : qd(q,5);
const real_t O20 = symmetric ? O02 : qd(q,6);
const real_t O21 = symmetric ? O12 : qd(q,7);
const real_t O22 = symmetric ? qd(q,5) : qd(q,8);
const real_t grad0 = grad[0](qx,qy,qz);
const real_t grad1 = grad[1](qx,qy,qz);
const real_t grad2 = grad[2](qx,qy,qz);
grad[0](qx,qy,qz) = (O00*grad0)+(O01*grad1)+(O02*grad2);
grad[1](qx,qy,qz) = (O10*grad0)+(O11*grad1)+(O12*grad2);
grad[2](qx,qy,qz) = (O20*grad0)+(O21*grad1)+(O22*grad2);
} // qx
} // qy
} // qz
for (int qz = 0; qz < Q1D[2]; ++qz)
{
for (int dy = 0; dy < D1D[1]; ++dy)
{
for (int dx = 0; dx < D1D[0]; ++dx)
{
for (int d=0; d<3; ++d)
{
gradXY(d,dx,dy) = 0.0;
}
}
}
for (int qy = 0; qy < Q1D[1]; ++qy)
{
for (int dx = 0; dx < D1D[0]; ++dx)
{
for (int d=0; d<3; ++d)
{
gradX(d,dx) = 0.0;
}
}
for (int qx = 0; qx < Q1D[0]; ++qx)
{
const real_t gX = grad[0](qx,qy,qz);
const real_t gY = grad[1](qx,qy,qz);
const real_t gZ = grad[2](qx,qy,qz);
for (int dx = minQ[0][qx]; dx <= maxQ[0][qx]; ++dx)
{
const real_t wx = B[0](qx,dx);
const real_t wDx = G[0](qx,dx);
gradX(0,dx) += gX * wDx;
gradX(1,dx) += gY * wx;
gradX(2,dx) += gZ * wx;
}
}
for (int dy = minQ[1][qy]; dy <= maxQ[1][qy]; ++dy)
{
const real_t wy = B[1](qy,dy);
const real_t wDy = G[1](qy,dy);
for (int dx = 0; dx < D1D[0]; ++dx)
{
gradXY(0,dx,dy) += gradX(0,dx) * wy;
gradXY(1,dx,dy) += gradX(1,dx) * wDy;
gradXY(2,dx,dy) += gradX(2,dx) * wy;
}
}
}
for (int dz = minQ[2][qz]; dz <= maxQ[2][qz]; ++dz)
{
const real_t wz = B[2](qz,dz);
const real_t wDz = G[2](qz,dz);
for (int dy = 0; dy < D1D[1]; ++dy)
{
for (int dx = 0; dx < D1D[0]; ++dx)
{
Y(dx,dy,dz) +=
((gradXY(0,dx,dy) * wz) +
(gradXY(1,dx,dy) * wz) +
(gradXY(2,dx,dy) * wDz));
}
}
} // dz
} // qz
}
void DiffusionIntegrator::AddMultNURBSPA(const Vector &x, Vector &y) const
{
Vector xp, yp;
for (int p=0; p<numPatches; ++p)
{
Array<int> vdofs;
fespace->GetPatchVDofs(p, vdofs);
x.GetSubVector(vdofs, xp);
yp.SetSize(vdofs.Size());
yp = 0.0;
AddMultPatchPA(p, xp, yp);
y.AddElementVector(vdofs, yp);
}
}
} // namespace mfem