// 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. // Implementations of classes FABilinearFormExtension, EABilinearFormExtension, // PABilinearFormExtension and MFBilinearFormExtension. #include "../general/forall.hpp" #include "bilinearform.hpp" #include "pbilinearform.hpp" #include "pgridfunc.hpp" #include "fe/face_map_utils.hpp" #include "ceed/interface/util.hpp" namespace mfem { BilinearFormExtension::BilinearFormExtension(BilinearForm *form) : Operator(form->Size()), a(form) { // empty } const Operator *BilinearFormExtension::GetProlongation() const { return a->GetProlongation(); } const Operator *BilinearFormExtension::GetRestriction() const { return a->GetRestriction(); } // Data and methods for partially-assembled bilinear forms MFBilinearFormExtension::MFBilinearFormExtension(BilinearForm *form) : BilinearFormExtension(form), trial_fes(a->FESpace()), test_fes(a->FESpace()) { elem_restrict = NULL; int_face_restrict_lex = NULL; bdr_face_restrict_lex = NULL; } void MFBilinearFormExtension::Assemble() { Array &integrators = *a->GetDBFI(); const int integratorCount = integrators.Size(); for (int i = 0; i < integratorCount; ++i) { integrators[i]->AssembleMF(*a->FESpace()); } MFEM_VERIFY(a->GetBBFI()->Size() == 0, "AddBoundaryIntegrator is not " "currently supported in MFBilinearFormExtension"); } void MFBilinearFormExtension::AssembleDiagonal(Vector &y) const { Array &integrators = *a->GetDBFI(); const int iSz = integrators.Size(); if (elem_restrict && !DeviceCanUseCeed()) { localY = 0.0; for (int i = 0; i < iSz; ++i) { integrators[i]->AssembleDiagonalMF(localY); } const ElementRestriction* H1elem_restrict = dynamic_cast(elem_restrict); if (H1elem_restrict) { H1elem_restrict->AbsMultTranspose(localY, y); } else { elem_restrict->MultTranspose(localY, y); } } else { y.UseDevice(true); // typically this is a large vector, so store on device y = 0.0; for (int i = 0; i < iSz; ++i) { integrators[i]->AssembleDiagonalMF(y); } } } void MFBilinearFormExtension::Update() { FiniteElementSpace *fes = a->FESpace(); height = width = fes->GetVSize(); trial_fes = fes; test_fes = fes; elem_restrict = nullptr; int_face_restrict_lex = nullptr; bdr_face_restrict_lex = nullptr; } void MFBilinearFormExtension::FormSystemMatrix(const Array &ess_tdof_list, OperatorHandle &A) { Operator *oper; Operator::FormSystemOperator(ess_tdof_list, oper); A.Reset(oper); // A will own oper } void MFBilinearFormExtension::FormLinearSystem(const Array &ess_tdof_list, Vector &x, Vector &b, OperatorHandle &A, Vector &X, Vector &B, int copy_interior) { Operator *oper; Operator::FormLinearSystem(ess_tdof_list, x, b, oper, X, B, copy_interior); A.Reset(oper); // A will own oper } void MFBilinearFormExtension::Mult(const Vector &x, Vector &y) const { Array &integrators = *a->GetDBFI(); const int iSz = integrators.Size(); if (DeviceCanUseCeed() || !elem_restrict) { y.UseDevice(true); // typically this is a large vector, so store on device y = 0.0; for (int i = 0; i < iSz; ++i) { integrators[i]->AddMultMF(x, y); } } else { elem_restrict->Mult(x, localX); localY = 0.0; for (int i = 0; i < iSz; ++i) { integrators[i]->AddMultMF(localX, localY); } elem_restrict->MultTranspose(localY, y); } Array &intFaceIntegrators = *a->GetFBFI(); const int iFISz = intFaceIntegrators.Size(); if (int_face_restrict_lex && iFISz>0) { int_face_restrict_lex->Mult(x, int_face_X); if (int_face_X.Size()>0) { int_face_Y = 0.0; for (int i = 0; i < iFISz; ++i) { intFaceIntegrators[i]->AddMultMF(int_face_X, int_face_Y); } int_face_restrict_lex->AddMultTransposeInPlace(int_face_Y, y); } } Array &bdrFaceIntegrators = *a->GetBFBFI(); const int bFISz = bdrFaceIntegrators.Size(); if (bdr_face_restrict_lex && bFISz>0) { bdr_face_restrict_lex->Mult(x, bdr_face_X); if (bdr_face_X.Size()>0) { bdr_face_Y = 0.0; for (int i = 0; i < bFISz; ++i) { bdrFaceIntegrators[i]->AddMultMF(bdr_face_X, bdr_face_Y); } bdr_face_restrict_lex->AddMultTransposeInPlace(bdr_face_Y, y); } } } void MFBilinearFormExtension::MultTranspose(const Vector &x, Vector &y) const { Array &integrators = *a->GetDBFI(); const int iSz = integrators.Size(); if (elem_restrict) { elem_restrict->Mult(x, localX); localY = 0.0; for (int i = 0; i < iSz; ++i) { integrators[i]->AddMultTransposeMF(localX, localY); } elem_restrict->MultTranspose(localY, y); } else { y.UseDevice(true); y = 0.0; for (int i = 0; i < iSz; ++i) { integrators[i]->AddMultTransposeMF(x, y); } } Array &intFaceIntegrators = *a->GetFBFI(); const int iFISz = intFaceIntegrators.Size(); if (int_face_restrict_lex && iFISz>0) { int_face_restrict_lex->Mult(x, int_face_X); if (int_face_X.Size()>0) { int_face_Y = 0.0; for (int i = 0; i < iFISz; ++i) { intFaceIntegrators[i]->AddMultTransposeMF(int_face_X, int_face_Y); } int_face_restrict_lex->AddMultTransposeInPlace(int_face_Y, y); } } Array &bdrFaceIntegrators = *a->GetBFBFI(); const int bFISz = bdrFaceIntegrators.Size(); if (bdr_face_restrict_lex && bFISz>0) { bdr_face_restrict_lex->Mult(x, bdr_face_X); if (bdr_face_X.Size()>0) { bdr_face_Y = 0.0; for (int i = 0; i < bFISz; ++i) { bdrFaceIntegrators[i]->AddMultTransposeMF(bdr_face_X, bdr_face_Y); } bdr_face_restrict_lex->AddMultTransposeInPlace(bdr_face_Y, y); } } } // Data and methods for partially-assembled bilinear forms PABilinearFormExtension::PABilinearFormExtension(BilinearForm *form) : BilinearFormExtension(form), trial_fes(a->FESpace()), test_fes(a->FESpace()) { elem_restrict = NULL; int_face_restrict_lex = NULL; bdr_face_restrict_lex = NULL; } void PABilinearFormExtension::SetupRestrictionOperators(const L2FaceValues m) { if ( Device::Allows(Backend::CEED_MASK) ) { return; } ElementDofOrdering ordering = GetEVectorOrdering(*a->FESpace()); elem_restrict = trial_fes->GetElementRestriction(ordering); if (elem_restrict) { localX.SetSize(elem_restrict->Height(), Device::GetDeviceMemoryType()); localY.SetSize(elem_restrict->Height(), Device::GetDeviceMemoryType()); localY.UseDevice(true); // ensure 'localY = 0.0' is done on device // Gather the attributes on the host from all the elements const Mesh &mesh = *trial_fes->GetMesh(); elem_attributes = &mesh.GetElementAttributes(); } // Construct face restriction operators only if the bilinear form has // interior or boundary face integrators if (int_face_restrict_lex == NULL && a->GetFBFI()->Size() > 0) { int_face_restrict_lex = trial_fes->GetFaceRestriction( ElementDofOrdering::LEXICOGRAPHIC, FaceType::Interior); int_face_X.SetSize(int_face_restrict_lex->Height(), Device::GetMemoryType()); int_face_Y.SetSize(int_face_restrict_lex->Height(), Device::GetMemoryType()); int_face_Y.UseDevice(true); // ensure 'int_face_Y = 0.0' is done on device bool needs_normal_derivs = false; auto &integs = *a->GetFBFI(); for (int i = 0; i < integs.Size(); ++i) { if (integs[i]->RequiresFaceNormalDerivatives()) { needs_normal_derivs = true; break; } } if (needs_normal_derivs) { int_face_dXdn.SetSize(int_face_restrict_lex->Height()); int_face_dYdn.SetSize(int_face_restrict_lex->Height()); } } const bool has_bdr_integs = (a->GetBFBFI()->Size() > 0 || a->GetBBFI()->Size() > 0); if (bdr_face_restrict_lex == NULL && has_bdr_integs) { bdr_face_restrict_lex = trial_fes->GetFaceRestriction( ElementDofOrdering::LEXICOGRAPHIC, FaceType::Boundary, m); bdr_face_X.SetSize(bdr_face_restrict_lex->Height(), Device::GetMemoryType()); bdr_face_Y.SetSize(bdr_face_restrict_lex->Height(), Device::GetMemoryType()); bdr_face_Y.UseDevice(true); // ensure 'faceBoundY = 0.0' is done on device bool needs_normal_derivs = false; auto &integs = *a->GetBFBFI(); for (int i = 0; i < integs.Size(); ++i) { if (integs[i]->RequiresFaceNormalDerivatives()) { needs_normal_derivs = true; break; } } if (needs_normal_derivs) { bdr_face_dXdn.SetSize(bdr_face_restrict_lex->Height()); bdr_face_dYdn.SetSize(bdr_face_restrict_lex->Height()); } bdr_face_attributes = &trial_fes->GetMesh()->GetBdrFaceAttributes(); } } void PABilinearFormExtension::Assemble() { SetupRestrictionOperators(L2FaceValues::DoubleValued); Array &integrators = *a->GetDBFI(); for (BilinearFormIntegrator *integ : integrators) { if (integ->Patchwise()) { MFEM_VERIFY(a->FESpace()->GetNURBSext(), "Patchwise integration requires a NURBS FE space"); integ->AssembleNURBSPA(*a->FESpace()); } else { integ->AssemblePA(*a->FESpace()); } } Array &bdr_integrators = *a->GetBBFI(); for (BilinearFormIntegrator *integ : bdr_integrators) { integ->AssemblePABoundary(*a->FESpace()); } Array &intFaceIntegrators = *a->GetFBFI(); for (BilinearFormIntegrator *integ : intFaceIntegrators) { integ->AssemblePAInteriorFaces(*a->FESpace()); } Array &bdrFaceIntegrators = *a->GetBFBFI(); for (BilinearFormIntegrator *integ : bdrFaceIntegrators) { integ->AssemblePABoundaryFaces(*a->FESpace()); } } void PABilinearFormExtension::AssembleDiagonal(Vector &y) const { Array &integrators = *a->GetDBFI(); auto assemble_diagonal_with_markers = [&](BilinearFormIntegrator &integ, const Array *markers, const Array &attributes, Vector &d) { integ.AssembleDiagonalPA(d); if (markers) { const int ne = attributes.Size(); const int nd = d.Size() / ne; const auto d_attr = Reshape(attributes.Read(), ne); const auto d_m = Reshape(markers->Read(), markers->Size()); auto d_d = Reshape(d.ReadWrite(), nd, ne); mfem::forall(ne, [=] MFEM_HOST_DEVICE (int e) { const int attr = d_attr[e]; if (attr <= 0 || d_m[attr - 1] == 0) { for (int i = 0; i < nd; ++i) { d_d(i, e) = 0.0; } } }); } }; const int iSz = integrators.Size(); if (elem_restrict && !DeviceCanUseCeed()) { if (iSz > 0) { localY = 0.0; Array*> &elem_markers = *a->GetDBFI_Marker(); for (int i = 0; i < iSz; ++i) { assemble_diagonal_with_markers(*integrators[i], elem_markers[i], *elem_attributes, localY); } const ElementRestriction* H1elem_restrict = dynamic_cast(elem_restrict); if (H1elem_restrict) { H1elem_restrict->AbsMultTranspose(localY, y); } else { elem_restrict->MultTranspose(localY, y); } } else { y = 0.0; } } else { Array*> &elem_markers = *a->GetDBFI_Marker(); y.UseDevice(true); // typically this is a large vector, so store on device y = 0.0; for (int i = 0; i < iSz; ++i) { assemble_diagonal_with_markers(*integrators[i], elem_markers[i], *elem_attributes, y); } } Array &bdr_integs = *a->GetBBFI(); const int n_bdr_integs = bdr_integs.Size(); if (bdr_face_restrict_lex && n_bdr_integs > 0) { Array*> &bdr_markers = *a->GetBBFI_Marker(); bdr_face_Y = 0.0; for (int i = 0; i < n_bdr_integs; ++i) { assemble_diagonal_with_markers(*bdr_integs[i], bdr_markers[i], *bdr_face_attributes, bdr_face_Y); } bdr_face_restrict_lex->AddAbsMultTranspose(bdr_face_Y, y); } } void PABilinearFormExtension::Update() { FiniteElementSpace *fes = a->FESpace(); height = width = fes->GetVSize(); trial_fes = fes; test_fes = fes; elem_restrict = nullptr; int_face_restrict_lex = nullptr; bdr_face_restrict_lex = nullptr; } void PABilinearFormExtension::FormSystemMatrix(const Array &ess_tdof_list, OperatorHandle &A) { Operator *oper; Operator::FormSystemOperator(ess_tdof_list, oper); A.Reset(oper); // A will own oper } void PABilinearFormExtension::FormLinearSystem(const Array &ess_tdof_list, Vector &x, Vector &b, OperatorHandle &A, Vector &X, Vector &B, int copy_interior) { Operator *oper; Operator::FormLinearSystem(ess_tdof_list, x, b, oper, X, B, copy_interior); A.Reset(oper); // A will own oper } void PABilinearFormExtension::MultInternal(const Vector &x, Vector &y, const bool useAbs) const { Array &integrators = *a->GetDBFI(); const int iSz = integrators.Size(); bool allPatchwise = true; bool somePatchwise = false; for (int i = 0; i < iSz; ++i) { if (integrators[i]->Patchwise()) { somePatchwise = true; } else { allPatchwise = false; } } MFEM_VERIFY(!(somePatchwise && !allPatchwise), "All or none of the integrators should be patchwise"); if (DeviceCanUseCeed() || !elem_restrict || allPatchwise) { y.UseDevice(true); // typically this is a large vector, so store on device y = 0.0; for (int i = 0; i < iSz; ++i) { if (integrators[i]->Patchwise()) { MFEM_ASSERT(!useAbs, "AbsMult not implemented with NURBS!") integrators[i]->AddMultNURBSPA(x, y); } else { if (useAbs) { integrators[i]->AddAbsMultPA(x, y); } else { integrators[i]->AddMultPA(x, y); } } } } else { if (iSz) { Array*> &elem_markers = *a->GetDBFI_Marker(); auto H1elem_restrict = dynamic_cast(elem_restrict); if (H1elem_restrict && useAbs) { H1elem_restrict->AbsMult(x, localX); } else { elem_restrict->Mult(x, localX); } localY = 0.0; for (int i = 0; i < iSz; ++i) { AddMultWithMarkers(*integrators[i], localX, elem_markers[i], *elem_attributes, false, localY, useAbs); } if (H1elem_restrict && useAbs) { H1elem_restrict->AbsMultTranspose(localY, y); } else { elem_restrict->MultTranspose(localY, y); } } else { y = 0.0; } } Array &intFaceIntegrators = *a->GetFBFI(); const int iFISz = intFaceIntegrators.Size(); if (int_face_restrict_lex && iFISz>0) { MFEM_ASSERT(!useAbs, "AbsMult not implemented for face integrators!") // When assembling interior face integrators for DG spaces, we need to // exchange the face-neighbor information. This happens inside member // functions of the 'int_face_restrict_lex'. To avoid repeated calls to // ParGridFunction::ExchangeFaceNbrData, if we have a parallel space // with interior face integrators, we create a ParGridFunction that // will be used to cache the face-neighbor data. x_dg should be passed // to any restriction operator that may need to use face-neighbor data. const Vector *x_dg = &x; #ifdef MFEM_USE_MPI ParGridFunction x_pgf; if (auto *pfes = dynamic_cast(a->FESpace())) { x_pgf.MakeRef(pfes, const_cast(x), 0); x_dg = &x_pgf; } #endif int_face_restrict_lex->Mult(*x_dg, int_face_X); if (int_face_dXdn.Size() > 0) { int_face_restrict_lex->NormalDerivativeMult(*x_dg, int_face_dXdn); } if (int_face_X.Size() > 0) { int_face_Y = 0.0; // if normal derivatives are needed by at least one integrator... if (int_face_dYdn.Size() > 0) { int_face_dYdn = 0.0; } for (int i = 0; i < iFISz; ++i) { if (intFaceIntegrators[i]->RequiresFaceNormalDerivatives()) { intFaceIntegrators[i]->AddMultPAFaceNormalDerivatives( int_face_X, int_face_dXdn, int_face_Y, int_face_dYdn); } else { intFaceIntegrators[i]->AddMultPA(int_face_X, int_face_Y); } } int_face_restrict_lex->AddMultTransposeInPlace(int_face_Y, y); if (int_face_dYdn.Size() > 0) { int_face_restrict_lex->NormalDerivativeAddMultTranspose( int_face_dYdn, y); } } } Array &bdr_integs = *a->GetBBFI(); Array &bdr_face_integs = *a->GetBFBFI(); const int n_bdr_integs = bdr_integs.Size(); const int n_bdr_face_integs = bdr_face_integs.Size(); const bool has_bdr_integs = (n_bdr_face_integs > 0 || n_bdr_integs > 0); if (bdr_face_restrict_lex && has_bdr_integs) { MFEM_ASSERT(!useAbs, "AbsMult not implemented for bdr integrators!") Array*> &bdr_markers = *a->GetBBFI_Marker(); Array*> &bdr_face_markers = *a->GetBFBFI_Marker(); bdr_face_restrict_lex->Mult(x, bdr_face_X); if (bdr_face_dXdn.Size() > 0) { bdr_face_restrict_lex->NormalDerivativeMult(x, bdr_face_dXdn); } if (bdr_face_X.Size() > 0) { bdr_face_Y = 0.0; // if normal derivatives are needed by at least one integrator... if (bdr_face_dYdn.Size() > 0) { bdr_face_dYdn = 0.0; } for (int i = 0; i < n_bdr_integs; ++i) { AddMultWithMarkers(*bdr_integs[i], bdr_face_X, bdr_markers[i], *bdr_face_attributes, false, bdr_face_Y); } for (int i = 0; i < n_bdr_face_integs; ++i) { if (bdr_face_integs[i]->RequiresFaceNormalDerivatives()) { AddMultNormalDerivativesWithMarkers( *bdr_face_integs[i], bdr_face_X, bdr_face_dXdn, bdr_face_markers[i], *bdr_face_attributes, bdr_face_Y, bdr_face_dYdn); } else { AddMultWithMarkers(*bdr_face_integs[i], bdr_face_X, bdr_face_markers[i], *bdr_face_attributes, false, bdr_face_Y); } } bdr_face_restrict_lex->AddMultTransposeInPlace(bdr_face_Y, y); if (bdr_face_dYdn.Size() > 0) { bdr_face_restrict_lex->NormalDerivativeAddMultTranspose(bdr_face_dYdn, y); } } } } void PABilinearFormExtension::MultTranspose(const Vector &x, Vector &y) const { Array &integrators = *a->GetDBFI(); const int iSz = integrators.Size(); if (elem_restrict) { Array*> &elem_markers = *a->GetDBFI_Marker(); elem_restrict->Mult(x, localX); localY = 0.0; for (int i = 0; i < iSz; ++i) { AddMultWithMarkers(*integrators[i], localX, elem_markers[i], *elem_attributes, true, localY); } elem_restrict->MultTranspose(localY, y); } else { y.UseDevice(true); y = 0.0; for (int i = 0; i < iSz; ++i) { integrators[i]->AddMultTransposePA(x, y); } } Array &intFaceIntegrators = *a->GetFBFI(); const int iFISz = intFaceIntegrators.Size(); if (int_face_restrict_lex && iFISz>0) { int_face_restrict_lex->Mult(x, int_face_X); if (int_face_X.Size()>0) { int_face_Y = 0.0; for (int i = 0; i < iFISz; ++i) { intFaceIntegrators[i]->AddMultTransposePA(int_face_X, int_face_Y); } int_face_restrict_lex->AddMultTransposeInPlace(int_face_Y, y); } } Array &bdr_integs = *a->GetBBFI(); Array &bdr_face_integs = *a->GetBFBFI(); const int n_bdr_integs = bdr_integs.Size(); const int n_bdr_face_integs = bdr_face_integs.Size(); const bool has_bdr_integs = (n_bdr_face_integs > 0 || n_bdr_integs > 0); if (bdr_face_restrict_lex && has_bdr_integs) { Array*> &bdr_markers = *a->GetBBFI_Marker(); Array*> &bdr_face_markers = *a->GetBFBFI_Marker(); bdr_face_restrict_lex->Mult(x, bdr_face_X); if (bdr_face_X.Size() > 0) { bdr_face_Y = 0.0; for (int i = 0; i < n_bdr_integs; ++i) { AddMultWithMarkers(*bdr_integs[i], bdr_face_X, bdr_markers[i], *bdr_face_attributes, true, bdr_face_Y); } for (int i = 0; i < n_bdr_face_integs; ++i) { AddMultWithMarkers(*bdr_face_integs[i], bdr_face_X, bdr_face_markers[i], *bdr_face_attributes, true, bdr_face_Y); } bdr_face_restrict_lex->AddMultTransposeInPlace(bdr_face_Y, y); } } } // Compute kernels for PABilinearFormExtension::AddMultWithMarkers. // Cannot be in member function with non-public visibility. static void AddWithMarkers_( const int ne, const int nd, const Vector &x, const Array &markers, const Array &attributes, Vector &y) { const auto d_x = Reshape(x.Read(), nd, ne); const auto d_m = Reshape(markers.Read(), markers.Size()); const auto d_attr = Reshape(attributes.Read(), ne); auto d_y = Reshape(y.ReadWrite(), nd, ne); mfem::forall(ne, [=] MFEM_HOST_DEVICE (int e) { const int attr = d_attr[e]; if (attr <= 0 || d_m[attr - 1] == 0) { return; } for (int i = 0; i < nd; ++i) { d_y(i, e) += d_x(i, e); } }); } void PABilinearFormExtension::AddMultNormalDerivativesWithMarkers( const BilinearFormIntegrator &integ, const Vector &x, const Vector &dxdn, const Array *markers, const Array &attributes, Vector &y, Vector &dydn) const { if (markers) { tmp_evec.SetSize(y.Size() + dydn.Size()); tmp_evec = 0.0; Vector tmp_y(tmp_evec, 0, y.Size()); Vector tmp_dydn(tmp_evec, y.Size(), dydn.Size()); integ.AddMultPAFaceNormalDerivatives(x, dxdn, tmp_y, tmp_dydn); const int ne = attributes.Size(); const int nd_1 = x.Size() / ne; const int nd_2 = dxdn.Size() / ne; AddWithMarkers_(ne, nd_1, tmp_y, *markers, attributes, y); AddWithMarkers_(ne, nd_2, tmp_dydn, *markers, attributes, dydn); } else { integ.AddMultPAFaceNormalDerivatives(x, dxdn, y, dydn); } } void PABilinearFormExtension::AddMultWithMarkers( const BilinearFormIntegrator &integ, const Vector &x, const Array *markers, const Array &attributes, const bool transpose, Vector &y, const bool useAbs) const { if (markers) { tmp_evec.SetSize(y.Size()); tmp_evec = 0.0; if (useAbs) { if (transpose) { integ.AddAbsMultTransposePA(x, tmp_evec); } else { integ.AddAbsMultPA(x, tmp_evec); } } else { if (transpose) { integ.AddMultTransposePA(x, tmp_evec); } else { integ.AddMultPA(x, tmp_evec); } } const int ne = attributes.Size(); const int nd = x.Size() / ne; AddWithMarkers_(ne, nd, tmp_evec, *markers, attributes, y); } else { if (useAbs) { if (transpose) { integ.AddAbsMultTransposePA(x, y); } else { integ.AddAbsMultPA(x, y); } } else { if (transpose) { integ.AddMultTransposePA(x, y); } else { integ.AddMultPA(x, y); } } } } // Data and methods for element-assembled bilinear forms EABilinearFormExtension::EABilinearFormExtension(BilinearForm *form) : PABilinearFormExtension(form), factorize_face_terms(false) { if ( form->FESpace()->IsDGSpace() ) { factorize_face_terms = true; } } void EABilinearFormExtension::Assemble() { SetupRestrictionOperators(L2FaceValues::SingleValued); ne = trial_fes->GetMesh()->GetNE(); elemDofs = trial_fes->GetTypicalFE()->GetDof(); Vector ea_data_tmp; auto add_with_markers = [&](const Vector &ea_1, Vector &ea_2, const int ne_, const Array &markers, const Array &attrs, const bool add) { if (ne_ == 0) { return; } const int sz = ea_1.Size() / ne_; const int *d_m = markers.Read(); const int *d_a = attrs.Read(); const auto d_ea_1 = Reshape(ea_1.Read(), sz, ne_); auto d_ea_2 = Reshape(add ? ea_2.ReadWrite() : ea_2.Write(), sz, ne_); mfem::forall(sz*ne_, [=] MFEM_HOST_DEVICE (int idx) { const int i = idx % sz; const int e = idx / sz; const real_t val = d_a[e] > 0 ? (d_m[d_a[e] - 1] ? d_ea_1(i, e) : 0) : 0; if (add) { d_ea_2(i, e) += val; } else { d_ea_2(i, e) = val; } }); }; { ea_data.SetSize(ne*elemDofs*elemDofs); ea_data.UseDevice(true); Array &integrators = *a->GetDBFI(); Array*> &markers_array = *a->GetDBFI_Marker(); if (integrators.Size() == 0) { ea_data = 0.0; } for (int i = 0; i < integrators.Size(); ++i) { const bool add = (i > 0); const Array *markers = markers_array[i]; if (markers == nullptr) { integrators[i]->AssembleEA(*a->FESpace(), ea_data, add); } else { ea_data_tmp.SetSize(ea_data.Size()); integrators[i]->AssembleEA(*a->FESpace(), ea_data_tmp, false); add_with_markers(ea_data_tmp, ea_data, ne, *markers, *elem_attributes, add); } } } faceDofs = trial_fes->GetTypicalTraceElement()->GetDof(); { Array &bdr_integs = *a->GetBBFI(); Array*> &markers_array = *a->GetBBFI_Marker(); const int n_bdr_integs = bdr_integs.Size(); if (n_bdr_integs > 0) { nf_bdr = trial_fes->GetNFbyType(FaceType::Boundary); ea_data_bdr.SetSize(nf_bdr*faceDofs*faceDofs); } for (int i = 0; i < n_bdr_integs; ++i) { const bool add = (i > 0); const Array *markers = markers_array[i]; if (markers == nullptr) { bdr_integs[i]->AssembleEABoundary(*a->FESpace(), ea_data_bdr, add); } else { ea_data_tmp.SetSize(ea_data_bdr.Size()); bdr_integs[i]->AssembleEABoundary(*a->FESpace(), ea_data_tmp, add); add_with_markers(ea_data_tmp, ea_data_bdr, nf_bdr, *markers, *bdr_face_attributes, add); } } } { Array &intFaceIntegrators = *a->GetFBFI(); const int intFaceIntegratorCount = intFaceIntegrators.Size(); if (intFaceIntegratorCount>0) { nf_int = trial_fes->GetNFbyType(FaceType::Interior); ea_data_int.SetSize(2*nf_int*faceDofs*faceDofs); ea_data_ext.SetSize(2*nf_int*faceDofs*faceDofs); } for (int i = 0; i < intFaceIntegratorCount; ++i) { const bool add = (i > 0); intFaceIntegrators[i]->AssembleEAInteriorFaces(*a->FESpace(), ea_data_int, ea_data_ext, add); } } { Array &bdr_face_integs = *a->GetBFBFI(); Array*> &markers_array = *a->GetBFBFI_Marker(); const int n_bdr_face_integs = bdr_face_integs.Size(); if (n_bdr_face_integs > 0) { nf_bdr = trial_fes->GetNFbyType(FaceType::Boundary); ea_data_bdr.SetSize(nf_bdr*faceDofs*faceDofs); } for (int i = 0; i < n_bdr_face_integs; ++i) { const bool add = (i > 0); const Array *markers = markers_array[i]; if (markers == nullptr) { bdr_face_integs[i]->AssembleEABoundaryFaces( *a->FESpace(), ea_data_bdr, add); } else { ea_data_tmp.SetSize(ea_data_bdr.Size()); bdr_face_integs[i]->AssembleEABoundaryFaces(*a->FESpace(), ea_data_tmp, add); add_with_markers(ea_data_tmp, ea_data_bdr, nf_bdr, *markers, *bdr_face_attributes, add); } } } if (factorize_face_terms && int_face_restrict_lex) { auto restFint = dynamic_cast(int_face_restrict_lex); restFint->AddFaceMatricesToElementMatrices(ea_data_int, ea_data); } if (factorize_face_terms && bdr_face_restrict_lex) { auto restFbdr = dynamic_cast(bdr_face_restrict_lex); restFbdr->AddFaceMatricesToElementMatrices(ea_data_bdr, ea_data); } } void EABilinearFormExtension::MultInternal(const Vector &x, Vector &y, const bool useTranspose, const bool useAbs) const { auto elemRest = dynamic_cast(elem_restrict); MFEM_ASSERT(useAbs?(elemRest!=nullptr):true, "elem_restrict is not ElementRestriction*!") // Apply the Element Restriction const bool useRestrict = !DeviceCanUseCeed() && elem_restrict; if (!useRestrict) { y.UseDevice(true); // typically this is a large vector, so store on device y = 0.0; } else if (useAbs) { elemRest->AbsMult(x, localX); localY = 0.0; } else { elem_restrict->Mult(x, localX); localY = 0.0; } // Apply the Element Matrices { Vector abs_ea_data; if (useAbs) { abs_ea_data = ea_data; abs_ea_data.Abs(); } const int NDOFS = elemDofs; auto X = Reshape(useRestrict?localX.Read():x.Read(), NDOFS, ne); auto Y = Reshape(useRestrict?localY.ReadWrite():y.ReadWrite(), NDOFS, ne); auto A = Reshape(useAbs?abs_ea_data.Read():ea_data.Read(), NDOFS, NDOFS, ne); if (!useTranspose) { mfem::forall(ne*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j) { const int e = glob_j/NDOFS; const int j = glob_j%NDOFS; real_t res = 0.0; for (int i = 0; i < NDOFS; i++) { res += A(i, j, e)*X(i, e); } Y(j, e) += res; }); } else { mfem::forall(ne*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j) { const int e = glob_j/NDOFS; const int j = glob_j%NDOFS; real_t res = 0.0; for (int i = 0; i < NDOFS; i++) { res += A(j, i, e)*X(i, e); } Y(j, e) += res; }); } // Apply the Element Restriction transposed if (useRestrict) { if (useAbs) { elemRest->AbsMultTranspose(localY, y); } else { elem_restrict->MultTranspose(localY, y); } } } // Treatment of interior faces Array &intFaceIntegrators = *a->GetFBFI(); const int iFISz = intFaceIntegrators.Size(); if (int_face_restrict_lex && iFISz>0) { MFEM_VERIFY(!useAbs, "AbsMult not implemented with Face integrators!") // Apply the Interior Face Restriction int_face_restrict_lex->Mult(x, int_face_X); if (int_face_X.Size()>0) { int_face_Y = 0.0; // Apply the interior face matrices const int NDOFS = faceDofs; auto X = Reshape(int_face_X.Read(), NDOFS, 2, nf_int); auto Y = Reshape(int_face_Y.ReadWrite(), NDOFS, 2, nf_int); if (!factorize_face_terms) { Vector abs_ea_data_int(ea_data_int.Size()); if (useAbs) { abs_ea_data_int = ea_data_int; abs_ea_data_int.Abs(); } auto A_int = Reshape(useAbs?abs_ea_data_int.Read():ea_data_int.Read(), NDOFS, NDOFS, 2, nf_int); if (!useTranspose) { mfem::forall(nf_int*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j) { const int f = glob_j/NDOFS; const int j = glob_j%NDOFS; real_t res = 0.0; for (int i = 0; i < NDOFS; i++) { res += A_int(i, j, 0, f)*X(i, 0, f); } Y(j, 0, f) += res; res = 0.0; for (int i = 0; i < NDOFS; i++) { res += A_int(i, j, 1, f)*X(i, 1, f); } Y(j, 1, f) += res; }); } else { mfem::forall(nf_int*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j) { const int f = glob_j/NDOFS; const int j = glob_j%NDOFS; real_t res = 0.0; for (int i = 0; i < NDOFS; i++) { res += A_int(j, i, 0, f)*X(i, 0, f); } Y(j, 0, f) += res; res = 0.0; for (int i = 0; i < NDOFS; i++) { res += A_int(j, i, 1, f)*X(i, 1, f); } Y(j, 1, f) += res; }); } } Vector abs_ea_data_ext(ea_data_ext.Size()); if (useAbs) { abs_ea_data_ext = ea_data_ext; abs_ea_data_ext.Abs(); } auto A_ext = Reshape(useAbs?abs_ea_data_ext.Read():ea_data_ext.Read(), NDOFS, NDOFS, 2, nf_int); if (!useTranspose) { mfem::forall(nf_int*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j) { const int f = glob_j/NDOFS; const int j = glob_j%NDOFS; real_t res = 0.0; for (int i = 0; i < NDOFS; i++) { res += A_ext(i, j, 0, f)*X(i, 0, f); } Y(j, 1, f) += res; res = 0.0; for (int i = 0; i < NDOFS; i++) { res += A_ext(i, j, 1, f)*X(i, 1, f); } Y(j, 0, f) += res; }); } else { mfem::forall(nf_int*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j) { const int f = glob_j/NDOFS; const int j = glob_j%NDOFS; real_t res = 0.0; for (int i = 0; i < NDOFS; i++) { res += A_ext(j, i, 1, f)*X(i, 0, f); } Y(j, 1, f) += res; res = 0.0; for (int i = 0; i < NDOFS; i++) { res += A_ext(j, i, 0, f)*X(i, 1, f); } Y(j, 0, f) += res; }); } // Apply the Interior Face Restriction transposed int_face_restrict_lex->AddMultTransposeInPlace(int_face_Y, y); } } // Treatment of boundary faces if (!factorize_face_terms && bdr_face_restrict_lex && ea_data_bdr.Size() > 0) { MFEM_ASSERT(!useAbs, "AbsMult not implemented with Face integrators!") // Apply the Boundary Face Restriction // TODO: AbsMult if needed bdr_face_restrict_lex->Mult(x, bdr_face_X); bdr_face_Y = 0.0; // Apply the boundary face matrices const int NDOFS = faceDofs; auto X = Reshape(bdr_face_X.Read(), NDOFS, nf_bdr); auto Y = Reshape(bdr_face_Y.ReadWrite(), NDOFS, nf_bdr); auto A = Reshape(ea_data_bdr.Read(), NDOFS, NDOFS, nf_bdr); if (!useTranspose) { // TODO: useAbs mfem::forall(nf_bdr*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j) { const int f = glob_j/NDOFS; const int j = glob_j%NDOFS; real_t res = 0.0; for (int i = 0; i < NDOFS; i++) { res += A(i, j, f)*X(i, f); } Y(j, f) += res; }); } else { // TODO: useAbs mfem::forall(nf_bdr*NDOFS, [=] MFEM_HOST_DEVICE (int glob_j) { const int f = glob_j/NDOFS; const int j = glob_j%NDOFS; real_t res = 0.0; for (int i = 0; i < NDOFS; i++) { res += A(j, i, f)*X(i, f); } Y(j, f) += res; }); } // Apply the Boundary Face Restriction transposed // TODO: AbsMultTranspose if needed bdr_face_restrict_lex->AddMultTransposeInPlace(bdr_face_Y, y); } } void EABilinearFormExtension::GetElementMatrices( DenseTensor &element_matrices, ElementDofOrdering ordering, bool add_bdr) { // Ensure the EA data is assembled if (ea_data.Size() == 0) { Assemble(); } const int ndofs = elemDofs; element_matrices.SetSize(ndofs, ndofs, ne); const int N = element_matrices.TotalSize(); const auto d_ea_data = Reshape(ea_data.Read(), ndofs, ndofs, ne); auto d_element_matrices = Reshape(element_matrices.Write(), ndofs, ndofs, ne); const int *d_dof_map = nullptr; Array dof_map; if (ordering == ElementDofOrdering::NATIVE) { const TensorBasisElement* tbe = dynamic_cast(trial_fes->GetFE(0)); if (tbe) { // Deep copy to avoid issues with host device (see similar comment in // HybridizationExtension::ConstructC). dof_map = tbe->GetDofMap(); d_dof_map = dof_map.Read(); } } if (d_dof_map) { // Reordering required mfem::forall(N, [=] MFEM_HOST_DEVICE (int idx) { const int e = idx / ndofs / ndofs; const int i = idx % ndofs; const int j = (idx / ndofs) % ndofs; const int ii_s = d_dof_map[i]; const int ii = (ii_s >= 0) ? ii_s : -1 - ii_s; const int s_i = (ii_s >= 0) ? 1 : -1; const int jj_s = d_dof_map[j]; const int jj = (jj_s >= 0) ? jj_s : -1 - jj_s; const int s_j = (jj_s >= 0) ? 1 : -1; d_element_matrices(ii, jj, e) = s_i*s_j*d_ea_data(j, i, e); }); } else { // No reordering required mfem::forall(N, [=] MFEM_HOST_DEVICE (int idx) { const int e = idx / ndofs / ndofs; const int i = idx % ndofs; const int j = (idx / ndofs) % ndofs; d_element_matrices(i, j, e) = d_ea_data(j, i, e); }); } if (add_bdr && ea_data_bdr.Size() > 0) { const int ndof_face = faceDofs; const auto d_ea_bdr = Reshape(ea_data_bdr.Read(), ndof_face, ndof_face, nf_bdr); // Get all the local face maps (mapping from lexicographic face index to // lexicographic volume index, depending on the local face index). const Mesh &mesh = *trial_fes->GetMesh(); const int dim = mesh.Dimension(); const int n_faces_per_el = 2*dim; // assuming tensor product Array face_maps(ndof_face * n_faces_per_el); for (int lf_i = 0; lf_i < n_faces_per_el; ++lf_i) { Array face_map(ndof_face); trial_fes->GetFE(0)->GetFaceMap(lf_i, face_map); for (int i = 0; i < ndof_face; ++i) { face_maps[i + lf_i*ndof_face] = face_map[i]; } } Array face_info(nf_bdr * 2); { int fidx = 0; for (int f = 0; f < mesh.GetNumFaces(); ++f) { Mesh::FaceInformation finfo = mesh.GetFaceInformation(f); if (!finfo.IsBoundary()) { continue; } face_info[0 + fidx*2] = finfo.element[0].local_face_id; face_info[1 + fidx*2] = finfo.element[0].index; fidx++; } } const auto d_face_maps = Reshape(face_maps.Read(), ndof_face, n_faces_per_el); const auto d_face_info = Reshape(face_info.Read(), 2, nf_bdr); const bool reorder = (ordering == ElementDofOrdering::NATIVE); mfem::forall_2D(nf_bdr, ndof_face, ndof_face, [=] MFEM_HOST_DEVICE (int f) { const int lf_i = d_face_info(0, f); const int e = d_face_info(1, f); // Loop over face indices in "native ordering" MFEM_FOREACH_THREAD(i_lex_face, x, ndof_face) { // Convert from lexicographic face DOF to volume DOF const int i_lex = d_face_maps(i_lex_face, lf_i); const int ii_s = d_dof_map[i_lex]; const int ii = (ii_s >= 0) ? ii_s : -1 - ii_s; const int i = reorder ? ii : i_lex; const int s_i = (ii_s < 0 && reorder) ? -1 : 1; MFEM_FOREACH_THREAD(j_lex_face, y, ndof_face) { // Convert from lexicographic face DOF to volume DOF const int j_lex = d_face_maps(j_lex_face, lf_i); const int jj_s = d_dof_map[j_lex]; const int jj = (jj_s >= 0) ? jj_s : -1 - jj_s; const int j = reorder ? jj : j_lex; const int s_j = (jj_s < 0 && reorder) ? -1 : 1; AtomicAdd(d_element_matrices(i, j, e), s_i*s_j*d_ea_bdr(i_lex_face, j_lex_face, f)); } } }); } } // Data and methods for fully-assembled bilinear forms FABilinearFormExtension::FABilinearFormExtension(BilinearForm *form) : EABilinearFormExtension(form), mat(a->mat) { #ifdef MFEM_USE_MPI ParFiniteElementSpace *pfes = nullptr; if ( a->GetFBFI()->Size()>0 && (pfes = dynamic_cast(form->FESpace())) ) { pfes->ExchangeFaceNbrData(); } #endif } void FABilinearFormExtension::Assemble() { EABilinearFormExtension::Assemble(); FiniteElementSpace &fes = *a->FESpace(); int width = fes.GetVSize(); int height = fes.GetVSize(); bool keep_nbr_block = false; #ifdef MFEM_USE_MPI ParFiniteElementSpace *pfes = nullptr; if ( a->GetFBFI()->Size()>0 && (pfes = dynamic_cast(&fes)) ) { pfes->ExchangeFaceNbrData(); width += pfes->GetFaceNbrVSize(); dg_x.SetSize(width); ParBilinearForm *pb = nullptr; if ((pb = dynamic_cast(a)) && (pb->keep_nbr_block)) { height += pfes->GetFaceNbrVSize(); dg_y.SetSize(height); keep_nbr_block = true; } } #endif if (a->mat) // We reuse the sparse matrix memory { if (fes.IsDGSpace()) { const L2ElementRestriction *restE = static_cast(elem_restrict); const L2FaceRestriction *restF = static_cast(int_face_restrict_lex); MFEM_VERIFY( fes.Conforming(), "Full Assembly not yet supported on NCMesh."); // 1. Fill J and Data // 1.1 Fill J and Data with Elem ea_data restE->FillJAndData(ea_data, *mat); // 1.2 Fill J and Data with Face ea_data_ext if (restF) { restF->FillJAndData(ea_data_ext, *mat, keep_nbr_block); } // 1.3 Shift indirections in I back to original auto I = mat->HostReadWriteI(); for (int i = height; i > 0; i--) { I[i] = I[i-1]; } I[0] = 0; } else { const ElementRestriction &rest = static_cast(*elem_restrict); rest.FillJAndData(ea_data, *mat); } } else // We create, compute the sparsity, and fill the sparse matrix { mat = new SparseMatrix; mat->OverrideSize(height, width); if (fes.IsDGSpace()) { const L2ElementRestriction *restE = static_cast(elem_restrict); const L2FaceRestriction *restF = static_cast(int_face_restrict_lex); // 1. Fill I mat->GetMemoryI().New(height+1, mat->GetMemoryI().GetMemoryType()); // 1.1 Increment with restE restE->FillI(*mat); // 1.2 Increment with restF if (restF) { restF->FillI(*mat, keep_nbr_block); } // 1.3 Sum the non-zeros in I auto h_I = mat->HostReadWriteI(); int cpt = 0; for (int i = 0; i < height; i++) { const int nnz = h_I[i]; h_I[i] = cpt; cpt += nnz; } const int nnz = cpt; h_I[height] = nnz; mat->GetMemoryJ().New(nnz, mat->GetMemoryJ().GetMemoryType()); mat->GetMemoryData().New(nnz, mat->GetMemoryData().GetMemoryType()); // 2. Fill J and Data // 2.1 Fill J and Data with Elem ea_data restE->FillJAndData(ea_data, *mat); // 2.2 Fill J and Data with Face ea_data_ext if (restF) { restF->FillJAndData(ea_data_ext, *mat, keep_nbr_block); } // 2.3 Shift indirections in I back to original auto I = mat->HostReadWriteI(); for (int i = height; i > 0; i--) { I[i] = I[i-1]; } I[0] = 0; } else // continuous Galerkin case { const ElementRestriction &rest = static_cast(*elem_restrict); rest.FillSparseMatrix(ea_data, *mat); } a->mat = mat; } if ( a->sort_sparse_matrix ) { a->mat->SortColumnIndices(); } } void FABilinearFormExtension::RAP(OperatorHandle &A) { #ifdef MFEM_USE_MPI if ( auto pa = dynamic_cast(a) ) { pa->ParallelRAP(*pa->mat, A); } else #endif { a->SerialRAP(A); } } void FABilinearFormExtension::EliminateBC(const Array &ess_dofs, OperatorHandle &A) { MFEM_VERIFY(a->diag_policy == DiagonalPolicy::DIAG_ONE, "Only DiagonalPolicy::DIAG_ONE supported with" " FABilinearFormExtension."); #ifdef MFEM_USE_MPI if ( dynamic_cast(a) ) { A.As()->EliminateBC(ess_dofs, DiagonalPolicy::DIAG_ONE); } else #endif { A.As()->EliminateBC(ess_dofs, DiagonalPolicy::DIAG_ONE); } } void FABilinearFormExtension::FormSystemMatrix(const Array &ess_dofs, OperatorHandle &A) { RAP(A); EliminateBC(ess_dofs, A); } void FABilinearFormExtension::FormLinearSystem(const Array &ess_tdof_list, Vector &x, Vector &b, OperatorHandle &A, Vector &X, Vector &B, int copy_interior) { Operator *A_out; Operator::FormLinearSystem(ess_tdof_list, x, b, A_out, X, B, copy_interior); delete A_out; FormSystemMatrix(ess_tdof_list, A); } void FABilinearFormExtension::DGMult(const Vector &x, Vector &y) const { #ifdef MFEM_USE_MPI const ParFiniteElementSpace *pfes; if ( (pfes = dynamic_cast(test_fes)) ) { // DG Prolongation ParGridFunction x_gf; x_gf.MakeRef(const_cast(pfes), const_cast(x),0); x_gf.ExchangeFaceNbrData(); Vector &shared_x = x_gf.FaceNbrData(); const int local_size = a->FESpace()->GetVSize(); auto dg_x_ptr = dg_x.Write(); auto x_ptr = x.Read(); mfem::forall(local_size, [=] MFEM_HOST_DEVICE (int i) { dg_x_ptr[i] = x_ptr[i]; }); const int shared_size = shared_x.Size(); auto shared_x_ptr = shared_x.Read(); mfem::forall(shared_size, [=] MFEM_HOST_DEVICE (int i) { dg_x_ptr[local_size+i] = shared_x_ptr[i]; }); ParBilinearForm *pform = nullptr; if ((pform = dynamic_cast(a)) && (pform->keep_nbr_block)) { mat->Mult(dg_x, dg_y); // DG Restriction auto dg_y_ptr = dg_y.Read(); auto y_ptr = y.ReadWrite(); mfem::forall(local_size, [=] MFEM_HOST_DEVICE (int i) { y_ptr[i] += dg_y_ptr[i]; }); } else { mat->Mult(dg_x, y); } } else #endif { mat->Mult(x, y); } } void FABilinearFormExtension::Mult(const Vector &x, Vector &y) const { if ( a->GetFBFI()->Size()>0 ) { DGMult(x, y); } else { mat->Mult(x, y); } } void FABilinearFormExtension::DGMultTranspose(const Vector &x, Vector &y) const { #ifdef MFEM_USE_MPI const ParFiniteElementSpace *pfes; if ( (pfes = dynamic_cast(test_fes)) ) { // DG Prolongation ParGridFunction x_gf; x_gf.MakeRef(const_cast(pfes), const_cast(x),0); x_gf.ExchangeFaceNbrData(); Vector &shared_x = x_gf.FaceNbrData(); const int local_size = a->FESpace()->GetVSize(); auto dg_x_ptr = dg_x.Write(); auto x_ptr = x.Read(); mfem::forall(local_size, [=] MFEM_HOST_DEVICE (int i) { dg_x_ptr[i] = x_ptr[i]; }); const int shared_size = shared_x.Size(); auto shared_x_ptr = shared_x.Read(); mfem::forall(shared_size, [=] MFEM_HOST_DEVICE (int i) { dg_x_ptr[local_size+i] = shared_x_ptr[i]; }); ParBilinearForm *pb = nullptr; if ((pb = dynamic_cast(a)) && (pb->keep_nbr_block)) { mat->MultTranspose(dg_x, dg_y); // DG Restriction auto dg_y_ptr = dg_y.Read(); auto y_ptr = y.ReadWrite(); mfem::forall(local_size, [=] MFEM_HOST_DEVICE (int i) { y_ptr[i] += dg_y_ptr[i]; }); } else { mat->MultTranspose(dg_x, y); } } else #endif { mat->MultTranspose(x, y); } } void FABilinearFormExtension::MultTranspose(const Vector &x, Vector &y) const { if ( a->GetFBFI()->Size()>0 ) { DGMultTranspose(x, y); } else { mat->MultTranspose(x, y); } } MixedBilinearFormExtension::MixedBilinearFormExtension(MixedBilinearForm *form) : Operator(form->Height(), form->Width()), a(form) { // empty } const Operator *MixedBilinearFormExtension::GetProlongation() const { return a->GetProlongation(); } const Operator *MixedBilinearFormExtension::GetRestriction() const { return a->GetRestriction(); } const Operator *MixedBilinearFormExtension::GetOutputProlongation() const { return a->GetOutputProlongation(); } const Operator *MixedBilinearFormExtension::GetOutputRestriction() const { return a->GetOutputRestriction(); } // Data and methods for partially-assembled bilinear forms PAMixedBilinearFormExtension::PAMixedBilinearFormExtension( MixedBilinearForm *form) : MixedBilinearFormExtension(form), trial_fes(form->TrialFESpace()), test_fes(form->TestFESpace()), elem_restrict_trial(NULL), elem_restrict_test(NULL) { Update(); } void PAMixedBilinearFormExtension::Assemble() { Array &integrators = *a->GetDBFI(); const int integratorCount = integrators.Size(); for (int i = 0; i < integratorCount; ++i) { integrators[i]->AssemblePA(*trial_fes, *test_fes); } MFEM_VERIFY(a->GetBBFI()->Size() == 0, "Partial assembly does not support AddBoundaryIntegrator yet."); MFEM_VERIFY(a->GetTFBFI()->Size() == 0, "Partial assembly does not support AddTraceFaceIntegrator yet."); MFEM_VERIFY(a->GetBTFBFI()->Size() == 0, "Partial assembly does not support AddBdrTraceFaceIntegrator yet."); } void PAMixedBilinearFormExtension::Update() { trial_fes = a->TrialFESpace(); test_fes = a->TestFESpace(); height = test_fes->GetVSize(); width = trial_fes->GetVSize(); elem_restrict_trial = trial_fes->GetElementRestriction( ElementDofOrdering::LEXICOGRAPHIC); elem_restrict_test = test_fes->GetElementRestriction( ElementDofOrdering::LEXICOGRAPHIC); if (elem_restrict_trial) { localTrial.UseDevice(true); localTrial.SetSize(elem_restrict_trial->Height(), Device::GetMemoryType()); } if (elem_restrict_test) { localTest.UseDevice(true); // ensure 'localY = 0.0' is done on device localTest.SetSize(elem_restrict_test->Height(), Device::GetMemoryType()); } } void PAMixedBilinearFormExtension::FormRectangularSystemOperator( const Array &trial_tdof_list, const Array &test_tdof_list, OperatorHandle &A) { Operator * oper; Operator::FormRectangularSystemOperator(trial_tdof_list, test_tdof_list, oper); A.Reset(oper); // A will own oper } void PAMixedBilinearFormExtension::FormRectangularLinearSystem( const Array &trial_tdof_list, const Array &test_tdof_list, Vector &x, Vector &b, OperatorHandle &A, Vector &X, Vector &B) { Operator *oper; Operator::FormRectangularLinearSystem(trial_tdof_list, test_tdof_list, x, b, oper, X, B); A.Reset(oper); // A will own oper } void PAMixedBilinearFormExtension::SetupMultInputs( const Operator *elem_restrict_x, const Vector &x, Vector &localX, const Operator *elem_restrict_y, Vector &y, Vector &localY, const real_t c) const { // * G operation: localX = c*local(x) if (elem_restrict_x) { elem_restrict_x->Mult(x, localX); if (c != 1.0) { localX *= c; } } else { if (c == 1.0) { localX.SyncAliasMemory(x); } else { localX.Set(c, x); } } if (elem_restrict_y) { localY = 0.0; } else { y.UseDevice(true); localY.SyncAliasMemory(y); } } void PAMixedBilinearFormExtension::Mult(const Vector &x, Vector &y) const { y = 0.0; AddMult(x, y); } void PAMixedBilinearFormExtension::AddMult(const Vector &x, Vector &y, const real_t c) const { Array &integrators = *a->GetDBFI(); const int iSz = integrators.Size(); // * G operation SetupMultInputs(elem_restrict_trial, x, localTrial, elem_restrict_test, y, localTest, c); // * B^TDB operation for (int i = 0; i < iSz; ++i) { integrators[i]->AddMultPA(localTrial, localTest); } // * G^T operation if (elem_restrict_test) { tempY.SetSize(y.Size()); elem_restrict_test->MultTranspose(localTest, tempY); y += tempY; } } void PAMixedBilinearFormExtension::MultTranspose(const Vector &x, Vector &y) const { y = 0.0; AddMultTranspose(x, y); } void PAMixedBilinearFormExtension::AddMultTranspose(const Vector &x, Vector &y, const real_t c) const { Array &integrators = *a->GetDBFI(); const int iSz = integrators.Size(); // * G operation SetupMultInputs(elem_restrict_test, x, localTest, elem_restrict_trial, y, localTrial, c); // * B^TD^TB operation for (int i = 0; i < iSz; ++i) { integrators[i]->AddMultTransposePA(localTest, localTrial); } // * G^T operation if (elem_restrict_trial) { tempY.SetSize(y.Size()); elem_restrict_trial->MultTranspose(localTrial, tempY); y += tempY; } } void PAMixedBilinearFormExtension::AssembleDiagonal_ADAt(const Vector &D, Vector &diag) const { Array &integrators = *a->GetDBFI(); const int iSz = integrators.Size(); if (elem_restrict_trial) { const ElementRestriction* H1elem_restrict_trial = dynamic_cast(elem_restrict_trial); if (H1elem_restrict_trial) { H1elem_restrict_trial->AbsMult(D, localTrial); } else { elem_restrict_trial->Mult(D, localTrial); } } if (elem_restrict_test) { localTest = 0.0; for (int i = 0; i < iSz; ++i) { if (elem_restrict_trial) { integrators[i]->AssembleDiagonalPA_ADAt(localTrial, localTest); } else { integrators[i]->AssembleDiagonalPA_ADAt(D, localTest); } } const ElementRestriction* H1elem_restrict_test = dynamic_cast(elem_restrict_test); if (H1elem_restrict_test) { H1elem_restrict_test->AbsMultTranspose(localTest, diag); } else { elem_restrict_test->MultTranspose(localTest, diag); } } else { diag.UseDevice(true); // typically this is a large vector, so store on device diag = 0.0; for (int i = 0; i < iSz; ++i) { if (elem_restrict_trial) { integrators[i]->AssembleDiagonalPA_ADAt(localTrial, diag); } else { integrators[i]->AssembleDiagonalPA_ADAt(D, diag); } } } } PADiscreteLinearOperatorExtension::PADiscreteLinearOperatorExtension( DiscreteLinearOperator *linop) : PAMixedBilinearFormExtension(linop) { } const Operator *PADiscreteLinearOperatorExtension::GetOutputRestrictionTranspose() const { return a->GetOutputRestrictionTranspose(); } void PADiscreteLinearOperatorExtension::Assemble() { Array &integrators = *a->GetDBFI(); const int integratorCount = integrators.Size(); for (int i = 0; i < integratorCount; ++i) { integrators[i]->AssemblePA(*trial_fes, *test_fes); } test_multiplicity.UseDevice(true); test_multiplicity.SetSize(elem_restrict_test->Width()); // l-vector Vector ones(elem_restrict_test->Height()); // e-vector ones = 1.0; const ElementRestriction* elem_restrict = dynamic_cast(elem_restrict_test); if (elem_restrict) { elem_restrict->AbsMultTranspose(ones, test_multiplicity); } else { mfem_error("A real ElementRestriction is required in this setting!"); } auto tm = test_multiplicity.ReadWrite(); mfem::forall(test_multiplicity.Size(), [=] MFEM_HOST_DEVICE (int i) { tm[i] = 1.0 / tm[i]; }); } void PADiscreteLinearOperatorExtension::AddMult( const Vector &x, Vector &y, const real_t c) const { Array &integrators = *a->GetDBFI(); const int iSz = integrators.Size(); // * G operation SetupMultInputs(elem_restrict_trial, x, localTrial, elem_restrict_test, y, localTest, c); // * B^TDB operation for (int i = 0; i < iSz; ++i) { integrators[i]->AddMultPA(localTrial, localTest); } // do a kind of "set" rather than "add" in the below // operation as compared to the BilinearForm case // * G^T operation (kind of...) const ElementRestriction* elem_restrict = dynamic_cast(elem_restrict_test); if (elem_restrict) { tempY.SetSize(y.Size()); elem_restrict->MultLeftInverse(localTest, tempY); y += tempY; } else { mfem_error("In this setting you need a real ElementRestriction!"); } } void PADiscreteLinearOperatorExtension::AddMultTranspose( const Vector &x, Vector &y, const real_t c) const { Array &integrators = *a->GetDBFI(); const int iSz = integrators.Size(); // do a kind of "set" rather than "add" in the below // operation as compared to the BilinearForm case // * G operation (kinda) Vector xscaled(x); MFEM_VERIFY(x.Size() == test_multiplicity.Size(), "Input vector of wrong size"); auto xs = xscaled.ReadWrite(); auto tm = test_multiplicity.Read(); mfem::forall(x.Size(), [=] MFEM_HOST_DEVICE (int i) { xs[i] *= tm[i]; }); SetupMultInputs(elem_restrict_test, xscaled, localTest, elem_restrict_trial, y, localTrial, c); // * B^TD^TB operation for (int i = 0; i < iSz; ++i) { integrators[i]->AddMultTransposePA(localTest, localTrial); } // * G^T operation if (elem_restrict_trial) { tempY.SetSize(y.Size()); elem_restrict_trial->MultTranspose(localTrial, tempY); y += tempY; } else { mfem_error("Trial ElementRestriction not defined"); } } void PADiscreteLinearOperatorExtension::FormRectangularSystemOperator( const Array& ess1, const Array& ess2, OperatorHandle &A) { const Operator *Pi = this->GetProlongation(); const Operator *RoT = this->GetOutputRestrictionTranspose(); Operator *rap = SetupRAP(Pi, RoT); RectangularConstrainedOperator *Arco = new RectangularConstrainedOperator(rap, ess1, ess2, rap != this); A.Reset(Arco); } } // namespace mfem