Files
mfem/miniapps/mtop/mtop_solvers.cpp
T

550 lines
16 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 <memory>
#include "mtop_solvers.hpp"
using namespace mfem;
using mfem::future::dual;
using mfem::future::tuple;
using mfem::future::tensor;
using mfem::future::Weight;
using mfem::future::Gradient;
using mfem::future::Identity;
///////////////////////////////////////////////////////////////////////////////
/// \brief The QFunction struct defining the linear elasticity operator at
/// integration points which is valid in 2D and 3D
template <int DIM> struct QFunction
{
using matd_t = tensor<real_t, DIM, DIM>;
struct Elasticity
{
MFEM_HOST_DEVICE inline auto operator()(const matd_t &dudxi,
const real_t &L, const real_t &M,
const matd_t &J,
const real_t &w) const
{
const matd_t JxW = transpose(inv(J)) * det(J) * w;
constexpr auto I = mfem::future::IsotropicIdentity<DIM>();
const auto eps = mfem::future::sym(dudxi * mfem::future::inv(J));
return tuple{(L * tr(eps) * I + 2.0 * M * eps) * JxW};
}
};
};
IsoLinElasticSolver::IsoLinElasticSolver(ParMesh *mesh, int vorder,
bool pa, bool dfem):
pmesh(mesh),
pa(pa),
dfem(dfem),
dim(mesh->Dimension()),
spaceDim(mesh->SpaceDimension()),
vfec(new H1_FECollection(vorder, dim)),
vfes(new ParFiniteElementSpace(pmesh, vfec, dim,
// PA Elasticity only implemented for byNODES ordering
pa||dfem ? Ordering::byNODES : Ordering::byVDIM)),
sol(vfes->GetTrueVSize()),
adj(vfes->GetTrueVSize()),
rhs(vfes->GetTrueVSize()),
fdisp(vfes),
adisp(vfes),
prec(nullptr),
ls(nullptr),
lor_block_offsets(dim + 1),
lvforce(nullptr),
volforce(nullptr),
E(nullptr),
nu(nullptr),
lambda(nullptr),
mu(nullptr),
bf(nullptr),
fe(vfes->GetFE(0)),
nodes((pmesh->EnsureNodes(),
static_cast<ParGridFunction *>(pmesh->GetNodes()))),
mfes(nodes->ParFESpace()),
ir(IntRules.Get(fe->GetGeomType(),
fe->GetOrder() + fe->GetOrder() + fe->GetDim() - 1)),
qs(*pmesh, ir),
Lambda_ps(*pmesh, ir, 1),
Mu_ps(*pmesh, ir, 1),
lf(nullptr)
{
MFEM_VERIFY(qs.GetSize() == Lambda_ps.GetTrueVSize(),
"QuadratureSpace and ParameterSpace size mismatch");
sol = 0.0;
rhs = 0.0;
adj = 0.0;
fdisp = 0.0;
adisp = 0.0;
SetLinearSolver();
Operator::width = vfes->GetTrueVSize();
Operator::height = vfes->GetTrueVSize();
lcsurf_load = std::make_unique<SurfaceLoad>(dim, load_coeff);
glsurf_load = std::make_unique<SurfaceLoad>(dim, surf_loads);
if (pmesh->attributes.Size() > 0)
{
domain_attributes.SetSize(pmesh->attributes.Max());
domain_attributes = 1;
}
}
IsoLinElasticSolver::~IsoLinElasticSolver()
{
delete prec;
delete ls;
delete bf;
delete lf;
delete vfes;
delete vfec;
delete lvforce;
for (auto it = load_coeff.begin(); it != load_coeff.end(); it++)
{
delete it->second;
}
delete lambda;
delete mu;
}
void IsoLinElasticSolver::SetLinearSolver(real_t rtol,
real_t atol,
int miter)
{
linear_rtol = rtol;
linear_atol = atol;
linear_iter = miter;
}
void IsoLinElasticSolver::AddDispBC(int id, int dir,
real_t val)
{
if (dir == 0)
{
bcx[id] = ConstantCoefficient(val);
AddDispBC(id, dir, bcx[id]);
}
else if (dir == 1)
{
bcy[id] = ConstantCoefficient(val);
AddDispBC(id, dir, bcy[id]);
}
else if (dir == 2)
{
bcz[id] = ConstantCoefficient(val);
AddDispBC(id, dir, bcz[id]);
}
else if (dir == -1)
{
bcx[id] = ConstantCoefficient(val);
bcy[id] = ConstantCoefficient(val);
bcz[id] = ConstantCoefficient(val);
AddDispBC(id, 0, bcx[id]);
AddDispBC(id, 1, bcy[id]);
AddDispBC(id, 2, bcz[id]);
}
else
{
MFEM_ABORT("Invalid BC direction: "
"0(x), 1(y), 2(z), or -1(all), got " << dir);
}
}
void IsoLinElasticSolver::DelDispBC()
{
bccx.clear();
bccy.clear();
bccz.clear();
bcx.clear();
bcy.clear();
bcz.clear();
ess_tdofv.DeleteAll();
}
void IsoLinElasticSolver::AddDispBC(int id, int dir, Coefficient &val)
{
if (dir == 0) { bccx[id] = &val; }
else if (dir == 1) { bccy[id] = &val; }
else if (dir == 2) { bccz[id] = &val; }
else if (dir == -1)
{
bccx[id] = &val;
bccy[id] = &val;
bccz[id] = &val;
}
else
{
MFEM_ABORT("Invalid BC direction: "
"0(x), 1(y), 2(z), or -1(all), got " << dir);
}
if (pmesh->Dimension() == 2) { bccz.clear(); }
}
void IsoLinElasticSolver::SetVolForce(real_t fx, real_t fy, real_t fz)
{
delete lvforce;
Vector ff(dim);
ff(0) = fx;
ff(1) = fy;
if (dim == 3) { ff(2) = fz; }
lvforce = new VectorConstantCoefficient(ff);
volforce = lvforce;
}
void IsoLinElasticSolver::SetVolForce(VectorCoefficient &fv)
{
volforce = &fv;
}
void IsoLinElasticSolver::SetEssTDofs(int j,
ParFiniteElementSpace& scalar_space,
Array<int> &ess_dofs)
{
// Set the BC
ess_dofs.DeleteAll();
auto cbcc = &bccx;
if (j == 1) { cbcc = &bccy; }
else if (j == 2) { cbcc = &bccz; }
Array<int> ess_bdr(pmesh->bdr_attributes.Max()); ess_bdr = 0;
for (auto it = cbcc->begin(); it != cbcc->end(); it++)
{
ess_bdr[it->first - 1] = 1;
}
scalar_space.GetEssentialTrueDofs(ess_bdr,ess_dofs);
}
void IsoLinElasticSolver::SetEssTDofs(Vector &bsol, Array<int> &ess_dofs)
{
// Set the BC
ess_tdofv.DeleteAll();
Array<int> ess_tdofx, ess_tdofy, ess_tdofz;
for (auto it = bccx.begin(); it != bccx.end(); it++)
{
Array<int> ess_bdr(pmesh->bdr_attributes.Max());
ess_bdr = 0;
ess_bdr[it->first - 1] = 1;
Array<int> ess_tdof_list;
vfes->GetEssentialTrueDofs(ess_bdr, ess_tdof_list, 0);
ess_tdofx.Append(ess_tdof_list);
VectorArrayCoefficient pcoeff(dim);
pcoeff.Set(0, it->second, false);
fdisp.ProjectBdrCoefficient(pcoeff, ess_bdr);
}
// copy tdofsx from displacement grid function
{
Vector &vc = fdisp.GetTrueVector();
const int Net = ess_tdofx.Size();
const auto d_vc = vc.Read();
const auto d_ess_tdofx = ess_tdofx.Read();
auto d_bsol = bsol.ReadWrite();
mfem::forall(Net, [=] MFEM_HOST_DEVICE(int ii)
{
d_bsol[d_ess_tdofx[ii]] = d_vc[d_ess_tdofx[ii]];
});
}
ess_tdofx.HostReadWrite(), ess_dofs.HostReadWrite();
ess_dofs.Append(ess_tdofx);
ess_tdofx.DeleteAll();
for (auto it = bccy.begin(); it != bccy.end(); it++)
{
Array<int> ess_bdr(pmesh->bdr_attributes.Max());
ess_bdr = 0;
ess_bdr[it->first - 1] = 1;
Array<int> ess_tdof_list;
vfes->GetEssentialTrueDofs(ess_bdr, ess_tdof_list, 1);
ess_tdofy.Append(ess_tdof_list);
VectorArrayCoefficient pcoeff(dim);
pcoeff.Set(1, it->second, false);
fdisp.ProjectBdrCoefficient(pcoeff, ess_bdr);
}
// copy tdofsy from displacement grid function
{
Vector &vc = fdisp.GetTrueVector();
const int Net = ess_tdofy.Size();
const auto d_vc = vc.Read();
const auto d_ess_tdofy = ess_tdofy.Read();
auto d_bsol = bsol.ReadWrite();
mfem::forall(Net, [=] MFEM_HOST_DEVICE(int ii)
{
d_bsol[d_ess_tdofy[ii]] = d_vc[d_ess_tdofy[ii]];
});
ess_tdofy.HostReadWrite(), ess_dofs.HostReadWrite();
}
ess_dofs.Append(ess_tdofy);
ess_tdofy.DeleteAll();
if (dim == 3)
{
for (auto it = bccz.begin(); it != bccz.end(); it++)
{
Array<int> ess_bdr(pmesh->bdr_attributes.Max());
ess_bdr = 0;
ess_bdr[it->first - 1] = 1;
Array<int> ess_tdof_list;
vfes->GetEssentialTrueDofs(ess_bdr, ess_tdof_list, 2);
ess_tdofz.Append(ess_tdof_list);
VectorArrayCoefficient pcoeff(dim);
pcoeff.Set(2, it->second, false);
fdisp.ProjectBdrCoefficient(pcoeff, ess_bdr);
}
// copy tdofsz from displacement grid function
{
Vector &vc = fdisp.GetTrueVector();
for (int ii = 0; ii < ess_tdofz.Size(); ii++)
{
bsol[ess_tdofz[ii]] = vc[ess_tdofz[ii]];
}
}
ess_dofs.Append(ess_tdofz);
ess_tdofz.DeleteAll();
}
}
void IsoLinElasticSolver::Mult(const Vector &x, Vector &y) const
{
// the rhs x is assumed to have the contribution of the BC set in advance
// the BC values are not modified here
ls->Mult(x, y);
int N = ess_tdofv.Size();
real_t *yp = y.ReadWrite();
const real_t *sp = sol.Read();
const int *ep = ess_tdofv.Read();
mfem::forall(N, [=] MFEM_HOST_DEVICE(int i) { yp[ep[i]] = sp[ep[i]]; });
}
void IsoLinElasticSolver::MultTranspose(const Vector &x,
Vector &y) const
{
// the adjoint rhs is assumed to be corrected for the BC
// K is symmetric
ls->Mult(x, y);
int N = ess_tdofv.Size();
ess_tdofv.Read();
auto yp = y.Write();
const auto ep = ess_tdofv.Read();
mfem::forall(N, [=] MFEM_HOST_DEVICE(int i) { yp[ep[i]] = 0.0; });
}
void IsoLinElasticSolver::Assemble()
{
delete bf; bf=nullptr;
if (dfem)
{
#ifdef MFEM_USE_DOUBLE
// define the differentiable operator
dop = std::make_unique<mfem::future::DifferentiableOperator>(
std::vector<mfem::future::FieldDescriptor> {{ U, vfes }},
std::vector<mfem::future::FieldDescriptor>
{
{ LCoeff, &Lambda_ps},
{ MuCoeff, &Mu_ps},
{ Coords, mfes }
},
*pmesh);
// sample lambda on the integration points
Lambda_cv = std::make_unique<CoefficientVector>(*lambda, qs);
// sample mu on the integration points
Mu_cv = std::make_unique<CoefficientVector>(*mu, qs);
// set the parameters of the differentiable operator
dop->SetParameters({ Lambda_cv.get(), Mu_cv.get(), nodes });
// define the q-function for dimensions 2 and 3
const auto inputs =
mfem::future::tuple{ Gradient<U>{},
Identity<LCoeff>{}, Identity<MuCoeff>{},
Gradient<Coords>{},
Weight{} };
const auto output = mfem::future::tuple{ Gradient<U>{} };
if (2 == spaceDim)
{
typename QFunction<2>::Elasticity e2qf;
dop->AddDomainIntegrator(e2qf, inputs, output, ir, domain_attributes);
}
else if (3 == spaceDim)
{
typename QFunction<3>::Elasticity e3qf;
dop->AddDomainIntegrator(e3qf, inputs, output, ir, domain_attributes);
}
else { MFEM_ABORT("Space dimension not supported"); }
#else
MFEM_ABORT("Differentiable operator is only supported in double precision");
#endif
}
else
{
// define standard bilinear form
bf = new mfem::ParBilinearForm(vfes);
bf->AddDomainIntegrator(new mfem::ElasticityIntegrator(*lambda, *mu));
if (pa) { bf->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL); }
}
// set BC
sol = real_t(0.0);
SetEssTDofs(sol, ess_tdofv);
if (pa)
{
bf->Assemble();
Operator *Kop;
bf->FormSystemOperator(ess_tdofv, Kop);
Kh = std::make_unique<OperatorHandle>(Kop);
Kc = dynamic_cast<mfem::ConstrainedOperator*>(Kop);
}
else if (dfem)
{
Operator *Kop;
dop->FormSystemOperator(ess_tdofv, Kop);
Kh = std::make_unique<OperatorHandle>(Kop);
Kc = dynamic_cast<mfem::ConstrainedOperator*>(Kop);
}
else
{
bf->Assemble(0);
bf->Finalize();
K.reset(bf->ParallelAssemble());
Ke.reset(K->EliminateRowsCols(ess_tdofv));
}
if (ls == nullptr)
{
ls = new CGSolver(pmesh->GetComm());
if (pa || dfem)
{
// PA LOR lor_disc & scalar_lor_fespace setup
lor_disc = std::make_unique<ParLORDiscretization>(*vfes);
ParFiniteElementSpace &lor_space = lor_disc->GetParFESpace();
const FiniteElementCollection &lor_fec = *lor_space.FEColl();
ParMesh &lor_mesh = *lor_space.GetParMesh();
lor_scalar_fespace = std::make_unique<ParFiniteElementSpace>(
&lor_mesh, &lor_fec, 1, Ordering::byNODES);
lor_block_offsets[0] = 0;
lor_integrator = std::make_unique<ElasticityIntegrator>(*lambda, *mu);
lor_integrator->AssemblePA(lor_disc->GetParFESpace());
for (int j = 0; j < dim; j++)
{
auto *block = new ElasticityComponentIntegrator(*lor_integrator, j, j);
// create the LOR matrix and corresponding AMG preconditioners.
lor_bilinear_forms.emplace_back(new ParBilinearForm(lor_scalar_fespace.get()));
lor_bilinear_forms[j]->SetAssemblyLevel(AssemblyLevel::FULL);
lor_bilinear_forms[j]->EnableSparseMatrixSorting(Device::IsEnabled());
lor_bilinear_forms[j]->AddDomainIntegrator(block);
lor_bilinear_forms[j]->Assemble();
// set the essential boundaries
Array<int> ess_tdof_list_block;
SetEssTDofs(j,*lor_scalar_fespace,ess_tdof_list_block);
lor_block.emplace_back(lor_bilinear_forms[j]->ParallelAssemble());
lor_block[j]->EliminateBC(ess_tdof_list_block,
Operator::DiagonalPolicy::DIAG_ONE);
lor_amg_blocks.emplace_back(new HypreBoomerAMG);
lor_amg_blocks[j]->SetStrengthThresh(0.25);
lor_amg_blocks[j]->SetRelaxType(16); // Chebyshev
lor_amg_blocks[j]->SetOperator(*lor_block[j]);
lor_block_offsets[j+1] = lor_amg_blocks[j]->Height();
}
lor_block_offsets.PartialSum();
lor_blockDiag =
std::make_unique<BlockDiagonalPreconditioner>(lor_block_offsets);
for (int i = 0; i < dim; i++)
{
lor_blockDiag->SetDiagonalBlock(i, lor_amg_blocks[i].get());
}
lor_pa_prec.reset(lor_blockDiag.release());
ls->SetPreconditioner(*lor_pa_prec);
}
else
{
prec = new HypreBoomerAMG();
// set the rigid body modes
prec->SetElasticityOptions(vfes);
ls->SetPreconditioner(*prec);
}
ls->SetOperator(((pa||dfem) ? *Kh->Ptr() : *K));
ls->SetPrintLevel(1);
}
else
{
ls->SetOperator((pa||dfem) ? *Kh->Ptr() : *K);
}
}
void IsoLinElasticSolver::FSolve()
{
ls->SetAbsTol(linear_atol);
ls->SetRelTol(linear_rtol);
ls->SetMaxIter(linear_iter);
if (lf == nullptr)
{
lf = new ParLinearForm(vfes);
if (volforce != nullptr)
{
lf->AddDomainIntegrator(new VectorDomainLFIntegrator(*volforce));
}
// add surface loads
lf->AddBoundaryIntegrator(new VectorBoundaryLFIntegrator(*lcsurf_load));
}
(*lf) = real_t(0.0);
if (pa || dfem) { lf->UseFastAssembly(true); }
lf->Assemble();
lf->ParallelAssemble(rhs);
if (pa || dfem) { Kc->EliminateRHS(sol, rhs); }
else
{
K->EliminateBC(*Ke, ess_tdofv, sol, rhs);
}
ls->Mult(rhs, sol);
delete lf;
lf = nullptr;
}