// 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 #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 struct QFunction { using matd_t = tensor; 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(); 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(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(dim, load_coeff); glsurf_load = std::make_unique(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 &ess_dofs) { // Set the BC ess_dofs.DeleteAll(); auto cbcc = &bccx; if (j == 1) { cbcc = &bccy; } else if (j == 2) { cbcc = &bccz; } Array 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 &ess_dofs) { // Set the BC ess_tdofv.DeleteAll(); Array ess_tdofx, ess_tdofy, ess_tdofz; for (auto it = bccx.begin(); it != bccx.end(); it++) { Array ess_bdr(pmesh->bdr_attributes.Max()); ess_bdr = 0; ess_bdr[it->first - 1] = 1; Array 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 ess_bdr(pmesh->bdr_attributes.Max()); ess_bdr = 0; ess_bdr[it->first - 1] = 1; Array 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 ess_bdr(pmesh->bdr_attributes.Max()); ess_bdr = 0; ess_bdr[it->first - 1] = 1; Array 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( std::vector {{ U, vfes }}, std::vector { { LCoeff, &Lambda_ps}, { MuCoeff, &Mu_ps}, { Coords, mfes } }, *pmesh); // sample lambda on the integration points Lambda_cv = std::make_unique(*lambda, qs); // sample mu on the integration points Mu_cv = std::make_unique(*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{}, Identity{}, Identity{}, Gradient{}, Weight{} }; const auto output = mfem::future::tuple{ Gradient{} }; 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(Kop); Kc = dynamic_cast(Kop); } else if (dfem) { Operator *Kop; dop->FormSystemOperator(ess_tdofv, Kop); Kh = std::make_unique(Kop); Kc = dynamic_cast(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(*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( &lor_mesh, &lor_fec, 1, Ordering::byNODES); lor_block_offsets[0] = 0; lor_integrator = std::make_unique(*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 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(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; }