Files
mfem/fem/lor/lor.cpp
T
2026-02-24 11:17:54 -08:00

537 lines
17 KiB
C++

// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "lor.hpp"
#include "lor_batched.hpp"
#include "../restriction.hpp"
#include "../pbilinearform.hpp"
#include "../../general/forall.hpp"
namespace mfem
{
void LORBase::AddIntegrators(BilinearForm &a_from,
BilinearForm &a_to,
GetIntegratorsFn get_integrators,
AddIntegratorFn add_integrator,
const IntegrationRule *ir)
{
Array<BilinearFormIntegrator*> *integrators = (a_from.*get_integrators)();
for (int i=0; i<integrators->Size(); ++i)
{
BilinearFormIntegrator *integrator = (*integrators)[i];
(a_to.*add_integrator)(integrator);
ir_map[integrator] = integrator->GetIntRule();
if (ir) { integrator->SetIntegrationRule(*ir); }
}
}
void LORBase::AddIntegratorsAndMarkers(BilinearForm &a_from,
BilinearForm &a_to,
GetIntegratorsFn get_integrators,
GetMarkersFn get_markers,
AddIntegratorMarkersFn add_integrator_marker,
AddIntegratorFn add_integrator,
const IntegrationRule *ir)
{
Array<BilinearFormIntegrator*> *integrators = (a_from.*get_integrators)();
Array<Array<int>*> &markers = *(a_from.*get_markers)();
for (int i=0; i<integrators->Size(); ++i)
{
BilinearFormIntegrator *integrator = (*integrators)[i];
if (markers[i] != nullptr)
{
(a_to.*add_integrator_marker)(integrator, *markers[i]);
}
else
{
(a_to.*add_integrator)(integrator);
}
ir_map[integrator] = integrator->GetIntRule();
if (ir) { integrator->SetIntegrationRule(*ir); }
}
}
void LORBase::ResetIntegrationRules(GetIntegratorsFn get_integrators)
{
Array<BilinearFormIntegrator*> *integrators = (a->*get_integrators)();
for (int i=0; i<integrators->Size(); ++i)
{
((*integrators)[i])->SetIntRule(ir_map[(*integrators)[i]]);
}
}
LORBase::FESpaceType LORBase::GetFESpaceType() const
{
const FiniteElementCollection *fec_ho = fes_ho.FEColl();
if (dynamic_cast<const H1_FECollection*>(fec_ho)) { return H1; }
else if (dynamic_cast<const ND_FECollection*>(fec_ho)) { return ND; }
else if (dynamic_cast<const RT_FECollection*>(fec_ho)) { return RT; }
else if (dynamic_cast<const L2_FECollection*>(fec_ho)) { return L2; }
else { MFEM_ABORT("Bad LOR space type."); }
return INVALID;
}
int LORBase::GetLOROrder() const
{
FESpaceType type = GetFESpaceType();
return (type == L2 || type == RT) ? 0 : 1;
}
void LORBase::ConstructLocalDofPermutation(Array<int> &perm_) const
{
FESpaceType type = GetFESpaceType();
MFEM_VERIFY(type != H1 && type != L2, "");
auto get_dof_map = [](FiniteElementSpace &fes_, int i)
{
const FiniteElement *fe = fes_.GetFE(i);
auto tfe = dynamic_cast<const TensorBasisElement*>(fe);
MFEM_ASSERT(tfe != NULL, "");
return tfe->GetDofMap();
};
FiniteElementSpace &fes_lor = GetFESpace();
Mesh &mesh_lor = *fes_lor.GetMesh();
int dim = mesh_lor.Dimension();
const CoarseFineTransformations &cf_tr = mesh_lor.GetRefinementTransforms();
using GeomRef = std::pair<Geometry::Type, int>;
std::map<GeomRef, int> point_matrices_offsets;
perm_.SetSize(fes_lor.GetVSize());
Array<int> vdof_ho, vdof_lor;
for (int ilor=0; ilor<mesh_lor.GetNE(); ++ilor)
{
int iho = cf_tr.embeddings[ilor].parent;
int p = fes_ho.GetOrder(iho);
int lor_index = cf_tr.embeddings[ilor].matrix;
// We use the point matrix index to identify the local LOR element index
// within the high-order coarse element.
//
// In variable-order spaces, the point matrices for each order are
// concatenated sequentially, so for the given element order, we need to
// find the offset that will give us the point matrix index relative to
// the current element order only.
GeomRef id(mesh_lor.GetElementBaseGeometry(ilor), p);
if (point_matrices_offsets.find(id) == point_matrices_offsets.end())
{
point_matrices_offsets[id] = lor_index;
}
lor_index -= point_matrices_offsets[id];
fes_ho.GetElementVDofs(iho, vdof_ho);
fes_lor.GetElementVDofs(ilor, vdof_lor);
if (type == L2)
{
perm_[vdof_lor[0]] = vdof_ho[lor_index];
continue;
}
int p1 = p+1;
int ndof_per_dim = (dim == 2) ? p*p1 : type == ND ? p*p1*p1 : p*p*p1;
const Array<int> &dofmap_ho = get_dof_map(fes_ho, iho);
const Array<int> &dofmap_lor = get_dof_map(fes_lor, ilor);
int off_x = lor_index % p;
int off_y = (lor_index / p) % p;
int off_z = (lor_index / p) / p;
auto set_perm = [&](int off_lor, int off_ho, int n1, int n2)
{
for (int i1=0; i1<2; ++i1)
{
int m = (dim == 2 || type == RT) ? 1 : 2;
for (int i2=0; i2<m; ++i2)
{
int i;
i = dofmap_lor[off_lor + i1 + i2*2];
int s1 = i < 0 ? -1 : 1;
int idof_lor = vdof_lor[UnsignIndex(i)];
i = dofmap_ho[off_ho + i1*n1 + i2*n2];
int s2 = i < 0 ? -1 : 1;
int idof_ho = vdof_ho[UnsignIndex(i)];
int s3 = idof_lor < 0 ? -1 : 1;
int s4 = idof_ho < 0 ? -1 : 1;
int s = s1*s2*s3*s4;
i = UnsignIndex(idof_ho);
perm_[UnsignIndex(idof_lor)] = s < 0 ? -1-UnsignIndex(i) :
UnsignIndex(i);
}
}
};
int offset;
if (type == ND)
{
// x
offset = off_x + off_y*p + off_z*p*p1;
set_perm(0, offset, p, p*p1);
// y
offset = ndof_per_dim + off_x + off_y*(p1) + off_z*p1*p;
set_perm(dim == 2 ? 2 : 4, offset, 1, p*p1);
// z
if (dim == 3)
{
offset = 2*ndof_per_dim + off_x + off_y*p1 + off_z*p1*p1;
set_perm(8, offset, 1, p+1);
}
}
else if (type == RT)
{
// x
offset = off_x + off_y*p1 + off_z*p*p1;
set_perm(0, offset, 1, 0);
// y
offset = ndof_per_dim + off_x + off_y*p + off_z*p1*p;
set_perm(2, offset, p, 0);
// z
if (dim == 3)
{
offset = 2*ndof_per_dim + off_x + off_y*p + off_z*p*p;
set_perm(4, offset, p*p, 0);
}
}
}
}
void LORBase::ConstructDofPermutation() const
{
FESpaceType type = GetFESpaceType();
if (type == H1 || type == L2)
{
// H1 and L2: no permutation necessary, return identity
perm.SetSize(fes_ho.GetTrueVSize());
for (int i=0; i<perm.Size(); ++i) { perm[i] = i; }
return;
}
#ifdef MFEM_USE_MPI
ParFiniteElementSpace *pfes_ho
= dynamic_cast<ParFiniteElementSpace*>(&fes_ho);
ParFiniteElementSpace *pfes_lor
= dynamic_cast<ParFiniteElementSpace*>(&GetFESpace());
if (pfes_ho && pfes_lor)
{
Array<int> l_perm;
ConstructLocalDofPermutation(l_perm);
perm.SetSize(pfes_lor->GetTrueVSize());
for (int i=0; i<l_perm.Size(); ++i)
{
int j = l_perm[i];
int s = j < 0 ? -1 : 1;
int t_i = pfes_lor->GetLocalTDofNumber(i);
int t_j = pfes_ho->GetLocalTDofNumber(UnsignIndex(j));
// Either t_i and t_j both -1, or both non-negative
if ((t_i < 0 && t_j >=0) || (t_j < 0 && t_i >= 0))
{
MFEM_ABORT("Inconsistent DOF numbering");
}
if (t_i < 0) { continue; }
perm[t_i] = s < 0 ? -1 - t_j : t_j;
}
}
else
#endif
{
ConstructLocalDofPermutation(perm);
}
}
const Array<int> &LORBase::GetDofPermutation() const
{
if (perm.Size() == 0) { ConstructDofPermutation(); }
return perm;
}
bool LORBase::HasSameDofNumbering() const
{
FESpaceType type = GetFESpaceType();
return type == H1 || type == L2;
}
OperatorHandle &LORBase::GetAssembledSystem()
{
MFEM_VERIFY(A.Ptr() != NULL, "No LOR system assembled");
return A;
}
const OperatorHandle &LORBase::GetAssembledSystem() const
{
MFEM_VERIFY(A.Ptr() != NULL, "No LOR system assembled");
return A;
}
void LORBase::SetupProlongationAndRestriction()
{
if (!HasSameDofNumbering())
{
Array<int> p;
ConstructLocalDofPermutation(p);
fes->CopyProlongationAndRestriction(fes_ho, &p);
}
else
{
fes->CopyProlongationAndRestriction(fes_ho, NULL);
}
}
template <typename FEC>
void CheckScalarBasisType(const FiniteElementSpace &fes)
{
const FEC *fec = dynamic_cast<const FEC*>(fes.FEColl());
if (fec)
{
int btype = fec->GetBasisType();
if (btype != BasisType::GaussLobatto)
{
mfem::err << "\nWARNING: Constructing low-order refined "
<< "discretization with basis type\n"
<< BasisType::Name(btype) << ". "
<< "The LOR discretization is only spectrally equivalent\n"
<< "with Gauss-Lobatto basis.\n" << std::endl;
}
}
}
template <typename FEC>
void CheckVectorBasisType(const FiniteElementSpace &fes)
{
const FEC *fec = dynamic_cast<const FEC*>(fes.FEColl());
if (fec)
{
int cbtype = fec->GetClosedBasisType();
int obtype = fec->GetOpenBasisType();
if (cbtype != BasisType::GaussLobatto || obtype != BasisType::IntegratedGLL)
{
mfem::err << "\nWARNING: Constructing vector low-order refined "
<< "discretization with basis type \npair ("
<< BasisType::Name(cbtype) << ", "
<< BasisType::Name(obtype) << "). "
<< "The LOR discretization is only spectrally\nequivalent "
<< "with basis types (Gauss-Lobatto, IntegratedGLL).\n"
<< std::endl;
}
}
}
void CheckBasisType(const FiniteElementSpace &fes)
{
CheckScalarBasisType<H1_FECollection>(fes);
CheckVectorBasisType<ND_FECollection>(fes);
CheckVectorBasisType<RT_FECollection>(fes);
// L2 is a bit more complicated, for now don't verify basis type
}
LORBase::LORBase(FiniteElementSpace &fes_ho_, int ref_type_)
: irs(0, Quadrature1D::GaussLobatto), ref_type(ref_type_), fes_ho(fes_ho_)
{
Mesh &mesh_ = *fes_ho_.GetMesh();
int dim = mesh_.Dimension();
Array<Geometry::Type> geoms;
mesh_.GetGeometries(dim, geoms);
if (geoms.Size() == 1 && Geometry::IsTensorProduct(geoms[0]))
{
ir_el = &irs.Get(geoms[0], 1);
ir_face = &irs.Get(Geometry::TensorProductGeometry(dim-1), 1);
}
else
{
ir_el = NULL;
ir_face = NULL;
}
a = NULL;
}
FiniteElementSpace &LORBase::GetFESpace() const
{
// In the case of "batched assembly", the creation of the LOR mesh and
// space can be completely omitted (for efficiency). In this case, the
// fes object is NULL, and we need to create it when requested.
if (fes == NULL) { const_cast<LORBase*>(this)->FormLORSpace(); }
return *fes;
}
void LORBase::AssembleSystem(BilinearForm &a_ho, const Array<int> &ess_dofs)
{
A.Clear();
delete a;
if (BatchedLORAssembly::FormIsSupported(a_ho))
{
// Skip forming the space
a = nullptr;
if (batched_lor == nullptr)
{
batched_lor = new BatchedLORAssembly(fes_ho);
}
batched_lor->Assemble(a_ho, ess_dofs, A);
}
else
{
LegacyAssembleSystem(a_ho, ess_dofs);
}
}
void LORBase::LegacyAssembleSystem(BilinearForm &a_ho,
const Array<int> &ess_dofs)
{
// TODO: use AssemblyLevel::FULL here instead of AssemblyLevel::LEGACY.
// This is waiting for parallel assembly + BCs with AssemblyLevel::FULL.
// In that case, maybe "LegacyAssembleSystem" is not a very clear name.
// If the space is not formed already, it will be constructed lazily in
// GetFESpace.
FiniteElementSpace &fes_lor = GetFESpace();
#ifdef MFEM_USE_MPI
if (auto *pfes = dynamic_cast<ParFiniteElementSpace*>(&fes_lor))
{
a = new ParBilinearForm(pfes);
}
else
#endif
{
a = new BilinearForm(&fes_lor);
}
a->UseExternalIntegrators();
AddIntegrators(a_ho, *a, &BilinearForm::GetDBFI,
&BilinearForm::AddDomainIntegrator, ir_el);
AddIntegrators(a_ho, *a, &BilinearForm::GetFBFI,
&BilinearForm::AddInteriorFaceIntegrator, ir_face);
AddIntegratorsAndMarkers(a_ho, *a, &BilinearForm::GetBBFI,
&BilinearForm::GetBBFI_Marker,
&BilinearForm::AddBoundaryIntegrator,
&BilinearForm::AddBoundaryIntegrator, ir_face);
AddIntegratorsAndMarkers(a_ho, *a, &BilinearForm::GetBFBFI,
&BilinearForm::GetBFBFI_Marker,
&BilinearForm::AddBdrFaceIntegrator,
&BilinearForm::AddBdrFaceIntegrator, ir_face);
a->Assemble();
a->FormSystemMatrix(ess_dofs, A);
ResetIntegrationRules(&BilinearForm::GetDBFI);
ResetIntegrationRules(&BilinearForm::GetFBFI);
ResetIntegrationRules(&BilinearForm::GetBBFI);
ResetIntegrationRules(&BilinearForm::GetBFBFI);
}
LORBase::~LORBase()
{
delete batched_lor;
delete a;
delete fes;
delete fec;
delete mesh;
}
LORDiscretization::LORDiscretization(BilinearForm &a_ho_,
const Array<int> &ess_tdof_list,
int ref_type_)
: LORBase(*a_ho_.FESpace(), ref_type_)
{
CheckBasisType(fes_ho);
A.SetType(Operator::MFEM_SPARSEMAT);
AssembleSystem(a_ho_, ess_tdof_list);
}
LORDiscretization::LORDiscretization(FiniteElementSpace &fes_ho,
int ref_type_) : LORBase(fes_ho, ref_type_)
{
CheckBasisType(fes_ho);
A.SetType(Operator::MFEM_SPARSEMAT);
}
void LORDiscretization::FormLORSpace()
{
Mesh &mesh_ho = *fes_ho.GetMesh();
// For H1, ND and RT spaces, use refinement = element order, for DG spaces,
// use refinement = element order + 1 (since LOR is p = 0 in this case).
int increment = (GetFESpaceType() == L2) ? 1 : 0;
Array<int> refinements(mesh_ho.GetNE());
for (int i=0; i<refinements.Size(); ++i)
{
refinements[i] = fes_ho.GetOrder(i) + increment;
}
mesh = new Mesh(Mesh::MakeRefined(mesh_ho, refinements, ref_type));
fec = fes_ho.FEColl()->Clone(GetLOROrder());
const int vdim = fes_ho.GetVDim();
const Ordering::Type ordering = fes_ho.GetOrdering();
fes = new FiniteElementSpace(mesh, fec, vdim, ordering);
SetupProlongationAndRestriction();
}
SparseMatrix &LORDiscretization::GetAssembledMatrix() const
{
MFEM_VERIFY(A.Ptr() != nullptr, "No LOR system assembled");
return *A.As<SparseMatrix>();
}
#ifdef MFEM_USE_MPI
ParLORDiscretization::ParLORDiscretization(ParBilinearForm &a_ho_,
const Array<int> &ess_tdof_list,
int ref_type_) : LORBase(*a_ho_.ParFESpace(), ref_type_)
{
ParFiniteElementSpace *pfes_ho = a_ho_.ParFESpace();
if (pfes_ho->GetMyRank() == 0) { CheckBasisType(fes_ho); }
A.SetType(Operator::Hypre_ParCSR);
AssembleSystem(a_ho_, ess_tdof_list);
}
ParLORDiscretization::ParLORDiscretization(
ParFiniteElementSpace &fes_ho, int ref_type_) : LORBase(fes_ho, ref_type_)
{
if (fes_ho.GetMyRank() == 0) { CheckBasisType(fes_ho); }
A.SetType(Operator::Hypre_ParCSR);
}
void ParLORDiscretization::FormLORSpace()
{
ParFiniteElementSpace &pfes_ho = static_cast<ParFiniteElementSpace&>(fes_ho);
// TODO: support variable-order spaces in parallel
MFEM_VERIFY(!pfes_ho.IsVariableOrder(),
"Cannot construct LOR operators on variable-order spaces");
int order = pfes_ho.GetMaxElementOrder();
if (GetFESpaceType() == L2) { ++order; }
ParMesh &mesh_ho = *pfes_ho.GetParMesh();
ParMesh *pmesh = new ParMesh(ParMesh::MakeRefined(mesh_ho, order, ref_type));
mesh = pmesh;
fec = pfes_ho.FEColl()->Clone(GetLOROrder());
const int vdim = fes_ho.GetVDim();
const Ordering::Type ordering = fes_ho.GetOrdering();
fes = new ParFiniteElementSpace(pmesh, fec, vdim, ordering);
SetupProlongationAndRestriction();
}
HypreParMatrix &ParLORDiscretization::GetAssembledMatrix() const
{
MFEM_VERIFY(A.Ptr() != nullptr, "No LOR system assembled");
return *A.As<HypreParMatrix>();
}
ParFiniteElementSpace &ParLORDiscretization::GetParFESpace() const
{
return static_cast<ParFiniteElementSpace&>(GetFESpace());
}
#endif // MFEM_USE_MPI
} // namespace mfem