// Copyright (c) 2010-2020, 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 "fem.hpp" namespace mfem { void NonlinearForm::SetAssemblyLevel(AssemblyLevel assembly_level) { if (ext) { MFEM_ABORT("the assembly level has already been set!"); } assembly = assembly_level; switch (assembly) { case AssemblyLevel::NONE: // This is the default behavior. break; case AssemblyLevel::PARTIAL: ext = new PANonlinearFormExtension(this); break; default: mfem_error("Unknown assembly level for this form."); } } void NonlinearForm::SetEssentialBC(const Array &bdr_attr_is_ess, Vector *rhs) { // virtual call, works in parallel too fes->GetEssentialTrueDofs(bdr_attr_is_ess, ess_tdof_list); if (rhs) { for (int i = 0; i < ess_tdof_list.Size(); i++) { (*rhs)(ess_tdof_list[i]) = 0.0; } } } void NonlinearForm::SetEssentialVDofs(const Array &ess_vdofs_list) { if (!P) { ess_vdofs_list.Copy(ess_tdof_list); // ess_vdofs_list --> ess_tdof_list } else { Array ess_vdof_marker, ess_tdof_marker; FiniteElementSpace::ListToMarker(ess_vdofs_list, fes->GetVSize(), ess_vdof_marker); if (Serial()) { fes->ConvertToConformingVDofs(ess_vdof_marker, ess_tdof_marker); } else { #ifdef MFEM_USE_MPI ParFiniteElementSpace *pf = dynamic_cast(fes); ess_tdof_marker.SetSize(pf->GetTrueVSize()); pf->Dof_TrueDof_Matrix()->BooleanMultTranspose(1, ess_vdof_marker, 0, ess_tdof_marker); #else MFEM_ABORT("internal MFEM error"); #endif } FiniteElementSpace::MarkerToList(ess_tdof_marker, ess_tdof_list); } } double NonlinearForm::GetGridFunctionEnergy(const Vector &x) const { Array vdofs; Vector el_x; const FiniteElement *fe; ElementTransformation *T; double energy = 0.0; if (dnfi.Size()) { for (int i = 0; i < fes->GetNE(); i++) { fe = fes->GetFE(i); fes->GetElementVDofs(i, vdofs); T = fes->GetElementTransformation(i); x.GetSubVector(vdofs, el_x); for (int k = 0; k < dnfi.Size(); k++) { energy += dnfi[k]->GetElementEnergy(*fe, *T, el_x); } } } if (fnfi.Size()) { MFEM_ABORT("TODO: add energy contribution from interior face terms"); } if (bfnfi.Size()) { MFEM_ABORT("TODO: add energy contribution from boundary face terms"); } return energy; } const Vector &NonlinearForm::Prolongate(const Vector &x) const { MFEM_VERIFY(x.Size() == Width(), "invalid input Vector size"); if (P) { aux1.SetSize(P->Height()); P->Mult(x, aux1); return aux1; } return x; } void NonlinearForm::Mult(const Vector &x, Vector &y) const { const Vector &px = Prolongate(x); if (P) { aux2.SetSize(P->Height()); } // If we are in parallel, ParNonLinearForm::Mult uses the aux2 vector. // In serial, place the result directly in y. Vector &py = P ? aux2 : y; if (ext) { ext->Mult(px, py); return; } Array vdofs; Vector el_x, el_y; const FiniteElement *fe; ElementTransformation *T; Mesh *mesh = fes->GetMesh(); py = 0.0; if (dnfi.Size()) { for (int i = 0; i < fes->GetNE(); i++) { fe = fes->GetFE(i); fes->GetElementVDofs(i, vdofs); T = fes->GetElementTransformation(i); px.GetSubVector(vdofs, el_x); for (int k = 0; k < dnfi.Size(); k++) { dnfi[k]->AssembleElementVector(*fe, *T, el_x, el_y); py.AddElementVector(vdofs, el_y); } } } if (fnfi.Size()) { FaceElementTransformations *tr; const FiniteElement *fe1, *fe2; Array vdofs2; for (int i = 0; i < mesh->GetNumFaces(); i++) { tr = mesh->GetInteriorFaceTransformations(i); if (tr != NULL) { fes->GetElementVDofs(tr->Elem1No, vdofs); fes->GetElementVDofs(tr->Elem2No, vdofs2); vdofs.Append (vdofs2); px.GetSubVector(vdofs, el_x); fe1 = fes->GetFE(tr->Elem1No); fe2 = fes->GetFE(tr->Elem2No); for (int k = 0; k < fnfi.Size(); k++) { fnfi[k]->AssembleFaceVector(*fe1, *fe2, *tr, el_x, el_y); py.AddElementVector(vdofs, el_y); } } } } if (bfnfi.Size()) { FaceElementTransformations *tr; const FiniteElement *fe1, *fe2; // Which boundary attributes need to be processed? Array bdr_attr_marker(mesh->bdr_attributes.Size() ? mesh->bdr_attributes.Max() : 0); bdr_attr_marker = 0; for (int k = 0; k < bfnfi.Size(); k++) { if (bfnfi_marker[k] == NULL) { bdr_attr_marker = 1; break; } Array &bdr_marker = *bfnfi_marker[k]; MFEM_ASSERT(bdr_marker.Size() == bdr_attr_marker.Size(), "invalid boundary marker for boundary face integrator #" << k << ", counting from zero"); for (int i = 0; i < bdr_attr_marker.Size(); i++) { bdr_attr_marker[i] |= bdr_marker[i]; } } for (int i = 0; i < fes -> GetNBE(); i++) { const int bdr_attr = mesh->GetBdrAttribute(i); if (bdr_attr_marker[bdr_attr-1] == 0) { continue; } tr = mesh->GetBdrFaceTransformations (i); if (tr != NULL) { fes->GetElementVDofs(tr->Elem1No, vdofs); px.GetSubVector(vdofs, el_x); fe1 = fes->GetFE(tr->Elem1No); // The fe2 object is really a dummy and not used on the boundaries, // but we can't dereference a NULL pointer, and we don't want to // actually make a fake element. fe2 = fe1; for (int k = 0; k < bfnfi.Size(); k++) { if (bfnfi_marker[k] && (*bfnfi_marker[k])[bdr_attr-1] == 0) { continue; } bfnfi[k]->AssembleFaceVector(*fe1, *fe2, *tr, el_x, el_y); py.AddElementVector(vdofs, el_y); } } } } if (Serial()) { if (cP) { cP->MultTranspose(py, y); } for (int i = 0; i < ess_tdof_list.Size(); i++) { y(ess_tdof_list[i]) = 0.0; } // y(ess_tdof_list[i]) = x(ess_tdof_list[i]); } } Operator &NonlinearForm::GetGradient(const Vector &x) const { if (ext) { MFEM_ABORT("Not yet implemented!"); } const int skip_zeros = 0; Array vdofs; Vector el_x; DenseMatrix elmat; const FiniteElement *fe; ElementTransformation *T; Mesh *mesh = fes->GetMesh(); const Vector &px = Prolongate(x); if (Grad == NULL) { Grad = new SparseMatrix(fes->GetVSize()); } else { *Grad = 0.0; } if (dnfi.Size()) { for (int i = 0; i < fes->GetNE(); i++) { fe = fes->GetFE(i); fes->GetElementVDofs(i, vdofs); T = fes->GetElementTransformation(i); px.GetSubVector(vdofs, el_x); for (int k = 0; k < dnfi.Size(); k++) { dnfi[k]->AssembleElementGrad(*fe, *T, el_x, elmat); Grad->AddSubMatrix(vdofs, vdofs, elmat, skip_zeros); // Grad->AddSubMatrix(vdofs, vdofs, elmat, 1); } } } if (fnfi.Size()) { FaceElementTransformations *tr; const FiniteElement *fe1, *fe2; Array vdofs2; for (int i = 0; i < mesh->GetNumFaces(); i++) { tr = mesh->GetInteriorFaceTransformations(i); if (tr != NULL) { fes->GetElementVDofs(tr->Elem1No, vdofs); fes->GetElementVDofs(tr->Elem2No, vdofs2); vdofs.Append (vdofs2); px.GetSubVector(vdofs, el_x); fe1 = fes->GetFE(tr->Elem1No); fe2 = fes->GetFE(tr->Elem2No); for (int k = 0; k < fnfi.Size(); k++) { fnfi[k]->AssembleFaceGrad(*fe1, *fe2, *tr, el_x, elmat); Grad->AddSubMatrix(vdofs, vdofs, elmat, skip_zeros); } } } } if (bfnfi.Size()) { FaceElementTransformations *tr; const FiniteElement *fe1, *fe2; // Which boundary attributes need to be processed? Array bdr_attr_marker(mesh->bdr_attributes.Size() ? mesh->bdr_attributes.Max() : 0); bdr_attr_marker = 0; for (int k = 0; k < bfnfi.Size(); k++) { if (bfnfi_marker[k] == NULL) { bdr_attr_marker = 1; break; } Array &bdr_marker = *bfnfi_marker[k]; MFEM_ASSERT(bdr_marker.Size() == bdr_attr_marker.Size(), "invalid boundary marker for boundary face integrator #" << k << ", counting from zero"); for (int i = 0; i < bdr_attr_marker.Size(); i++) { bdr_attr_marker[i] |= bdr_marker[i]; } } for (int i = 0; i < fes -> GetNBE(); i++) { const int bdr_attr = mesh->GetBdrAttribute(i); if (bdr_attr_marker[bdr_attr-1] == 0) { continue; } tr = mesh->GetBdrFaceTransformations (i); if (tr != NULL) { fes->GetElementVDofs(tr->Elem1No, vdofs); px.GetSubVector(vdofs, el_x); fe1 = fes->GetFE(tr->Elem1No); // The fe2 object is really a dummy and not used on the boundaries, // but we can't dereference a NULL pointer, and we don't want to // actually make a fake element. fe2 = fe1; for (int k = 0; k < bfnfi.Size(); k++) { if (bfnfi_marker[k] && (*bfnfi_marker[k])[bdr_attr-1] == 0) { continue; } bfnfi[k]->AssembleFaceGrad(*fe1, *fe2, *tr, el_x, elmat); Grad->AddSubMatrix(vdofs, vdofs, elmat, skip_zeros); } } } } if (!Grad->Finalized()) { Grad->Finalize(skip_zeros); } SparseMatrix *mGrad = Grad; if (Serial()) { if (cP) { delete cGrad; cGrad = RAP(*cP, *Grad, *cP); mGrad = cGrad; } for (int i = 0; i < ess_tdof_list.Size(); i++) { mGrad->EliminateRowCol(ess_tdof_list[i]); } } return *mGrad; } void NonlinearForm::Update() { if (ext) { MFEM_ABORT("Not yet implemented!"); } if (sequence == fes->GetSequence()) { return; } height = width = fes->GetTrueVSize(); delete cGrad; cGrad = NULL; delete Grad; Grad = NULL; ess_tdof_list.SetSize(0); // essential b.c. will need to be set again sequence = fes->GetSequence(); // Do not modify aux1 and aux2, their size will be set before use. P = fes->GetProlongationMatrix(); cP = dynamic_cast(P); } void NonlinearForm::Setup() { if (ext) { return ext->AssemblePA(); } } NonlinearForm::~NonlinearForm() { delete cGrad; delete Grad; for (int i = 0; i < dnfi.Size(); i++) { delete dnfi[i]; } for (int i = 0; i < fnfi.Size(); i++) { delete fnfi[i]; } for (int i = 0; i < bfnfi.Size(); i++) { delete bfnfi[i]; } delete ext; } BlockNonlinearForm::BlockNonlinearForm() : fes(0), BlockGrad(NULL) { height = 0; width = 0; } void BlockNonlinearForm::SetSpaces(Array &f) { delete BlockGrad; BlockGrad = NULL; for (int i=0; iGetVSize(); block_trueOffsets[i+1] = fes[i]->GetTrueVSize(); } block_offsets.PartialSum(); block_trueOffsets.PartialSum(); height = block_trueOffsets[fes.Size()]; width = block_trueOffsets[fes.Size()]; Grads.SetSize(fes.Size(), fes.Size()); Grads = NULL; ess_vdofs.SetSize(fes.Size()); for (int s = 0; s < fes.Size(); ++s) { ess_vdofs[s] = new Array; } } BlockNonlinearForm::BlockNonlinearForm(Array &f) : fes(0), BlockGrad(NULL) { SetSpaces(f); } void BlockNonlinearForm::AddBdrFaceIntegrator(BlockNonlinearFormIntegrator *nfi, Array &bdr_attr_marker) { bfnfi.Append(nfi); bfnfi_marker.Append(&bdr_attr_marker); } void BlockNonlinearForm::SetEssentialBC(const Array *>&bdr_attr_is_ess, Array &rhs) { int i, j, vsize, nv; for (int s=0; sGetVSize(); Array vdof_marker(vsize); // virtual call, works in parallel too fes[s]->GetEssentialVDofs(*(bdr_attr_is_ess[s]), vdof_marker); nv = 0; for (i = 0; i < vsize; ++i) { if (vdof_marker[i]) { nv++; } } ess_vdofs[s]->SetSize(nv); for (i = j = 0; i < vsize; ++i) { if (vdof_marker[i]) { (*ess_vdofs[s])[j++] = i; } } if (rhs[s]) { for (i = 0; i < nv; ++i) { (*rhs[s])[(*ess_vdofs[s])[i]] = 0.0; } } } } double BlockNonlinearForm::GetEnergyBlocked(const BlockVector &bx) const { Array *> vdofs(fes.Size()); Array el_x(fes.Size()); Array el_x_const(fes.Size()); Array fe(fes.Size()); ElementTransformation *T; double energy = 0.0; for (int i=0; i; } if (dnfi.Size()) for (int i = 0; i < fes[0]->GetNE(); ++i) { T = fes[0]->GetElementTransformation(i); for (int s=0; sGetFE(i); fes[s]->GetElementVDofs(i, *vdofs[s]); bx.GetBlock(s).GetSubVector(*vdofs[s], *el_x[s]); } for (int k = 0; k < dnfi.Size(); ++k) { energy += dnfi[k]->GetElementEnergy(fe, *T, el_x_const); } } if (fnfi.Size()) { MFEM_ABORT("TODO: add energy contribution from interior face terms"); } if (bfnfi.Size()) { MFEM_ABORT("TODO: add energy contribution from boundary face terms"); } return energy; } double BlockNonlinearForm::GetEnergy(const Vector &x) const { xs.Update(x.GetData(), block_offsets); return GetEnergyBlocked(xs); } void BlockNonlinearForm::MultBlocked(const BlockVector &bx, BlockVector &by) const { 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()); ElementTransformation *T; by = 0.0; for (int s=0; s; vdofs2[s] = new Array; } if (dnfi.Size()) { for (int i = 0; i < fes[0]->GetNE(); ++i) { T = fes[0]->GetElementTransformation(i); for (int s = 0; s < fes.Size(); ++s) { fes[s]->GetElementVDofs(i, *(vdofs[s])); fe[s] = fes[s]->GetFE(i); bx.GetBlock(s).GetSubVector(*(vdofs[s]), *el_x[s]); } for (int k = 0; k < dnfi.Size(); ++k) { dnfi[k]->AssembleElementVector(fe, *T, el_x_const, el_y); for (int s=0; sSize() == 0) { continue; } by.GetBlock(s).AddElementVector(*(vdofs[s]), *el_y[s]); } } } } if (fnfi.Size()) { Mesh *mesh = fes[0]->GetMesh(); FaceElementTransformations *tr; for (int i = 0; i < mesh->GetNumFaces(); ++i) { tr = mesh->GetInteriorFaceTransformations(i); if (tr != NULL) { for (int s=0; sGetFE(tr->Elem1No); fe2[s] = fes[s]->GetFE(tr->Elem2No); fes[s]->GetElementVDofs(tr->Elem1No, *(vdofs[s])); fes[s]->GetElementVDofs(tr->Elem2No, *(vdofs2[s])); vdofs[s]->Append(*(vdofs2[s])); bx.GetBlock(s).GetSubVector(*(vdofs[s]), *el_x[s]); } 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; } by.GetBlock(s).AddElementVector(*(vdofs[s]), *el_y[s]); } } } } } if (bfnfi.Size()) { Mesh *mesh = fes[0]->GetMesh(); FaceElementTransformations *tr; // Which boundary attributes need to be processed? Array bdr_attr_marker(mesh->bdr_attributes.Size() ? mesh->bdr_attributes.Max() : 0); bdr_attr_marker = 0; for (int k = 0; k < bfnfi.Size(); ++k) { if (bfnfi_marker[k] == NULL) { bdr_attr_marker = 1; break; } Array &bdr_marker = *bfnfi_marker[k]; MFEM_ASSERT(bdr_marker.Size() == bdr_attr_marker.Size(), "invalid boundary marker for boundary face integrator #" << k << ", counting from zero"); for (int i = 0; i < bdr_attr_marker.Size(); ++i) { bdr_attr_marker[i] |= bdr_marker[i]; } } for (int i = 0; i < mesh->GetNBE(); ++i) { const int bdr_attr = mesh->GetBdrAttribute(i); if (bdr_attr_marker[bdr_attr-1] == 0) { continue; } tr = mesh->GetBdrFaceTransformations(i); if (tr != NULL) { for (int s=0; sGetFE(tr->Elem1No); fe2[s] = fes[s]->GetFE(tr->Elem1No); fes[s]->GetElementVDofs(tr->Elem1No, *(vdofs[s])); bx.GetBlock(s).GetSubVector(*(vdofs[s]), *el_x[s]); } for (int k = 0; k < bfnfi.Size(); ++k) { if (bfnfi_marker[k] && (*bfnfi_marker[k])[bdr_attr-1] == 0) { continue; } bfnfi[k]->AssembleFaceVector(fe, fe2, *tr, el_x_const, el_y); for (int s=0; sSize() == 0) { continue; } by.GetBlock(s).AddElementVector(*(vdofs[s]), *el_y[s]); } } } } } for (int s=0; s *> vdofs(fes.Size()); Array *> vdofs2(fes.Size()); Array el_x(fes.Size()); Array el_x_const(fes.Size()); Array2D elmats(fes.Size(), fes.Size()); Arrayfe(fes.Size()); Arrayfe2(fes.Size()); ElementTransformation * T; if (BlockGrad != NULL) { delete BlockGrad; } BlockGrad = new BlockOperator(block_offsets); for (int i=0; i; vdofs2[i] = new Array; for (int j=0; jGetVSize(), fes[j]->GetVSize()); } } } if (dnfi.Size()) { for (int i = 0; i < fes[0]->GetNE(); ++i) { T = fes[0]->GetElementTransformation(i); for (int s = 0; s < fes.Size(); ++s) { fe[s] = fes[s]->GetFE(i); fes[s]->GetElementVDofs(i, *vdofs[s]); bx.GetBlock(s).GetSubVector(*vdofs[s], *el_x[s]); } for (int k = 0; k < dnfi.Size(); ++k) { dnfi[k]->AssembleElementGrad(fe, *T, el_x_const, elmats); for (int j=0; jHeight() == 0) { continue; } Grads(j,l)->AddSubMatrix(*vdofs[j], *vdofs[l], *elmats(j,l), skip_zeros); } } } } } if (fnfi.Size()) { FaceElementTransformations *tr; Mesh *mesh = fes[0]->GetMesh(); for (int i = 0; i < mesh->GetNumFaces(); ++i) { tr = mesh->GetInteriorFaceTransformations(i); for (int s=0; s < fes.Size(); ++s) { fe[s] = fes[s]->GetFE(tr->Elem1No); fe2[s] = fes[s]->GetFE(tr->Elem2No); fes[s]->GetElementVDofs(tr->Elem1No, *vdofs[s]); fes[s]->GetElementVDofs(tr->Elem2No, *vdofs2[s]); vdofs[s]->Append(*(vdofs2[s])); bx.GetBlock(s).GetSubVector(*vdofs[s], *el_x[s]); } for (int k = 0; k < fnfi.Size(); ++k) { fnfi[k]->AssembleFaceGrad(fe, fe2, *tr, el_x_const, elmats); for (int j=0; jHeight() == 0) { continue; } Grads(j,l)->AddSubMatrix(*vdofs[j], *vdofs[l], *elmats(j,l), skip_zeros); } } } } } if (bfnfi.Size()) { FaceElementTransformations *tr; Mesh *mesh = fes[0]->GetMesh(); // Which boundary attributes need to be processed? Array bdr_attr_marker(mesh->bdr_attributes.Size() ? mesh->bdr_attributes.Max() : 0); bdr_attr_marker = 0; for (int k = 0; k < bfnfi.Size(); ++k) { if (bfnfi_marker[k] == NULL) { bdr_attr_marker = 1; break; } Array &bdr_marker = *bfnfi_marker[k]; MFEM_ASSERT(bdr_marker.Size() == bdr_attr_marker.Size(), "invalid boundary marker for boundary face integrator #" << k << ", counting from zero"); for (int i = 0; i < bdr_attr_marker.Size(); ++i) { bdr_attr_marker[i] |= bdr_marker[i]; } } for (int i = 0; i < mesh->GetNBE(); ++i) { const int bdr_attr = mesh->GetBdrAttribute(i); if (bdr_attr_marker[bdr_attr-1] == 0) { continue; } tr = mesh->GetBdrFaceTransformations(i); if (tr != NULL) { for (int s = 0; s < fes.Size(); ++s) { fe[s] = fes[s]->GetFE(tr->Elem1No); fe2[s] = fe[s]; fes[s]->GetElementVDofs(i, *vdofs[s]); bx.GetBlock(s).GetSubVector(*vdofs[s], *el_x[s]); } for (int k = 0; k < bfnfi.Size(); ++k) { bfnfi[k]->AssembleFaceGrad(fe, fe2, *tr, el_x_const, elmats); for (int l=0; lHeight() == 0) { continue; } Grads(j,l)->AddSubMatrix(*vdofs[j], *vdofs[l], *elmats(j,l), skip_zeros); } } } } } } for (int s=0; sSize(); ++i) { for (int j=0; jEliminateRowCol((*ess_vdofs[s])[i], Matrix::DIAG_ONE); } else { Grads(s,j)->EliminateRow((*ess_vdofs[s])[i]); Grads(j,s)->EliminateCol((*ess_vdofs[s])[i]); } } } } if (!Grads(0,0)->Finalized()) { for (int i=0; iFinalize(skip_zeros); } } } for (int i=0; iSetBlock(i,j,Grads(i,j)); delete elmats(i,j); } delete vdofs2[i]; delete vdofs[i]; delete el_x[i]; } return *BlockGrad; } Operator &BlockNonlinearForm::GetGradient(const Vector &x) const { xs.Update(x.GetData(), block_offsets); return GetGradientBlocked(xs); } BlockNonlinearForm::~BlockNonlinearForm() { delete BlockGrad; for (int i=0; i