// 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 "../config/config.hpp" #ifdef MFEM_USE_MPI #include "fem.hpp" #include "../general/forall.hpp" namespace mfem { ParNonlinearForm::ParNonlinearForm(ParFiniteElementSpace *pf) : NonlinearForm(pf), pGrad(Operator::Hypre_ParCSR) { X.MakeRef(pf, NULL); Y.MakeRef(pf, NULL); MFEM_VERIFY(!Serial(), "internal MFEM error"); } real_t ParNonlinearForm::GetParGridFunctionEnergy(const Vector &x) const { real_t loc_energy, glob_energy; loc_energy = GetGridFunctionEnergy(x); if (fnfi.Size()) { MFEM_ABORT("TODO: add energy contribution from shared faces"); } MPI_Allreduce(&loc_energy, &glob_energy, 1, MPITypeMap::mpi_type, MPI_SUM, ParFESpace()->GetComm()); return glob_energy; } void ParNonlinearForm::Mult(const Vector &x, Vector &y) const { NonlinearForm::Mult(x, y); // x --(P)--> aux1 --(A_local)--> aux2 if (fnfi.Size()) { MFEM_VERIFY(!NonlinearForm::ext, "Not implemented (extensions + faces"); // Terms over shared interior faces in parallel. ParFiniteElementSpace *pfes = ParFESpace(); ParMesh *pmesh = pfes->GetParMesh(); FaceElementTransformations *tr; const FiniteElement *fe1, *fe2; Array vdofs1, vdofs2; Vector el_x, el_y; aux1.HostReadWrite(); X.MakeRef(aux1, 0); // aux1 contains P.x X.ExchangeFaceNbrData(); const int n_shared_faces = pmesh->GetNSharedFaces(); for (int i = 0; i < n_shared_faces; i++) { tr = pmesh->GetSharedFaceTransformations(i, true); int Elem2NbrNo = tr->Elem2No - pmesh->GetNE(); fe1 = pfes->GetFE(tr->Elem1No); fe2 = pfes->GetFaceNbrFE(Elem2NbrNo); pfes->GetElementVDofs(tr->Elem1No, vdofs1); pfes->GetFaceNbrElementVDofs(Elem2NbrNo, vdofs2); el_x.SetSize(vdofs1.Size() + vdofs2.Size()); X.GetSubVector(vdofs1, el_x.GetData()); X.FaceNbrData().GetSubVector(vdofs2, el_x.GetData() + vdofs1.Size()); for (int k = 0; k < fnfi.Size(); k++) { fnfi[k]->AssembleFaceVector(*fe1, *fe2, *tr, el_x, el_y); aux2.AddElementVector(vdofs1, el_y.GetData()); } } } P->MultTranspose(aux2, y); const int N = ess_tdof_list.Size(); const auto idx = ess_tdof_list.Read(); auto Y_RW = y.ReadWrite(); mfem::forall(N, [=] MFEM_HOST_DEVICE (int i) { Y_RW[idx[i]] = 0.0; }); } const SparseMatrix &ParNonlinearForm::GetLocalGradient(const Vector &x) const { MFEM_VERIFY(NonlinearForm::ext == nullptr, "this method is not supported yet with partial assembly"); NonlinearForm::GetGradient(x); // (re)assemble Grad, no b.c. return *Grad; } void ParNonlinearForm::GradientSharedFaces(const Vector &x, int skip_zeros) const { ParFiniteElementSpace *pfes = ParFESpace(); ParMesh *pmesh = pfes->GetParMesh(); FaceElementTransformations *T; Array vdofs1, vdofs2, vdofs_all; DenseMatrix elemmat; Vector el_x, nbr_x, face_x; const Vector &px = Prolongate(x); ParGridFunction pgf(pfes, const_cast(px), 0); pgf.ExchangeFaceNbrData(); int nfaces = pmesh->GetNSharedFaces(); for (int i = 0; i < nfaces; i++) { T = pmesh->GetSharedFaceTransformations(i); int Elem2NbrNo = T->Elem2No - pmesh->GetNE(); pfes->GetElementVDofs(T->Elem1No, vdofs1); pfes->GetFaceNbrElementVDofs(Elem2NbrNo, vdofs2); face_x.SetSize(vdofs1.Size() + vdofs2.Size()); el_x.MakeRef(face_x, 0, vdofs1.Size()); pgf.GetSubVector(vdofs1, el_x); nbr_x.MakeRef(face_x, vdofs1.Size(), vdofs2.Size()); pgf.FaceNbrData().GetSubVector(vdofs2, nbr_x); vdofs1.Copy(vdofs_all); for (int j = 0; j < vdofs2.Size(); j++) { if (vdofs2[j] >= 0) { vdofs2[j] += height; } else { vdofs2[j] -= height; } } vdofs_all.Append(vdofs2); for (int k = 0; k < fnfi.Size(); k++) { fnfi[k]->AssembleFaceGrad(*pfes->GetFE(T->Elem1No), *pfes->GetFaceNbrFE(Elem2NbrNo), *T, face_x, elemmat); Grad->AddSubMatrix(vdofs1, vdofs_all, elemmat, skip_zeros); } } } Operator &ParNonlinearForm::GetGradient(const Vector &x) const { if (NonlinearForm::ext) { return NonlinearForm::GetGradient(x); } ParFiniteElementSpace *pfes = ParFESpace(); pGrad.Clear(); OperatorHandle dA(pGrad.Type()), Ph(pGrad.Type()), hdA; if (fnfi.Size()) { const int skip_zeros = 0; pfes->ExchangeFaceNbrData(); if (Grad == NULL) { int nbr_size = pfes->GetFaceNbrVSize(); Grad = new SparseMatrix(pfes->GetVSize(), pfes->GetVSize() + nbr_size); } NonlinearForm::GetGradient(x, false); // (re)assemble Grad, no b.c. GradientSharedFaces(x, skip_zeros); Grad->Finalize(skip_zeros); // handle the case when 'a' contains off-diagonal int lvsize = pfes->GetVSize(); const HYPRE_BigInt *face_nbr_glob_ldof = pfes->GetFaceNbrGlobalDofMap(); HYPRE_BigInt ldof_offset = pfes->GetMyDofOffset(); Array glob_J(Grad->NumNonZeroElems()); int *J = Grad->GetJ(); for (int i = 0; i < glob_J.Size(); i++) { if (J[i] < lvsize) { glob_J[i] = J[i] + ldof_offset; } else { glob_J[i] = face_nbr_glob_ldof[J[i] - lvsize]; } } // TODO - construct dA directly in the A format hdA.Reset( new HypreParMatrix(pfes->GetComm(), lvsize, pfes->GlobalVSize(), pfes->GlobalVSize(), Grad->GetI(), glob_J, Grad->GetData(), pfes->GetDofOffsets(), pfes->GetDofOffsets())); // - hdA owns the new HypreParMatrix // - the above constructor copies all input arrays glob_J.DeleteAll(); dA.ConvertFrom(hdA); } else { NonlinearForm::GetGradient(x); // (re)assemble Grad, no b.c. dA.MakeSquareBlockDiag(pfes->GetComm(), pfes->GlobalVSize(), pfes->GetDofOffsets(), Grad); } // RAP the local gradient dA. // TODO - construct Dof_TrueDof_Matrix directly in the pGrad format Ph.ConvertFrom(pfes->Dof_TrueDof_Matrix()); pGrad.MakePtAP(dA, Ph); // Impose b.c. on pGrad OperatorHandle pGrad_e; pGrad_e.EliminateRowsCols(pGrad, ess_tdof_list); return *pGrad.Ptr(); } void ParNonlinearForm::Update() { Y.MakeRef(ParFESpace(), NULL); X.MakeRef(ParFESpace(), NULL); pGrad.Clear(); NonlinearForm::Update(); } ParBlockNonlinearForm::ParBlockNonlinearForm(Array &pf) : BlockNonlinearForm() { pBlockGrad = NULL; SetParSpaces(pf); } void ParBlockNonlinearForm::SetParSpaces(Array &pf) { delete pBlockGrad; pBlockGrad = NULL; for (int s1=0; s1 serialSpaces(pf.Size()); for (int s=0; s*> &bdr_attr_is_ess, Array &rhs) { Array nullarray(fes.Size()); nullarray = NULL; BlockNonlinearForm::SetEssentialBC(bdr_attr_is_ess, nullarray); for (int s = 0; s < fes.Size(); ++s) { if (rhs[s]) { rhs[s]->SetSubVector(*ess_tdofs[s], 0.0); } } } void ParBlockNonlinearForm::SetEssentialTrueDofs( const Array*> &ess_tdof_list, Array &rhs) { Array nullarray(fes.Size()); nullarray = nullptr; BlockNonlinearForm::SetEssentialTrueDofs(ess_tdof_list, nullarray); for (int s = 0; s < fes.Size(); ++s) { if (rhs[s]) { rhs[s]->SetSubVector(*ess_tdofs[s], 0.0); } } } real_t ParBlockNonlinearForm::GetEnergy(const Vector &x) const { // xs_true is not modified, so const_cast is okay xs_true.Update(const_cast(x), block_trueOffsets); xs.Update(block_offsets); for (int s = 0; s < fes.Size(); ++s) { fes[s]->GetProlongationMatrix()->Mult(xs_true.GetBlock(s), xs.GetBlock(s)); } real_t enloc = BlockNonlinearForm::GetEnergyBlocked(xs); real_t englo = 0.0; MPI_Allreduce(&enloc, &englo, 1, MPITypeMap::mpi_type, MPI_SUM, ParFESpace(0)->GetComm()); return englo; } void ParBlockNonlinearForm::Mult(const Vector &x, Vector &y) const { // xs_true is not modified, so const_cast is okay xs_true.Update(const_cast(x), block_trueOffsets); ys_true.Update(y, block_trueOffsets); xs.Update(block_offsets); ys.Update(block_offsets); for (int s=0; sGetProlongationMatrix()->Mult( xs_true.GetBlock(s), xs.GetBlock(s)); } BlockNonlinearForm::MultBlocked(xs, ys); if (fnfi.Size() > 0) { // Terms over shared interior faces in parallel. ParMesh *pmesh = ParFESpace(0)->GetParMesh(); FaceElementTransformations *tr; Array *>vdofs(fes.Size()); Array *>vdofs2(fes.Size()); Array el_x(fes.Size()); Array el_x_const(fes.Size()); Array el_y(fes.Size()); Array fe(fes.Size()); Array fe2(fes.Size()); Array pgfs(fes.Size()); for (int s=0; s; vdofs2[s] = new Array; pgfs[s] = new ParGridFunction(const_cast(ParFESpace(s)), xs.GetBlock(s)); pgfs[s]->ExchangeFaceNbrData(); } const int n_shared_faces = pmesh->GetNSharedFaces(); for (int i = 0; i < n_shared_faces; i++) { tr = pmesh->GetSharedFaceTransformations(i, true); int Elem2NbrNo = tr->Elem2No - pmesh->GetNE(); for (int s=0; sGetFE(tr->Elem1No); fe2[s] = pfes->GetFaceNbrFE(Elem2NbrNo); pfes->GetElementVDofs(tr->Elem1No, *(vdofs[s])); pfes->GetFaceNbrElementVDofs(Elem2NbrNo, *(vdofs2[s])); el_x[s]->SetSize(vdofs[s]->Size() + vdofs2[s]->Size()); xs.GetBlock(s).GetSubVector(*(vdofs[s]), el_x[s]->GetData()); pgfs[s]->FaceNbrData().GetSubVector(*(vdofs2[s]), el_x[s]->GetData() + vdofs[s]->Size()); } for (int k = 0; k < fnfi.Size(); ++k) { fnfi[k]->AssembleFaceVector(fe, fe2, *tr, el_x_const, el_y); for (int s=0; sSize() == 0) { continue; } ys.GetBlock(s).AddElementVector(*(vdofs[s]), *el_y[s]); } } } for (int s=0; sGetProlongationMatrix()->MultTranspose( ys.GetBlock(s), ys_true.GetBlock(s)); ys_true.GetBlock(s).SetSubVector(*ess_tdofs[s], 0.0); } ys_true.SyncFromBlocks(); y.SyncMemory(ys_true); } /// Return the local gradient matrix for the given true-dof vector x const BlockOperator & ParBlockNonlinearForm::GetLocalGradient( const Vector &x) const { // xs_true is not modified, so const_cast is okay xs_true.Update(const_cast(x), block_trueOffsets); xs.Update(block_offsets); for (int s=0; sGetProlongationMatrix()->Mult( xs_true.GetBlock(s), xs.GetBlock(s)); } // (re)assemble Grad without b.c. into 'Grads' BlockNonlinearForm::ComputeGradientBlocked(xs); delete BlockGrad; BlockGrad = new BlockOperator(block_offsets); for (int i = 0; i < fes.Size(); ++i) { for (int j = 0; j < fes.Size(); ++j) { BlockGrad->SetBlock(i, j, Grads(i, j)); } } return *BlockGrad; } // Set the operator type id for the parallel gradient matrix/operator. void ParBlockNonlinearForm::SetGradientType(Operator::Type tid) { for (int s1=0; s1SetType(tid); } } } void ParBlockNonlinearForm::GradientSharedFaces(const BlockVector &xs, int skip_zeros) const { // Terms over shared interior faces in parallel. ParMesh *pmesh = ParFESpace(0)->GetParMesh(); FaceElementTransformations *tr; Array *>vdofs(fes.Size()); Array *>vdofs2(fes.Size()); Array *>vdofs_all(fes.Size()); Array el_x(fes.Size()); Array el_x_const(fes.Size()); Array2D elmats(fes.Size(), fes.Size()); Array fe(fes.Size()); Array fe2(fes.Size()); Array pgfs(fes.Size()); for (int s1=0; s1; vdofs2[s1] = new Array; vdofs_all[s1] = new Array; pgfs[s1] = new ParGridFunction( const_cast(ParFESpace(s1)), const_cast(xs.GetBlock(s1))); pgfs[s1]->ExchangeFaceNbrData(); for (int s2=0; s2GetNSharedFaces(); for (int i = 0; i < n_shared_faces; i++) { tr = pmesh->GetSharedFaceTransformations(i, true); int Elem2NbrNo = tr->Elem2No - pmesh->GetNE(); for (int s=0; sGetFE(tr->Elem1No); fe2[s] = pfes->GetFaceNbrFE(Elem2NbrNo); pfes->GetElementVDofs(tr->Elem1No, *(vdofs[s])); pfes->GetFaceNbrElementVDofs(Elem2NbrNo, *(vdofs2[s])); el_x[s]->SetSize(vdofs[s]->Size() + vdofs2[s]->Size()); xs.GetBlock(s).GetSubVector(*(vdofs[s]), el_x[s]->GetData()); pgfs[s]->FaceNbrData().GetSubVector(*(vdofs2[s]), el_x[s]->GetData() + vdofs[s]->Size()); vdofs[s]->Copy(*vdofs_all[s]); const int lvsize = pfes->GetVSize(); for (int j = 0; j < vdofs2[s]->Size(); j++) { if ((*vdofs2[s])[j] >= 0) { (*vdofs2[s])[j] += lvsize; } else { (*vdofs2[s])[j] -= lvsize; } } vdofs_all[s]->Append(*(vdofs2[s])); } for (int k = 0; k < fnfi.Size(); ++k) { fnfi[k]->AssembleFaceGrad(fe, fe2, *tr, el_x_const, elmats); for (int s1=0; s1Height() == 0) { continue; } Grads(s1,s2)->AddSubMatrix(*vdofs[s1], *vdofs_all[s2], *elmats(s1,s2), skip_zeros); } } } } for (int s1=0; s1 pfes(fes.Size()); for (int s1=0; s1Clear(); } } // xs_true is not modified, so const_cast is okay xs_true.Update(const_cast(x), block_trueOffsets); xs.Update(block_offsets); for (int s=0; sGetProlongationMatrix()->Mult( xs_true.GetBlock(s), xs.GetBlock(s)); } if (fnfi.Size() > 0) { const int skip_zeros = 0; for (int s=0; s(pfes[s])->ExchangeFaceNbrData(); } for (int s1=0; s1GetFaceNbrVSize(); Grads(s1,s2) = new SparseMatrix(pfes[s1]->GetVSize(), pfes[s2]->GetVSize() + nbr_size); } } } // (re)assemble Grad without b.c. into 'Grads' BlockNonlinearForm::ComputeGradientBlocked(xs, false); GradientSharedFaces(xs, skip_zeros); // finalize the gradients for (int s1=0; s1Finalize(skip_zeros); } for (int s1=0; s1Type()), Ph(phBlockGrad(s1,s2)->Type()), Rh(phBlockGrad(s1,s2)->Type()); // handle the case when 'a' contains off-diagonal int lvsize = pfes[s2]->GetVSize(); const HYPRE_BigInt *face_nbr_glob_ldof = const_cast(pfes[s2])->GetFaceNbrGlobalDofMap(); HYPRE_BigInt ldof_offset = pfes[s2]->GetMyDofOffset(); Array glob_J(Grads(s1,s2)->NumNonZeroElems()); int *J = Grads(s1,s2)->GetJ(); for (int i = 0; i < glob_J.Size(); i++) { if (J[i] < lvsize) { glob_J[i] = J[i] + ldof_offset; } else { glob_J[i] = face_nbr_glob_ldof[J[i] - lvsize]; } } // TODO - construct dA directly in the A format hdA.Reset( new HypreParMatrix(pfes[s2]->GetComm(), pfes[s1]->GetVSize(), pfes[s1]->GlobalVSize(), pfes[s2]->GlobalVSize(), Grads(s1,s2)->GetI(), glob_J, Grads(s1,s2)->GetData(), pfes[s1]->GetDofOffsets(), pfes[s2]->GetDofOffsets())); // - hdA owns the new HypreParMatrix // - the above constructor copies all input arrays glob_J.DeleteAll(); dA.ConvertFrom(hdA); if (s1 == s2) { Ph.ConvertFrom(pfes[s1]->Dof_TrueDof_Matrix()); phBlockGrad(s1,s1)->MakePtAP(dA, Ph); OperatorHandle Ae; Ae.EliminateRowsCols(*phBlockGrad(s1,s1), *ess_tdofs[s1]); } else { Rh.ConvertFrom(pfes[s1]->Dof_TrueDof_Matrix()); Ph.ConvertFrom(pfes[s2]->Dof_TrueDof_Matrix()); phBlockGrad(s1,s2)->MakeRAP(Rh, dA, Ph); phBlockGrad(s1,s2)->EliminateRows(*ess_tdofs[s1]); phBlockGrad(s1,s2)->EliminateCols(*ess_tdofs[s2]); } pBlockGrad->SetBlock(s1, s2, phBlockGrad(s1,s2)->Ptr()); } } } else { // (re)assemble Grad without b.c. into 'Grads' BlockNonlinearForm::ComputeGradientBlocked(xs); for (int s1=0; s1Type()), Ph(phBlockGrad(s1,s2)->Type()), Rh(phBlockGrad(s1,s2)->Type()); if (s1 == s2) { dA.MakeSquareBlockDiag(pfes[s1]->GetComm(), pfes[s1]->GlobalVSize(), pfes[s1]->GetDofOffsets(), Grads(s1,s1)); Ph.ConvertFrom(pfes[s1]->Dof_TrueDof_Matrix()); phBlockGrad(s1,s1)->MakePtAP(dA, Ph); OperatorHandle Ae; Ae.EliminateRowsCols(*phBlockGrad(s1,s1), *ess_tdofs[s1]); } else { dA.MakeRectangularBlockDiag(pfes[s1]->GetComm(), pfes[s1]->GlobalVSize(), pfes[s2]->GlobalVSize(), pfes[s1]->GetDofOffsets(), pfes[s2]->GetDofOffsets(), Grads(s1,s2)); Rh.ConvertFrom(pfes[s1]->Dof_TrueDof_Matrix()); Ph.ConvertFrom(pfes[s2]->Dof_TrueDof_Matrix()); phBlockGrad(s1,s2)->MakeRAP(Rh, dA, Ph); phBlockGrad(s1,s2)->EliminateRows(*ess_tdofs[s1]); phBlockGrad(s1,s2)->EliminateCols(*ess_tdofs[s2]); } pBlockGrad->SetBlock(s1, s2, phBlockGrad(s1,s2)->Ptr()); } } } return *pBlockGrad; } ParBlockNonlinearForm::~ParBlockNonlinearForm() { delete pBlockGrad; for (int s1=0; s1