Files
mfem/fem/transfer.cpp
T
2025-11-12 16:31:18 -08:00

2668 lines
81 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 "transfer.hpp"
#include "bilinearform.hpp"
#include "pbilinearform.hpp"
#include "../general/forall.hpp"
namespace mfem
{
GridTransfer::GridTransfer(FiniteElementSpace &dom_fes_,
FiniteElementSpace &ran_fes_)
: dom_fes(dom_fes_), ran_fes(ran_fes_),
oper_type(Operator::ANY_TYPE),
fw_t_oper(), bw_t_oper(), use_ea(false), d_mt(Device::GetHostMemoryType())
{
#ifdef MFEM_USE_MPI
const bool par_dom = dynamic_cast<ParFiniteElementSpace*>(&dom_fes);
const bool par_ran = dynamic_cast<ParFiniteElementSpace*>(&ran_fes);
MFEM_VERIFY(par_dom == par_ran, "the domain and range FE spaces must both"
" be either serial or parallel");
parallel = par_dom;
#endif
}
const Operator &GridTransfer::MakeTrueOperator(
FiniteElementSpace &fes_in, FiniteElementSpace &fes_out,
const Operator &oper, OperatorHandle &t_oper)
{
if (t_oper.Ptr())
{
return *t_oper.Ptr();
}
if (!Parallel())
{
const SparseMatrix *in_cP = fes_in.GetConformingProlongation();
const SparseMatrix *out_cR = fes_out.GetConformingRestriction();
if (oper_type == Operator::MFEM_SPARSEMAT)
{
const SparseMatrix *mat = dynamic_cast<const SparseMatrix *>(&oper);
MFEM_VERIFY(mat != NULL, "Operator is not a SparseMatrix");
if (!out_cR)
{
t_oper.Reset(const_cast<SparseMatrix*>(mat), false);
}
else
{
t_oper.Reset(mfem::Mult(*out_cR, *mat));
}
if (in_cP)
{
t_oper.Reset(mfem::Mult(*t_oper.As<SparseMatrix>(), *in_cP));
}
}
else if (oper_type == Operator::ANY_TYPE)
{
const int RP_case = bool(out_cR) + 2*bool(in_cP);
switch (RP_case)
{
case 0:
t_oper.Reset(const_cast<Operator*>(&oper), false);
break;
case 1:
t_oper.Reset(
new ProductOperator(out_cR, &oper, false, false));
break;
case 2:
t_oper.Reset(
new ProductOperator(&oper, in_cP, false, false));
break;
case 3:
t_oper.Reset(
new TripleProductOperator(
out_cR, &oper, in_cP, false, false, false));
break;
}
}
else
{
MFEM_ABORT("Operator::Type is not supported: " << oper_type);
}
}
else // Parallel() == true
{
#ifdef MFEM_USE_MPI
if (oper_type == Operator::Hypre_ParCSR)
{
const SparseMatrix *out_R = fes_out.GetRestrictionMatrix();
const ParFiniteElementSpace *pfes_in =
dynamic_cast<const ParFiniteElementSpace *>(&fes_in);
const ParFiniteElementSpace *pfes_out =
dynamic_cast<const ParFiniteElementSpace *>(&fes_out);
const SparseMatrix *sp_mat = dynamic_cast<const SparseMatrix *>(&oper);
const HypreParMatrix *hy_mat;
if (sp_mat)
{
SparseMatrix *RA = mfem::Mult(*out_R, *sp_mat);
t_oper.Reset(pfes_in->Dof_TrueDof_Matrix()->
LeftDiagMult(*RA, pfes_out->GetTrueDofOffsets()));
delete RA;
}
else if ((hy_mat = dynamic_cast<const HypreParMatrix *>(&oper)))
{
HypreParMatrix *RA =
hy_mat->LeftDiagMult(*out_R, pfes_out->GetTrueDofOffsets());
t_oper.Reset(mfem::ParMult(RA, pfes_in->Dof_TrueDof_Matrix()));
delete RA;
}
else
{
MFEM_ABORT("unknown Operator type");
}
}
else if (oper_type == Operator::ANY_TYPE)
{
const Operator *out_R = fes_out.GetRestrictionOperator();
t_oper.Reset(new TripleProductOperator(
out_R, &oper, fes_in.GetProlongationMatrix(),
false, false, false));
}
else
{
MFEM_ABORT("Operator::Type is not supported: " << oper_type);
}
#endif
}
return *t_oper.Ptr();
}
InterpolationGridTransfer::~InterpolationGridTransfer()
{
if (own_mass_integ) { delete mass_integ; }
}
void InterpolationGridTransfer::SetMassIntegrator(
BilinearFormIntegrator *mass_integ_, bool own_mass_integ_)
{
if (own_mass_integ) { delete mass_integ; }
mass_integ = mass_integ_;
own_mass_integ = own_mass_integ_;
}
const Operator &InterpolationGridTransfer::ForwardOperator()
{
if (F.Ptr())
{
return *F.Ptr();
}
// Construct F
if (oper_type == Operator::ANY_TYPE)
{
F.Reset(new FiniteElementSpace::RefinementOperator(&ran_fes, &dom_fes));
}
else if (oper_type == Operator::MFEM_SPARSEMAT)
{
Mesh::GeometryList elem_geoms(*ran_fes.GetMesh());
DenseTensor localP[Geometry::NumGeom];
for (int i = 0; i < elem_geoms.Size(); i++)
{
ran_fes.GetLocalRefinementMatrices(dom_fes, elem_geoms[i],
localP[elem_geoms[i]]);
}
F.Reset(ran_fes.RefinementMatrix_main(
dom_fes.GetNDofs(), dom_fes.GetElementToDofTable(),
dom_fes.GetElementToFaceOrientationTable(), localP));
}
else
{
MFEM_ABORT("Operator::Type is not supported: " << oper_type);
}
return *F.Ptr();
}
const Operator &InterpolationGridTransfer::BackwardOperator()
{
if (B.Ptr())
{
return *B.Ptr();
}
// Construct B, if not set, define a suitable mass_integ
if (!mass_integ)
{
const FiniteElement *f_fe_0 = ran_fes.GetTypicalFE();
const int map_type = f_fe_0->GetMapType();
if (map_type == FiniteElement::VALUE ||
map_type == FiniteElement::INTEGRAL)
{
mass_integ = new MassIntegrator;
}
else if (map_type == FiniteElement::H_DIV ||
map_type == FiniteElement::H_CURL)
{
mass_integ = new VectorFEMassIntegrator;
}
else
{
MFEM_ABORT("unknown type of FE space");
}
own_mass_integ = true;
}
if (oper_type == Operator::ANY_TYPE)
{
B.Reset(new FiniteElementSpace::DerefinementOperator(
&ran_fes, &dom_fes, mass_integ));
}
else
{
MFEM_ABORT("Operator::Type is not supported: " << oper_type);
}
return *B.Ptr();
}
L2ProjectionGridTransfer::L2Projection::L2Projection(
const FiniteElementSpace &fes_ho_, const FiniteElementSpace &fes_lor_,
MemoryType d_mt_)
: Operator(fes_lor_.GetVSize(), fes_ho_.GetVSize()),
fes_ho(fes_ho_), fes_lor(fes_lor_), d_mt(d_mt_)
{ }
void L2ProjectionGridTransfer::L2Projection::BuildHo2Lor(
int nel_ho, int nel_lor, const CoarseFineTransformations& cf_tr)
{
// Construct the mapping from HO to LOR
// ho2lor.GetRow(iho) will give all the LOR elements contained in iho
ho2lor.MakeI(nel_ho);
for (int ilor = 0; ilor < nel_lor; ++ilor)
{
int iho = cf_tr.embeddings[ilor].parent;
ho2lor.AddAColumnInRow(iho);
}
ho2lor.MakeJ();
for (int ilor = 0; ilor < nel_lor; ++ilor)
{
int iho = cf_tr.embeddings[ilor].parent;
ho2lor.AddConnection(iho, ilor);
}
ho2lor.ShiftUpI();
}
void L2ProjectionGridTransfer::L2Projection::ElemMixedMass(
Geometry::Type geom, const FiniteElement& fe_ho,
const FiniteElement& fe_lor, ElementTransformation* tr_ho,
ElementTransformation* tr_lor,
IntegrationPointTransformation& ip_tr,
DenseMatrix& M_mixed_el) const
{
int order = fe_lor.GetOrder() + fe_ho.GetOrder() + tr_lor->OrderW();
const IntegrationRule* ir = &IntRules.Get(geom, order);
M_mixed_el = 0.0;
for (int i = 0; i < ir->GetNPoints(); i++)
{
const IntegrationPoint& ip_lor = ir->IntPoint(i);
IntegrationPoint ip_ho;
ip_tr.Transform(ip_lor, ip_ho);
Vector shape_lor(fe_lor.GetDof());
fe_lor.CalcShape(ip_lor, shape_lor);
Vector shape_ho(fe_ho.GetDof());
tr_ho->SetIntPoint(&ip_ho);
fe_ho.CalcPhysShape(*tr_ho, shape_ho);
tr_lor->SetIntPoint(&ip_lor);
// For now we use the geometry information from the LOR space, which means
// we won't be mass conservative if the mesh is curved
real_t w = ip_lor.weight;
if (fe_lor.GetMapType() == FiniteElement::VALUE)
{
w *= tr_lor->Weight();
}
shape_lor *= w;
AddMultVWt(shape_lor, shape_ho, M_mixed_el);
}
}
void L2ProjectionGridTransfer::L2Projection::ElemMixedMass(
Geometry::Type geom, const FiniteElement& fe_ho,
const FiniteElement& fe_lor, ElementTransformation* el_tr,
IntegrationPointTransformation& ip_tr,
DenseMatrix& B_L, DenseMatrix& B_H) const
{
int order = fe_lor.GetOrder() + fe_ho.GetOrder() + el_tr->OrderW();
const IntegrationRule* ir = &IntRules.Get(geom, order);
for (int i = 0; i < ir->GetNPoints(); i++)
{
const IntegrationPoint& ip_lor = ir->IntPoint(i);
IntegrationPoint ip_ho;
// maps integration point ip_lor -> ip_ho
ip_tr.Transform(ip_lor, ip_ho);
Vector shape_lor(fe_lor.GetDof());
fe_lor.CalcShape(ip_lor, shape_lor);
Vector shape_ho(fe_ho.GetDof());
fe_ho.CalcShape(ip_ho, shape_ho);
for (int j=0; j<shape_lor.Size(); ++j)
{
B_L(i, j) = shape_lor(j);
}
for (int j=0; j<shape_ho.Size(); ++j)
{
B_H(i, j) = shape_ho(j);
}
}
}
void L2ProjectionGridTransfer::L2Projection::MixedMassEA(
const FiniteElementSpace& fes_ho_ea,
const FiniteElementSpace& fes_lor_ea,
Vector &M_LH, MemoryType d_mt_)
{
Mesh* mesh_ho = fes_ho_ea.GetMesh();
Mesh* mesh_lor = fes_lor_ea.GetMesh();
int nel_ho = mesh_ho->GetNE();
int nel_lor = mesh_lor->GetNE();
if (nel_ho == 0)
{
M_LH.SetSize(0);
return;
}
const CoarseFineTransformations& cf_tr = mesh_lor->GetRefinementTransforms();
int nref_max = 0;
Array<Geometry::Type> geoms;
mesh_ho->GetGeometries(mesh_ho->Dimension(), geoms);
for (int ig = 0; ig < geoms.Size(); ++ig)
{
Geometry::Type geom = geoms[ig];
nref_max = std::max(nref_max, cf_tr.point_matrices[geom].SizeK());
}
BuildHo2Lor(nel_ho, nel_lor, cf_tr);
IntegrationPointTransformation ip_tr;
IsoparametricTransformation &emb_tr = ip_tr.Transf;
// Gather basis functions (B_L, B_HO) and data at quadrature points
DenseTensor B_L, B_H, D;
{
// Assume all HO elements are LOR in the same way
const int iho = 0;
{
Array<int> lor_els;
ho2lor.GetRow(iho, lor_els);
int nref = ho2lor.RowSize(iho);
Geometry::Type geom = mesh_ho->GetElementBaseGeometry(iho);
const FiniteElement &fe_ho = *fes_ho_ea.GetFE(iho);
const FiniteElement &fe_lor = *fes_lor_ea.GetFE(lor_els[0]);
// Allocate space for DenseTensors
ElementTransformation *el_tr = fes_lor_ea.GetElementTransformation(0);
int order = fe_lor.GetOrder() + fe_ho.GetOrder() + el_tr->OrderW();
const IntegrationRule* ir_ea = &IntRules.Get(geom, order);
int qPts = ir_ea->GetNPoints();
// Containers for the basis functions sampled at quadrature points
B_L.SetSize(qPts, fe_lor.GetDof(), nref, d_mt);
B_H.SetSize(qPts, fe_ho.GetDof(), nref, d_mt);
D.SetSize(qPts, nref, nel_ho, d_mt);
const GeometricFactors *geo_facts =
mesh_lor->GetGeometricFactors(*ir_ea, GeometricFactors::DETERMINANTS);
MFEM_ASSERT(nel_ho*nref == nel_lor, "we expect nel_ho*nref == nel_lor");
// Setup data at quadrature points
// TODO add support for user coefficient
const auto W = Reshape(ir_ea->GetWeights().Read(), qPts);
const auto J = Reshape(geo_facts->detJ.Read(), qPts, nel_lor);
const auto d_D = Reshape(D.Write(), qPts, nref, nel_ho);
mfem::forall(qPts * nref * nel_ho, [=] MFEM_HOST_DEVICE (int tid)
{
const int q = tid % qPts;
const int iref = (tid / qPts) % nref;
const int iho = (tid / (qPts * nref)) % nel_ho;
const int lo_el_id = iref + nref*iho;
const real_t detJ = J(q, lo_el_id);
d_D(q, iref, iho) = W(q) * detJ;
});
emb_tr.SetIdentityTransformation(geom);
const DenseTensor &pmats = cf_tr.point_matrices[geom];
// Collect the basis functions
for (int iref = 0; iref < nref; ++iref)
{
int ilor = lor_els[iref];
// Now assemble the block-row of the mixed mass matrix associated
// with integrating HO functions against LOR functions on the LOR
// sub-element.
// Create the transformation that embeds the fine low-order element
// within the coarse high-order element in reference space
emb_tr.SetPointMat(pmats(cf_tr.embeddings[ilor].matrix));
DenseMatrix &b_lo = B_L(ilor);
DenseMatrix &b_ho = B_H(ilor);
ElemMixedMass(geom, fe_ho, fe_lor, el_tr, ip_tr, b_lo, b_ho);
} // loop over subcells of ho element
// end of quadrature point setup
}
} // completed setup of basis function and quadrature point
// Assemble mixed mass matrix
{
int iho = 0;
Array<int> lor_els;
ho2lor.GetRow(iho, lor_els);
int nref = ho2lor.RowSize(iho);
const FiniteElement &fe_ho = *fes_ho_ea.GetFE(iho);
const FiniteElement &fe_lor = *fes_lor_ea.GetFE(lor_els[0]);
const int ndof_ho = fe_ho.GetDof();
const int ndof_lor = fe_lor.GetDof();
const int qPts = D.SizeI();
M_LH.SetSize(ndof_lor*ndof_ho*nref*nel_ho, d_mt);
// Rows x columns
// Recall MFEM is column major
// rows x columns is inverted - matrix is ndof_lor x ndof_ho
auto v_M_LH = Reshape(M_LH.Write(), ndof_lor, ndof_ho, nref,
nel_ho);
const int fe_ho_ndof = fe_ho.GetDof();
const int fe_lor_ndof = fe_lor.GetDof();
auto d_B_L = Reshape(B_L.Read(), qPts, fe_lor_ndof, nref);
auto d_B_H = Reshape(B_H.Read(), qPts, fe_ho_ndof, nref);
auto d_D = Reshape(D.Read(), qPts, nref, nel_ho);
mfem::forall(fe_ho_ndof*nref*nel_ho, [=] MFEM_HOST_DEVICE (int idx)
{
const int bh = idx % fe_ho_ndof;
const int iref = (idx / fe_ho_ndof) % nref;
const int iho = idx / fe_ho_ndof / nref;
// (B_lo_dofs x Q) x (Q x B_ho_dofs)
for (int bl = 0; bl < fe_lor_ndof; ++bl)
{
real_t dot = 0.0;
for (int qi=0; qi<qPts; ++qi)
{
dot += d_B_L(qi, bl, iref) * d_D(qi, iref, iho) * d_B_H(qi, bh, iref);
}
// column major storage
v_M_LH(bl, bh, iref, iho) = dot;
}
});
} // end of mixed assembly mass matrix
}
L2ProjectionGridTransfer::L2ProjectionL2Space::L2ProjectionL2Space
(const FiniteElementSpace &fes_ho_, const FiniteElementSpace &fes_lor_,
const bool use_ea_, MemoryType d_mt_)
: L2Projection(fes_ho_, fes_lor_, d_mt_),
use_ea(use_ea_)
{
if (use_ea)
{
EAL2ProjectionL2Space();
return;
}
Mesh *mesh_ho = fes_ho.GetMesh();
Mesh *mesh_lor = fes_lor.GetMesh();
int nel_ho = mesh_ho->GetNE();
int nel_lor = mesh_lor->GetNE();
// The prolongation operation is only well-defined when the LOR space has at
// least as many DOFs as the high-order space.
const bool build_P = fes_lor.GetTrueVSize() >= fes_ho.GetTrueVSize();
// If the local mesh is empty, skip all computations
if (nel_ho == 0) { return; }
const CoarseFineTransformations &cf_tr = mesh_lor->GetRefinementTransforms();
int nref_max = 0;
Array<Geometry::Type> geoms;
mesh_ho->GetGeometries(mesh_ho->Dimension(), geoms);
for (int ig = 0; ig < geoms.Size(); ++ig)
{
Geometry::Type geom = geoms[ig];
nref_max = std::max(nref_max, cf_tr.point_matrices[geom].SizeK());
}
BuildHo2Lor(nel_ho, nel_lor, cf_tr);
offsets.SetSize(nel_ho+1);
offsets[0] = 0;
for (int iho = 0; iho < nel_ho; ++iho)
{
int nref = ho2lor.RowSize(iho);
const FiniteElement &fe_ho = *fes_ho.GetFE(iho);
const FiniteElement &fe_lor = *fes_lor.GetFE(ho2lor.GetRow(iho)[0]);
offsets[iho+1] = offsets[iho] + fe_ho.GetDof()*fe_lor.GetDof()*nref;
}
// R will contain the restriction (L^2 projection operator) defined on each
// coarse HO element (and corresponding patch of LOR elements)
R.SetSize(offsets[nel_ho]);
if (build_P)
{
// P will contain the corresponding prolongation operator
P.SetSize(offsets[nel_ho]);
}
IntegrationPointTransformation ip_tr;
IsoparametricTransformation &emb_tr = ip_tr.Transf;
for (int iho = 0; iho < nel_ho; ++iho)
{
Array<int> lor_els;
ho2lor.GetRow(iho, lor_els);
int nref = ho2lor.RowSize(iho);
Geometry::Type geom = mesh_ho->GetElementBaseGeometry(iho);
const FiniteElement &fe_ho = *fes_ho.GetFE(iho);
const FiniteElement &fe_lor = *fes_lor.GetFE(lor_els[0]);
int ndof_ho = fe_ho.GetDof();
int ndof_lor = fe_lor.GetDof();
ElementTransformation *tr_ho = fes_ho.GetElementTransformation(iho);
emb_tr.SetIdentityTransformation(geom);
const DenseTensor &pmats = cf_tr.point_matrices[geom];
DenseMatrix R_iho(&R[offsets[iho]], ndof_lor*nref, ndof_ho);
DenseMatrix Minv_lor(ndof_lor*nref, ndof_lor*nref);
DenseMatrix M_mixed(ndof_lor*nref, ndof_ho);
MassIntegrator mi;
DenseMatrix M_lor_el(ndof_lor, ndof_lor);
DenseMatrixInverse Minv_lor_el(&M_lor_el);
DenseMatrix M_lor(ndof_lor*nref, ndof_lor*nref);
DenseMatrix M_mixed_el(ndof_lor, ndof_ho);
Minv_lor = 0.0;
M_lor = 0.0;
DenseMatrix RtMlor(ndof_ho, ndof_lor*nref);
DenseMatrix RtMlorR(ndof_ho, ndof_ho);
DenseMatrixInverse RtMlorR_inv(&RtMlorR);
for (int iref = 0; iref < nref; ++iref)
{
// Assemble the low-order refined mass matrix and invert locally
int ilor = lor_els[iref];
ElementTransformation *tr_lor = fes_lor.GetElementTransformation(ilor);
mi.AssembleElementMatrix(fe_lor, *tr_lor, M_lor_el);
M_lor.CopyMN(M_lor_el, iref*ndof_lor, iref*ndof_lor);
Minv_lor_el.Factor();
Minv_lor_el.GetInverseMatrix(M_lor_el);
// Insert into the diagonal of the patch LOR mass matrix
Minv_lor.CopyMN(M_lor_el, iref*ndof_lor, iref*ndof_lor);
// Now assemble the block-row of the mixed mass matrix associated
// with integrating HO functions against LOR functions on the LOR
// sub-element.
// Create the transformation that embeds the fine low-order element
// within the coarse high-order element in reference space
emb_tr.SetPointMat(pmats(cf_tr.embeddings[ilor].matrix));
ElemMixedMass(geom, fe_ho, fe_lor, tr_ho, tr_lor, ip_tr, M_mixed_el);
M_mixed.CopyMN(M_mixed_el, iref*ndof_lor, 0);
}
mfem::Mult(Minv_lor, M_mixed, R_iho);
if (build_P)
{
DenseMatrix P_iho(&P[offsets[iho]], ndof_ho, ndof_lor*nref);
mfem::MultAtB(R_iho, M_lor, RtMlor);
mfem::Mult(RtMlor, R_iho, RtMlorR);
RtMlorR_inv.Factor();
RtMlorR_inv.Mult(RtMlor, P_iho);
}
}
}
void L2ProjectionGridTransfer::L2ProjectionL2Space::EAL2ProjectionL2Space()
{
Mesh *mesh_ho = fes_ho.GetMesh();
Mesh *mesh_lor = fes_lor.GetMesh();
int nel_ho = mesh_ho->GetNE();
int nel_lor = mesh_lor->GetNE();
// The prolongation operation is only well-defined when the LOR space has at
// least as many DOFs as the high-order space.
const bool build_P = fes_lor.GetTrueVSize() >= fes_ho.GetTrueVSize();
// If the local mesh is empty, skip all computations
if (nel_ho == 0) { return; }
const CoarseFineTransformations &cf_tr = mesh_lor->GetRefinementTransforms();
int nref_max = 0;
Array<Geometry::Type> geoms;
mesh_ho->GetGeometries(mesh_ho->Dimension(), geoms);
for (int ig = 0; ig < geoms.Size(); ++ig)
{
Geometry::Type geom = geoms[ig];
nref_max = std::max(nref_max, cf_tr.point_matrices[geom].SizeK());
}
BuildHo2Lor(nel_ho, nel_lor, cf_tr);
offsets.SetSize(nel_ho+1);
offsets[0] = 0;
for (int iho = 0; iho < nel_ho; ++iho)
{
int nref = ho2lor.RowSize(iho);
const FiniteElement &fe_ho = *fes_ho.GetFE(iho);
const FiniteElement &fe_lor = *fes_lor.GetFE(ho2lor.GetRow(iho)[0]);
offsets[iho+1] = offsets[iho] + fe_ho.GetDof()*fe_lor.GetDof()*nref;
}
// R will contain the restriction (L^2 projection operator) defined on each
// coarse HO element (and corresponding patch of LOR elements)
R.SetSize(offsets[nel_ho]);
if (build_P)
{
// P will contain the corresponding prolongation operator
P.SetSize(offsets[nel_ho]);
}
// Assemble mixed mass matrix
Vector M_mixed_all;
MixedMassEA(fes_ho, fes_lor, M_mixed_all, d_mt);
// R = inv(M_L) * M_mixed
// Need to compute M_L
// Note: Using user-inputted M_LH IntegrationRule ir
// (higher order than needed) in order to re-use coeff
MassIntegrator mi;
Vector M_ea_lor;
int ndof_lor;
int ndof_ho;
int nref;
{
int iho = 0;
Array<int> lor_els;
ho2lor.GetRow(iho, lor_els);
nref = ho2lor.RowSize(iho);
const FiniteElement &fe_ho = *fes_ho.GetFE(iho);
const FiniteElement &fe_lor = *fes_lor.GetFE(lor_els[0]);
ndof_ho = fe_ho.GetDof();
ndof_lor = fe_lor.GetDof();
M_ea_lor.SetSize(ndof_lor*ndof_lor*nel_lor, d_mt);
}
const bool add = false;
mi.AssembleEA(fes_lor, M_ea_lor, add);
DenseTensor Minv_ear_lor;
Minv_ear_lor.SetSize(ndof_lor, ndof_lor, nel_lor, d_mt);
Minv_ear_lor.GetMemory().CopyFrom(M_ea_lor.GetMemory(), M_ea_lor.Size());
BatchedLinAlg::Invert(Minv_ear_lor);
{
// Recall mfem is column major
// ndof_lor x ndof_ho
auto v_M_mixed_all = Reshape(M_mixed_all.Read(), ndof_lor, ndof_ho, nref,
nel_ho);
// matrix is symmetric
auto v_Minv_ear_lor = Reshape(Minv_ear_lor.Read(), ndof_lor, ndof_lor,
nel_lor);
// ndof_lor x ndof_ho
auto v_R = Reshape(R.Write(), ndof_lor, nref, ndof_ho, nel_ho);
MFEM_VERIFY(nel_lor==nel_ho*nref, "nel_lor != nel_ho*nref");
// (ndofs_lor x ndofs_lor) x (ndofs_lor x ndof_ho)
mfem::forall(ndof_lor * nref * ndof_ho * nel_ho, [=] MFEM_HOST_DEVICE (int tid)
{
const int i = tid % ndof_lor;
const int iref = (tid / ndof_lor) % nref;
const int j = (tid / (ndof_lor * nref) ) % ndof_ho;
const int iho = (tid / (ndof_lor * nref * ndof_ho)) % nel_ho;
const int lor_idx = iref + iho * nref;
//matrices are stored in the transpose position
real_t dot = 0.0;
for (int k=0; k<ndof_lor; ++k)
{
dot += v_Minv_ear_lor(i, k, lor_idx) * v_M_mixed_all(k, j, iref, iho);
}
v_R(i, iref, j, iho) = dot;
});
}
if (build_P)
{
// P = inv(R^T M_L R) * R^T M_L
// M_lor is size of ndof_lor x ndof_lor
// R is size of (ndof_lor x nref x ndof_ho)
auto v_M_ea_lor = Reshape(M_ea_lor.Read(), ndof_lor, ndof_lor, nel_lor);
auto v_R = Reshape(R.Read(), ndof_lor, nref, ndof_ho, nel_ho);
// R^T M_LO is of size nref x ndof_lor
// Compute R^T M_L
Vector RtM_L(ndof_ho*nref*ndof_lor*nel_ho, d_mt);
auto v_RtM_L = Reshape(RtM_L.Write(), ndof_ho, ndof_lor, nref, nel_ho);
mfem::forall(ndof_lor * nref * ndof_ho * nel_ho, [=] MFEM_HOST_DEVICE (int tid)
{
const int jlo = tid % ndof_lor;
const int iref = (tid / ndof_lor) % nref;
const int iho = (tid / (ndof_lor * nref)) % ndof_ho;
const int e = (tid / (ndof_lor * nref * ndof_ho)) % nel_ho;
const int lor_idx = iref + e * nref;
real_t dot = 0.0;
for (int t=0; t<ndof_lor; ++t)
{
dot += v_R(t, iref, iho, e) * v_M_ea_lor(t, jlo, lor_idx);
}
v_RtM_L(iho, jlo, iref, e) = dot;
});
// Resulting matrix should be: ndof_ho x ndof_ho
// R^T M_L x R
DenseTensor RtM_L_dt;
RtM_L_dt.NewMemoryAndSize(RtM_L.GetMemory(), ndof_ho, ndof_lor*nref,
nel_ho, false);
Vector R_vec;
R_vec.NewMemoryAndSize(R.GetMemory(), R.Size(), false);
Vector RtM_LR(ndof_ho * ndof_ho * nel_ho, d_mt);
BatchedLinAlg::Mult(RtM_L_dt, R_vec, RtM_LR);
// Ensure that changes to the alias R_vec are propagated to the base, R
R_vec.GetMemory().SyncAlias(R.GetMemory(), P.Size());
// Compute the inverse of InvRtM_LR
DenseTensor InvRtM_LR;
InvRtM_LR.NewMemoryAndSize(RtM_LR.GetMemory(), ndof_ho, ndof_ho, nel_ho, false);
BatchedLinAlg::Invert(InvRtM_LR);
// Form P
// P should be of dimension (ndof_ho x ndof_ho) x (ndof_ho x nref*ndof_lor)
// P ndof_ho x nref*ndof_lor
Vector P_vec;
P_vec.NewMemoryAndSize(P.GetMemory(), P.Size(), false);
BatchedLinAlg::Mult(InvRtM_LR, RtM_L, P_vec);
// Ensure that changes to the alias P_vec are propagated to the base, P
P_vec.GetMemory().SyncAlias(P.GetMemory(), P.Size());
}
}
void L2ProjectionGridTransfer::L2ProjectionL2Space::Mult(
const Vector &x, Vector &y) const
{
if (use_ea)
{
return EAMult(x,y);
}
int vdim = fes_ho.GetVDim();
Array<int> vdofs;
DenseMatrix xel_mat, yel_mat;
for (int iho = 0; iho < fes_ho.GetNE(); ++iho)
{
int nref = ho2lor.RowSize(iho);
int ndof_ho = fes_ho.GetFE(iho)->GetDof();
int ndof_lor = fes_lor.GetFE(ho2lor.GetRow(iho)[0])->GetDof();
xel_mat.SetSize(ndof_ho, vdim);
yel_mat.SetSize(ndof_lor*nref, vdim);
DenseMatrix R_iho(&R[offsets[iho]], ndof_lor*nref, ndof_ho);
fes_ho.GetElementVDofs(iho, vdofs);
x.GetSubVector(vdofs, xel_mat.GetData());
mfem::Mult(R_iho, xel_mat, yel_mat);
// Place result correctly into the low-order vector
for (int iref = 0; iref < nref; ++iref)
{
int ilor = ho2lor.GetRow(iho)[iref];
for (int vd=0; vd<vdim; ++vd)
{
fes_lor.GetElementDofs(ilor, vdofs);
fes_lor.DofsToVDofs(vd, vdofs);
y.SetSubVector(vdofs, &yel_mat(iref*ndof_lor,vd));
}
}
}
}
void L2ProjectionGridTransfer::L2ProjectionL2Space::EAMult(
const Vector &x, Vector &y) const
{
const int nel_ho = fes_ho.GetMesh()->GetNE();
if (nel_ho == 0)
{
return;
}
const int iho = 0;
const int nref = ho2lor.RowSize(iho);
const int ndof_ho = fes_ho.GetFE(iho)->GetDof();
const int ndof_lor = fes_lor.GetFE(ho2lor.GetRow(iho)[0])->GetDof();
DenseTensor R_dt;
R_dt.NewMemoryAndSize(R.GetMemory(), ndof_lor*nref, ndof_ho, nel_ho, false);
BatchedLinAlg::Mult(R_dt, x, y);
}
void L2ProjectionGridTransfer::L2ProjectionL2Space::MultTranspose(
const Vector &x, Vector &y) const
{
if (use_ea)
{
return EAMultTranspose(x,y);
}
int vdim = fes_ho.GetVDim();
Array<int> vdofs;
DenseMatrix xel_mat, yel_mat;
y = 0.0;
for (int iho = 0; iho < fes_ho.GetNE(); ++iho)
{
int nref = ho2lor.RowSize(iho);
int ndof_ho = fes_ho.GetFE(iho)->GetDof();
int ndof_lor = fes_lor.GetFE(ho2lor.GetRow(iho)[0])->GetDof();
xel_mat.SetSize(ndof_lor*nref, vdim);
yel_mat.SetSize(ndof_ho, vdim);
DenseMatrix R_iho(&R[offsets[iho]], ndof_lor*nref, ndof_ho);
// Extract the LOR DOFs
for (int iref=0; iref<nref; ++iref)
{
int ilor = ho2lor.GetRow(iho)[iref];
for (int vd=0; vd<vdim; ++vd)
{
fes_lor.GetElementDofs(ilor, vdofs);
fes_lor.DofsToVDofs(vd, vdofs);
x.GetSubVector(vdofs, &xel_mat(iref*ndof_lor, vd));
}
}
// Multiply locally by the transpose
mfem::MultAtB(R_iho, xel_mat, yel_mat);
// Place the result in the HO vector
fes_ho.GetElementVDofs(iho, vdofs);
y.AddElementVector(vdofs, yel_mat.GetData());
}
}
void L2ProjectionGridTransfer::L2ProjectionL2Space::EAMultTranspose(
const Vector &x, Vector &y) const
{
const int nel_ho = fes_ho.GetMesh()->GetNE();
if (nel_ho == 0)
{
return;
}
const int iho = 0;
const int nref = ho2lor.RowSize(iho);
const int ndof_ho = fes_ho.GetFE(iho)->GetDof();
const int ndof_lor = fes_lor.GetFE(ho2lor.GetRow(iho)[0])->GetDof();
DenseTensor R_dt;
R_dt.NewMemoryAndSize(R.GetMemory(), ndof_lor*nref, ndof_ho, nel_ho, false);
BatchedLinAlg::MultTranspose(R_dt, x, y);
}
void L2ProjectionGridTransfer::L2ProjectionL2Space::Prolongate(
const Vector &x, Vector &y) const
{
if (fes_ho.GetNE() == 0) { return; }
if (use_ea)
{
return EAProlongate(x,y);
}
MFEM_VERIFY(P.Size() > 0, "Prolongation not supported for these spaces.")
int vdim = fes_ho.GetVDim();
Array<int> vdofs;
DenseMatrix xel_mat,yel_mat;
y = 0.0;
for (int iho = 0; iho < fes_ho.GetNE(); ++iho)
{
int nref = ho2lor.RowSize(iho);
int ndof_ho = fes_ho.GetFE(iho)->GetDof();
int ndof_lor = fes_lor.GetFE(ho2lor.GetRow(iho)[0])->GetDof();
xel_mat.SetSize(ndof_lor*nref, vdim);
yel_mat.SetSize(ndof_ho, vdim);
DenseMatrix P_iho(&P[offsets[iho]], ndof_ho, ndof_lor*nref);
// Extract the LOR DOFs
for (int iref = 0; iref < nref; ++iref)
{
int ilor = ho2lor.GetRow(iho)[iref];
for (int vd = 0; vd < vdim; ++vd)
{
fes_lor.GetElementDofs(ilor, vdofs);
fes_lor.DofsToVDofs(vd, vdofs);
x.GetSubVector(vdofs, &xel_mat(iref*ndof_lor, vd));
}
}
// Locally prolongate
mfem::Mult(P_iho, xel_mat, yel_mat);
// Place the result in the HO vector
fes_ho.GetElementVDofs(iho, vdofs);
y.AddElementVector(vdofs, yel_mat.GetData());
}
}
void L2ProjectionGridTransfer::L2ProjectionL2Space::EAProlongate(
const Vector &x, Vector &y) const
{
const int iho = 0;
const int nref = ho2lor.RowSize(iho);
const int ndof_ho = fes_ho.GetFE(iho)->GetDof();
const int ndof_lor = fes_lor.GetFE(ho2lor.GetRow(iho)[0])->GetDof();
const int nel_ho = fes_ho.GetMesh()->GetNE();
DenseTensor P_dt;
P_dt.NewMemoryAndSize(P.GetMemory(), ndof_ho, ndof_lor * nref, nel_ho, false);
BatchedLinAlg::Mult(P_dt, x, y);
}
void L2ProjectionGridTransfer::L2ProjectionL2Space::ProlongateTranspose(
const Vector &x, Vector &y) const
{
if (fes_ho.GetNE() == 0) { return; }
if (use_ea)
{
return EAProlongateTranspose(x,y);
}
MFEM_VERIFY(P.Size() > 0, "Prolongation not supported for these spaces.")
int vdim = fes_ho.GetVDim();
Array<int> vdofs;
DenseMatrix xel_mat,yel_mat;
for (int iho = 0; iho < fes_ho.GetNE(); ++iho)
{
int nref = ho2lor.RowSize(iho);
int ndof_ho = fes_ho.GetFE(iho)->GetDof();
int ndof_lor = fes_lor.GetFE(ho2lor.GetRow(iho)[0])->GetDof();
xel_mat.SetSize(ndof_ho, vdim);
yel_mat.SetSize(ndof_lor*nref, vdim);
DenseMatrix P_iho(&P[offsets[iho]], ndof_ho, ndof_lor*nref);
fes_ho.GetElementVDofs(iho, vdofs);
x.GetSubVector(vdofs, xel_mat.GetData());
mfem::MultAtB(P_iho, xel_mat, yel_mat);
// Place result correctly into the low-order vector
for (int iref = 0; iref < nref; ++iref)
{
int ilor = ho2lor.GetRow(iho)[iref];
for (int vd=0; vd<vdim; ++vd)
{
fes_lor.GetElementDofs(ilor, vdofs);
fes_lor.DofsToVDofs(vd, vdofs);
y.SetSubVector(vdofs, &yel_mat(iref*ndof_lor,vd));
}
}
}
}
void L2ProjectionGridTransfer::L2ProjectionL2Space::EAProlongateTranspose(
const Vector &x, Vector &y) const
{
const int iho = 0;
const int nref = ho2lor.RowSize(iho);
const int ndof_ho = fes_ho.GetFE(iho)->GetDof();
const int ndof_lor = fes_lor.GetFE(ho2lor.GetRow(iho)[0])->GetDof();
const int nel_ho = fes_ho.GetMesh()->GetNE();
DenseTensor P_dt;
P_dt.NewMemoryAndSize(P.GetMemory(), ndof_ho, ndof_lor * nref, nel_ho, false);
BatchedLinAlg::MultTranspose(P_dt, x, y);
}
L2ProjectionGridTransfer::L2ProjectionH1Space::L2ProjectionH1Space(
const FiniteElementSpace& fes_ho_, const FiniteElementSpace& fes_lor_,
const bool use_ea_, MemoryType d_mt_)
: L2Projection(fes_ho_, fes_lor_, d_mt_),
use_ea(use_ea_)
{
// need scalar to keep dimensions matching (operators are built to apply
// individually on each vdim)
// needed in both matrix and element based versions
fes_ho_scalar.reset(new FiniteElementSpace(fes_ho.GetMesh(),
fes_ho.FEColl(), 1));
fes_lor_scalar.reset(new FiniteElementSpace(fes_lor.GetMesh(),
fes_lor.FEColl(), 1));
if (use_ea)
{
EAL2ProjectionH1Space();
return;
}
std::unique_ptr<SparseMatrix> R_mat, M_LH_mat;
std::tie(R_mat, M_LH_mat) = ComputeSparseRAndM_LH();
const SparseMatrix *P_ho = fes_ho_scalar->GetConformingProlongation();
const SparseMatrix *P_lor = fes_lor_scalar->GetConformingProlongation();
if (P_ho || P_lor)
{
if (P_ho && P_lor)
{
R_mat.reset(RAP(*P_lor, *R_mat, *P_ho));
M_LH_mat.reset(RAP(*P_lor, *M_LH_mat, *P_ho));
}
else if (P_ho)
{
R_mat.reset(mfem::Mult(*R_mat, *P_ho));
M_LH_mat.reset(mfem::Mult(*M_LH_mat, *P_ho));
}
else // P_lor != nullptr
{
R_mat.reset(mfem::Mult(*P_lor, *R_mat));
M_LH_mat.reset(mfem::Mult(*P_lor, *M_LH_mat));
}
}
SparseMatrix *RTxM_LH_mat = TransposeMult(*R_mat, *M_LH_mat);
precon.reset(new DSmoother(*RTxM_LH_mat));
// Set ownership
RTxM_LH.reset(RTxM_LH_mat);
R = std::move(R_mat);
M_LH = std::move(M_LH_mat);
SetupPCG();
}
#ifdef MFEM_USE_MPI
L2ProjectionGridTransfer::L2ProjectionH1Space::L2ProjectionH1Space(
const ParFiniteElementSpace& pfes_ho, const ParFiniteElementSpace& pfes_lor,
const bool use_ea_, MemoryType d_mt_)
: L2Projection(pfes_ho, pfes_lor, d_mt_),
use_ea(use_ea_), pcg(pfes_ho.GetComm())
{
// need scalar to keep dimensions matching (operators are built to apply
// individually on each vdim)
// needed in both matrix and element based versions
pfes_ho_scalar.reset(new ParFiniteElementSpace(pfes_ho.GetParMesh(),
pfes_ho.FEColl(), 1));
pfes_lor_scalar.reset(new ParFiniteElementSpace(pfes_lor.GetParMesh(),
pfes_lor.FEColl(), 1));
if (use_ea)
{
EAL2ProjectionH1Space(pfes_ho, pfes_lor);
return;
}
std::tie(R, M_LH) = ComputeSparseRAndM_LH();
HypreParMatrix R_local = HypreParMatrix(pfes_ho.GetComm(),
pfes_lor_scalar->GlobalVSize(),
pfes_ho_scalar->GlobalVSize(),
pfes_lor_scalar->GetDofOffsets(),
pfes_ho_scalar->GetDofOffsets(),
static_cast<SparseMatrix*>(R.get()));
HypreParMatrix M_LH_local = HypreParMatrix(pfes_ho.GetComm(),
pfes_lor_scalar->GlobalVSize(),
pfes_ho_scalar->GlobalVSize(),
pfes_lor_scalar->GetDofOffsets(),
pfes_ho_scalar->GetDofOffsets(),
static_cast<SparseMatrix*>(M_LH.get()));
HypreParMatrix *R_mat = RAP(pfes_lor_scalar->Dof_TrueDof_Matrix(),
&R_local, pfes_ho_scalar->Dof_TrueDof_Matrix());
HypreParMatrix *M_LH_mat = RAP(pfes_lor_scalar->Dof_TrueDof_Matrix(),
&M_LH_local, pfes_ho_scalar->Dof_TrueDof_Matrix());
std::unique_ptr<HypreParMatrix> R_T(R_mat->Transpose());
HypreParMatrix *RTxM_LH_mat = ParMult(R_T.get(), M_LH_mat, true);
HypreBoomerAMG *amg = new HypreBoomerAMG(*RTxM_LH_mat);
amg->SetPrintLevel(0);
R.reset(R_mat);
M_LH.reset(M_LH_mat);
RTxM_LH.reset(RTxM_LH_mat);
precon.reset(amg);
SetupPCG();
pcg.SetPreconditioner(*precon);
pcg.SetOperator(*RTxM_LH);
}
#endif
void L2ProjectionGridTransfer::L2ProjectionH1Space::SetupPCG()
{
// Basic PCG solver setup
pcg.SetPrintLevel(0);
// pcg.SetPrintLevel(IterativeSolver::PrintLevel().Summary());
pcg.SetMaxIter(1000);
// initial values for relative and absolute tolerance
pcg.SetRelTol(1e-13);
pcg.SetAbsTol(1e-13);
pcg.SetPreconditioner(*precon);
pcg.SetOperator(*RTxM_LH);
}
void L2ProjectionGridTransfer::L2ProjectionH1Space::EAL2ProjectionH1Space()
{
Mesh* mesh_ho = fes_ho.GetMesh();
Mesh* mesh_lor = fes_lor.GetMesh();
int nel_ho = mesh_ho->GetNE();
int nel_lor = mesh_lor->GetNE();
int ndof_ho = fes_ho.GetNDofs();
int ndof_lor = fes_lor.GetNDofs();
// If the local mesh is empty, skip all computations
if (nel_ho == 0)
{
return;
}
const CoarseFineTransformations& cf_tr = mesh_lor->GetRefinementTransforms();
int nref_max = 0;
Array<Geometry::Type> geoms;
mesh_ho->GetGeometries(mesh_ho->Dimension(), geoms);
for (int ig = 0; ig < geoms.Size(); ++ig)
{
Geometry::Type geom = geoms[ig];
nref_max = std::max(nref_max, cf_tr.point_matrices[geom].SizeK());
}
BuildHo2Lor(nel_ho, nel_lor, cf_tr);
// lumped M_H and inv lumped M_L
// M_H contains the lumped (row sum) high order mass matrix. This is built for
// preconditioning the inverse needed to build the prolongation operator P
Vector M_H(ndof_ho);
M_H = 0.0;
// ML_inv_ea contains the inverse lumped (row sum) mass matrix. Note that the
// method will also work with a full (consistent) mass matrix, though this is
// not implemented here. L refers to the low-order refined mesh
ML_inv_ea.SetSize(ndof_lor);
ML_inv_ea = 0.0;
BilinearForm Mho(fes_ho_scalar.get());
Mho.SetAssemblyLevel(AssemblyLevel::PARTIAL);
Mho.AddDomainIntegrator(new MassIntegrator);
Mho.Assemble();
// Processor local lumped Mass
Vector ones_ho(Mho.Width()); ones_ho = 1.0;
M_H = 0.0;
Mho.Mult(ones_ho, M_H);
BilinearForm Mlor(fes_lor_scalar.get());
Mlor.SetAssemblyLevel(AssemblyLevel::PARTIAL);
Mlor.AddDomainIntegrator(new MassIntegrator);
Mlor.Assemble();
Vector ones_lor(Mlor.Width()); ones_lor = 1.0;
Mlor.Mult(ones_lor, ML_inv_ea);
// DOF by DOF inverse of non-zero entries
LumpedMassInverse(ML_inv_ea);
// mixed mass M_LH
MixedMassEA(fes_ho, fes_lor, M_LH_ea, d_mt);
// Set ownership
M_LH_local_op = new H1SpaceMixedMassOperator(fes_ho_scalar.get(),
fes_lor_scalar.get(),
&ho2lor,
&M_LH_ea);
ML_inv_vea.reset(new H1SpaceLumpedMassOperator(fes_ho_scalar.get(),
fes_lor_scalar.get(),
ML_inv_ea));
M_LH.reset(M_LH_local_op);
R.reset(new ProductOperator(ML_inv_vea.get(), M_LH.get(), false,
false));
Array<int> ess_tdof_list; // leave empty
precon.reset(new OperatorJacobiSmoother(M_H, ess_tdof_list));
TransposeOperator* RT = new TransposeOperator(R.get());
RTxM_LH.reset(new ProductOperator(RT, M_LH.get(), true, false));
SetupPCG();
}
#ifdef MFEM_USE_MPI
void L2ProjectionGridTransfer::L2ProjectionH1Space::EAL2ProjectionH1Space
(const ParFiniteElementSpace& pfes_ho, const ParFiniteElementSpace& pfes_lor)
{
Mesh* mesh_ho = pfes_ho.GetParMesh();
Mesh* mesh_lor = pfes_lor.GetParMesh();
int nel_ho = mesh_ho->GetNE();
int nel_lor = mesh_lor->GetNE();
int ndof_ho = pfes_ho.GetNDofs();
int ndof_lor = pfes_lor.GetNDofs();
const CoarseFineTransformations& cf_tr = mesh_lor->GetRefinementTransforms();
int nref_max = 0;
Array<Geometry::Type> geoms;
mesh_ho->GetGeometries(mesh_ho->Dimension(), geoms);
for (int ig = 0; ig < geoms.Size(); ++ig)
{
Geometry::Type geom = geoms[ig];
nref_max = std::max(nref_max, cf_tr.point_matrices[geom].SizeK());
}
BuildHo2Lor(nel_ho, nel_lor, cf_tr);
// lumped M_H and inv lumped M_L
// M_H contains the lumped (row sum) high order mass matrix. This is built for
// preconditioning the inverse needed to build the prolongation operator P
Vector M_H(ndof_ho);
M_H = 0.0;
// ML_inv_ea contains the inverse lumped (row sum) mass matrix. Note that the
// method will also work with a full (consistent) mass matrix, though this is
// not implemented here. L refers to the low-order refined mesh
ML_inv_ea.SetSize(ndof_lor);
ML_inv_ea = 0.0;
ParBilinearForm pMho(pfes_ho_scalar.get());
pMho.SetAssemblyLevel(AssemblyLevel::PARTIAL);
pMho.AddDomainIntegrator(new MassIntegrator);
pMho.Assemble();
// Processor local lumped Mass
Vector ones_ho(pMho.Width()); ones_ho = 1.0;
M_H = 0.0;
pMho.Mult(ones_ho, M_H);
ParBilinearForm pMlor(pfes_lor_scalar.get());
pMlor.SetAssemblyLevel(AssemblyLevel::PARTIAL);
pMlor.AddDomainIntegrator(new MassIntegrator);
pMlor.Assemble();
Vector ones_lor(pMlor.Width()); ones_lor = 1.0;
pMlor.Mult(ones_lor, ML_inv_ea);
// DOF by DOF inverse of non-zero entries
LumpedMassInverse(ML_inv_ea);
// mixed mass M_LH
MixedMassEA(*pfes_ho_scalar.get(), *pfes_lor_scalar.get(), M_LH_ea, d_mt);
// Set ownership
M_LH_local_op = new H1SpaceMixedMassOperator(pfes_ho_scalar.get(),
pfes_lor_scalar.get(),
&ho2lor, &M_LH_ea);
const Operator *P_ho = pfes_ho_scalar->GetProlongationMatrix();
const Operator *P_lor = pfes_lor_scalar->GetProlongationMatrix();
Array<int> ess_tdof_list; // leave empty
if (P_ho || P_lor)
{
if (P_ho && P_lor)
{
Operator *Pt_lor = new TransposeOperator(P_lor);
RML_inv.SetSize(pfes_lor_scalar->GetTrueVSize());
GetTDofs(*pfes_lor_scalar, ML_inv_ea, RML_inv);
ML_inv_vea.reset(new H1SpaceLumpedMassOperator(pfes_ho_scalar.get(),
pfes_lor_scalar.get(),
RML_inv));
M_LH.reset(new TripleProductOperator(Pt_lor, M_LH_local_op, P_ho, true,
true, false));
Vector RM_H(pfes_ho_scalar->GetTrueVSize());
GetTDofsTranspose(*pfes_ho_scalar, M_H, RM_H);
precon.reset(new OperatorJacobiSmoother(RM_H, ess_tdof_list));
}
else if (P_ho)
{
ML_inv_vea.reset(new H1SpaceLumpedMassOperator(pfes_ho_scalar.get(),
pfes_lor_scalar.get(),
ML_inv_ea));
M_LH.reset(new ProductOperator(M_LH_local_op, P_ho, true, false));
Vector RM_H(pfes_ho_scalar->GetTrueVSize());
GetTDofsTranspose(*pfes_ho_scalar.get(), M_H, RM_H);
precon.reset(new OperatorJacobiSmoother(RM_H, ess_tdof_list));
}
else if (P_lor)
{
Operator *Pt_lor = new TransposeOperator(P_lor);
RML_inv.SetSize(pfes_lor_scalar->GetTrueVSize());
GetTDofsTranspose(*pfes_lor_scalar, ML_inv_ea, RML_inv);
ML_inv_vea.reset(new H1SpaceLumpedMassOperator(pfes_ho_scalar.get(),
pfes_lor_scalar.get(),
RML_inv));
M_LH.reset(new ProductOperator(Pt_lor, M_LH_local_op, true, true));
R.reset(new ProductOperator(ML_inv_vea.get(), M_LH.get(), false,
false));
precon.reset(new OperatorJacobiSmoother(M_H, ess_tdof_list));
}
else
{
ML_inv_vea.reset(new H1SpaceLumpedMassOperator(pfes_ho_scalar.get(),
pfes_lor_scalar.get(),
ML_inv_ea));
M_LH.reset(M_LH_local_op);
precon.reset(new OperatorJacobiSmoother(M_H, ess_tdof_list));
}
}
R.reset(new ProductOperator(ML_inv_vea.get(), M_LH.get(), false,
false));
TransposeOperator* RT = new TransposeOperator(R.get());
RTxM_LH.reset(new ProductOperator(RT, M_LH.get(), true, false));
SetupPCG();
}
#endif
void L2ProjectionGridTransfer::L2ProjectionH1Space::Mult(
const Vector& x, Vector& y) const
{
Vector X(fes_ho.GetTrueVSize());
Vector X_dim(R->Width());
Vector Y_dim(R->Height());
Vector Y(fes_lor.GetTrueVSize());
Array<int> vdofs_list;
GetTDofs(fes_ho, x, X);
for (int d = 0; d < fes_ho.GetVDim(); ++d)
{
TDofsListByVDim(fes_ho, d, vdofs_list);
X.GetSubVector(vdofs_list, X_dim);
R->Mult(X_dim, Y_dim);
TDofsListByVDim(fes_lor, d, vdofs_list);
Y.SetSubVector(vdofs_list, Y_dim);
}
SetFromTDofs(fes_lor, Y, y);
}
void L2ProjectionGridTransfer::L2ProjectionH1Space::MultTranspose(
const Vector& x, Vector& y) const
{
Vector X(fes_lor.GetTrueVSize());
Vector X_dim(R->Height());
Vector Y_dim(R->Width());
Vector Y(fes_ho.GetTrueVSize());
Array<int> vdofs_list;
GetTDofsTranspose(fes_lor, x, X);
for (int d = 0; d < fes_ho.GetVDim(); ++d)
{
TDofsListByVDim(fes_lor, d, vdofs_list);
X.GetSubVector(vdofs_list, X_dim);
R->MultTranspose(X_dim, Y_dim);
TDofsListByVDim(fes_ho, d, vdofs_list);
Y.SetSubVector(vdofs_list, Y_dim);
}
SetFromTDofsTranspose(fes_ho, Y, y);
}
void L2ProjectionGridTransfer::L2ProjectionH1Space::Prolongate(
const Vector& x, Vector& y) const
{
Vector X(fes_lor.GetTrueVSize());
Vector X_dim(M_LH->Height());
Vector Xbar(pcg.Width());
Vector Y_dim(pcg.Height());
Y_dim = 0.0;
Vector Y(fes_ho.GetTrueVSize());
Array<int> vdofs_list;
GetTDofs(fes_lor, x, X);
for (int d = 0; d < fes_ho.GetVDim(); ++d)
{
TDofsListByVDim(fes_lor, d, vdofs_list);
X.GetSubVector(vdofs_list, X_dim);
// Compute y = P x = (R^T M_LH)^(-1) M_LH^T X = (R^T M_LH)^(-1) Xbar
M_LH->MultTranspose(X_dim, Xbar);
pcg.Mult(Xbar, Y_dim);
TDofsListByVDim(fes_ho, d, vdofs_list);
Y.SetSubVector(vdofs_list, Y_dim);
}
SetFromTDofs(fes_ho, Y, y);
}
void L2ProjectionGridTransfer::L2ProjectionH1Space::ProlongateTranspose(
const Vector& x, Vector& y) const
{
Vector X(fes_ho.GetTrueVSize());
Vector X_dim(pcg.Width());
Vector Xbar(pcg.Height());
Vector Y_dim(M_LH->Height());
Vector Y(fes_lor.GetTrueVSize());
Array<int> vdofs_list;
GetTDofsTranspose(fes_ho, x, X);
for (int d = 0; d < fes_ho.GetVDim(); ++d)
{
TDofsListByVDim(fes_ho, d, vdofs_list);
X.GetSubVector(vdofs_list, X_dim);
// Compute y = P^T x = M_LH (R^T M_LH)^(-1) X = M_LH Xbar
Xbar = 0.0;
pcg.Mult(X_dim, Xbar);
M_LH->Mult(Xbar, Y_dim);
TDofsListByVDim(fes_lor, d, vdofs_list);
Y.SetSubVector(vdofs_list, Y_dim);
}
SetFromTDofsTranspose(fes_lor, Y, y);
}
void L2ProjectionGridTransfer::L2ProjectionH1Space::SetRelTol(real_t p_rtol_)
{
pcg.SetRelTol(p_rtol_);
}
void L2ProjectionGridTransfer::L2ProjectionH1Space::SetAbsTol(real_t p_atol_)
{
pcg.SetAbsTol(p_atol_);
}
std::pair<
std::unique_ptr<SparseMatrix>,
std::unique_ptr<SparseMatrix>>
L2ProjectionGridTransfer::L2ProjectionH1Space::ComputeSparseRAndM_LH()
{
std::pair<std::unique_ptr<SparseMatrix>,
std::unique_ptr<SparseMatrix>> r_and_mlh;
Mesh* mesh_ho = fes_ho.GetMesh();
Mesh* mesh_lor = fes_lor.GetMesh();
int nel_ho = mesh_ho->GetNE();
int nel_lor = mesh_lor->GetNE();
int ndof_lor = fes_lor.GetNDofs();
// If the local mesh is empty, skip all computations
if (nel_ho == 0)
{
return std::make_pair(
std::unique_ptr<SparseMatrix>(new SparseMatrix),
std::unique_ptr<SparseMatrix>(new SparseMatrix)
);
}
const CoarseFineTransformations& cf_tr = mesh_lor->GetRefinementTransforms();
int nref_max = 0;
Array<Geometry::Type> geoms;
mesh_ho->GetGeometries(mesh_ho->Dimension(), geoms);
for (int ig = 0; ig < geoms.Size(); ++ig)
{
Geometry::Type geom = geoms[ig];
nref_max = std::max(nref_max, cf_tr.point_matrices[geom].SizeK());
}
BuildHo2Lor(nel_ho, nel_lor, cf_tr);
// ML_inv contains the inverse lumped (row sum) mass matrix. Note that the
// method will also work with a full (consistent) mass matrix, though this is
// not implemented here. L refers to the low-order refined mesh
Vector ML_inv(ndof_lor);
ML_inv = 0.0;
// Compute ML_inv
for (int iho = 0; iho < nel_ho; ++iho)
{
Array<int> lor_els;
ho2lor.GetRow(iho, lor_els);
int nref = ho2lor.RowSize(iho);
Geometry::Type geom = mesh_ho->GetElementBaseGeometry(iho);
const FiniteElement& fe_lor = *fes_lor.GetFE(lor_els[0]);
int nedof_lor = fe_lor.GetDof();
// Instead of using a MassIntegrator, manually loop over integration
// points so we can row sum and store the diagonal as a Vector.
Vector ML_el(nedof_lor);
Vector shape_lor(nedof_lor);
Array<int> dofs_lor(nedof_lor);
for (int iref = 0; iref < nref; ++iref)
{
int ilor = lor_els[iref];
ElementTransformation* el_tr = fes_lor.GetElementTransformation(ilor);
int order = 2 * fe_lor.GetOrder() + el_tr->OrderW();
const IntegrationRule* ir = &IntRules.Get(geom, order);
ML_el = 0.0;
for (int i = 0; i < ir->GetNPoints(); ++i)
{
const IntegrationPoint& ip_lor = ir->IntPoint(i);
fe_lor.CalcShape(ip_lor, shape_lor);
el_tr->SetIntPoint(&ip_lor);
ML_el += (shape_lor *= (el_tr->Weight() * ip_lor.weight));
}
fes_lor.GetElementDofs(ilor, dofs_lor);
ML_inv.AddElementVector(dofs_lor, ML_el);
}
}
// DOF by DOF inverse of non-zero entries
LumpedMassInverse(ML_inv);
// Compute sparsity pattern for R = M_L^(-1) M_LH and allocate
r_and_mlh.first = AllocR();
// Allocate M_LH (same sparsity pattern as R)
// L refers to the low-order refined mesh (DOFs correspond to rows)
// H refers to the higher-order mesh (DOFs correspond to columns)
Memory<int> I(r_and_mlh.first->Height() + 1);
for (int icol = 0; icol < r_and_mlh.first->Height() + 1; ++icol)
{
I[icol] = r_and_mlh.first->GetI()[icol];
}
Memory<int> J(r_and_mlh.first->NumNonZeroElems());
for (int jcol = 0; jcol < r_and_mlh.first->NumNonZeroElems(); ++jcol)
{
J[jcol] = r_and_mlh.first->GetJ()[jcol];
}
r_and_mlh.second = std::unique_ptr<SparseMatrix>(
new SparseMatrix(I, J, NULL, r_and_mlh.first->Height(),
r_and_mlh.first->Width(), true, true, true));
IntegrationPointTransformation ip_tr;
IsoparametricTransformation& emb_tr = ip_tr.Transf;
// Compute M_LH and R
offsets.SetSize(nel_ho+1);
offsets[0] = 0;
for (int iho = 0; iho < nel_ho; ++iho)
{
Array<int> lor_els;
ho2lor.GetRow(iho, lor_els);
int nref = ho2lor.RowSize(iho);
Geometry::Type geom = mesh_ho->GetElementBaseGeometry(iho);
const FiniteElement& fe_ho = *fes_ho.GetFE(iho);
const FiniteElement& fe_lor = *fes_lor.GetFE(lor_els[0]);
offsets[iho+1] = offsets[iho] + fe_ho.GetDof()*fe_lor.GetDof()*nref;
ElementTransformation *tr_ho = fes_ho.GetElementTransformation(iho);
emb_tr.SetIdentityTransformation(geom);
const DenseTensor& pmats = cf_tr.point_matrices[geom];
int nedof_ho = fe_ho.GetDof();
int nedof_lor = fe_lor.GetDof();
DenseMatrix M_LH_el(nedof_lor, nedof_ho);
DenseMatrix R_el(nedof_lor, nedof_ho);
for (int iref = 0; iref < nref; ++iref)
{
int ilor = lor_els[iref];
ElementTransformation* tr_lor = fes_lor.GetElementTransformation(ilor);
// Create the transformation that embeds the fine low-order element
// within the coarse high-order element in reference space
emb_tr.SetPointMat(pmats(cf_tr.embeddings[ilor].matrix));
ElemMixedMass(geom, fe_ho, fe_lor, tr_ho, tr_lor, ip_tr, M_LH_el);
Array<int> dofs_lor(nedof_lor);
fes_lor.GetElementDofs(ilor, dofs_lor);
Vector R_row;
for (int i = 0; i < nedof_lor; ++i)
{
M_LH_el.GetRow(i, R_row);
R_el.SetRow(i, R_row.Set(ML_inv[dofs_lor[i]], R_row));
}
Array<int> dofs_ho(nedof_ho);
fes_ho.GetElementDofs(iho, dofs_ho);
r_and_mlh.second->AddSubMatrix(dofs_lor, dofs_ho, M_LH_el);
r_and_mlh.first->AddSubMatrix(dofs_lor, dofs_ho, R_el);
}
}
return r_and_mlh;
}
void L2ProjectionGridTransfer::L2ProjectionH1Space::GetTDofs(
const FiniteElementSpace& fes, const Vector& x, Vector& X) const
{
const Operator* res = fes.GetRestrictionOperator();
if (res)
{
res->Mult(x, X);
}
else
{
X = x;
}
}
void L2ProjectionGridTransfer::L2ProjectionH1Space::SetFromTDofs(
const FiniteElementSpace& fes, const Vector &X, Vector& x) const
{
const Operator* P = fes.GetProlongationMatrix();
if (P)
{
P->Mult(X, x);
}
else
{
x = X;
}
}
void L2ProjectionGridTransfer::L2ProjectionH1Space::GetTDofsTranspose(
const FiniteElementSpace& fes, const Vector& x, Vector& X) const
{
const Operator* P = fes.GetProlongationMatrix();
if (P)
{
P->MultTranspose(x, X);
}
else
{
X = x;
}
}
void L2ProjectionGridTransfer::L2ProjectionH1Space::SetFromTDofsTranspose(
const FiniteElementSpace& fes, const Vector &X, Vector& x) const
{
const Operator *R_op = fes.GetRestrictionOperator();
if (R_op)
{
R_op->MultTranspose(X, x);
}
else
{
x = X;
}
}
void L2ProjectionGridTransfer::L2ProjectionH1Space::TDofsListByVDim(
const FiniteElementSpace& fes, int vdim, Array<int>& vdofs_list) const
{
const SparseMatrix *R_mat = fes.GetRestrictionMatrix();
if (R_mat)
{
Array<int> x_vdofs_list(fes.GetNDofs());
Array<int> x_vdofs_marker(fes.GetVSize());
Array<int> X_vdofs_marker(fes.GetTrueVSize());
fes.GetVDofs(vdim, x_vdofs_list);
FiniteElementSpace::ListToMarker(x_vdofs_list, fes.GetVSize(), x_vdofs_marker);
R_mat->BooleanMult(x_vdofs_marker, X_vdofs_marker);
FiniteElementSpace::MarkerToList(X_vdofs_marker, vdofs_list);
}
else
{
vdofs_list.SetSize(fes.GetNDofs());
fes.GetVDofs(vdim, vdofs_list);
}
}
void L2ProjectionGridTransfer::L2ProjectionH1Space::LumpedMassInverse(
Vector& ML_inv) const
{
#ifdef MFEM_USE_MPI
// LumpedMassInverse may get called from serial and MPI parallel routines
// since we do not know which code path is calling it we must check if
// the pfes pointer is null when MPI is available.
auto * fes = pfes_lor_scalar == nullptr ? fes_lor_scalar.get() :
pfes_lor_scalar.get();
#else
auto * fes = fes_lor_scalar.get();
#endif
MFEM_ASSERT(fes != nullptr, "[p]fes_lor_scalar is nullptr");
Vector ML_inv_true(fes->GetTrueVSize());
const Operator *P = fes->GetProlongationMatrix();
if (P) { P->MultTranspose(ML_inv, ML_inv_true); }
else { ML_inv_true = ML_inv; }
ML_inv_true.Reciprocal();
if (P) { P->Mult(ML_inv_true, ML_inv); }
else { ML_inv = ML_inv_true; }
}
std::unique_ptr<SparseMatrix>
L2ProjectionGridTransfer::L2ProjectionH1Space::AllocR()
{
const Table& elem_dof_ho = fes_ho.GetElementToDofTable();
const Table& elem_dof_lor = fes_lor.GetElementToDofTable();
const int ndof_ho = fes_ho.GetNDofs();
const int ndof_lor = fes_lor.GetNDofs();
Table dof_elem_lor;
Transpose(elem_dof_lor, dof_elem_lor, ndof_lor);
Mesh* mesh_lor = fes_lor.GetMesh();
const CoarseFineTransformations& cf_tr = mesh_lor->GetRefinementTransforms();
// mfem::Mult but uses ho2lor to map HO elements to LOR elements
const int* elem_dof_hoI = elem_dof_ho.GetI();
const int* elem_dof_hoJ = elem_dof_ho.GetJ();
const int* dof_elem_lorI = dof_elem_lor.GetI();
const int* dof_elem_lorJ = dof_elem_lor.GetJ();
Array<int> I(ndof_lor + 1);
// figure out the size of J
Array<int> dof_used_ho;
dof_used_ho.SetSize(ndof_ho, -1);
int sizeJ = 0;
for (int ilor = 0; ilor < ndof_lor; ++ilor)
{
for (int jlor = dof_elem_lorI[ilor]; jlor < dof_elem_lorI[ilor + 1]; ++jlor)
{
int el_lor = dof_elem_lorJ[jlor];
int iho = cf_tr.embeddings[el_lor].parent;
for (int jho = elem_dof_hoI[iho]; jho < elem_dof_hoI[iho + 1]; ++jho)
{
int dof_ho = elem_dof_hoJ[jho];
if (dof_used_ho[dof_ho] != ilor)
{
dof_used_ho[dof_ho] = ilor;
++sizeJ;
}
}
}
}
// initialize dof_ho_dof_lor
Table dof_lor_dof_ho;
dof_lor_dof_ho.SetDims(ndof_lor, sizeJ);
for (int i = 0; i < ndof_ho; ++i)
{
dof_used_ho[i] = -1;
}
// set values of J
int* dof_dofI = dof_lor_dof_ho.GetI();
int* dof_dofJ = dof_lor_dof_ho.GetJ();
sizeJ = 0;
for (int ilor = 0; ilor < ndof_lor; ++ilor)
{
dof_dofI[ilor] = sizeJ;
for (int jlor = dof_elem_lorI[ilor]; jlor < dof_elem_lorI[ilor + 1]; ++jlor)
{
int el_lor = dof_elem_lorJ[jlor];
int iho = cf_tr.embeddings[el_lor].parent;
for (int jho = elem_dof_hoI[iho]; jho < elem_dof_hoI[iho + 1]; ++jho)
{
int dof_ho = elem_dof_hoJ[jho];
if (dof_used_ho[dof_ho] != ilor)
{
dof_used_ho[dof_ho] = ilor;
dof_dofJ[sizeJ] = dof_ho;
++sizeJ;
}
}
}
}
dof_lor_dof_ho.SortRows();
real_t* data = Memory<real_t>(dof_dofI[ndof_lor]);
std::unique_ptr<SparseMatrix> R_local(new SparseMatrix(
dof_dofI, dof_dofJ, data, ndof_lor,
ndof_ho, true, true, true));
(*R_local) = 0.0;
dof_lor_dof_ho.LoseData();
return R_local;
}
L2ProjectionGridTransfer::H1SpaceMixedMassOperator::H1SpaceMixedMassOperator(
const FiniteElementSpace* fes_ho_, const FiniteElementSpace* fes_lor_,
Table* ho2lor_, Vector* M_LH_ea_) :
Operator(fes_lor_->GetElementRestriction(ElementDofOrdering::NATIVE)->Width(),
fes_ho_->GetElementRestriction(ElementDofOrdering::NATIVE)->Width()),
fes_ho(fes_ho_), fes_lor(fes_lor_), ho2lor(ho2lor_),
M_LH_ea(M_LH_ea_)
{ }
void L2ProjectionGridTransfer::H1SpaceMixedMassOperator::Mult(const Vector &x,
Vector &y) const
{
if (fes_ho->GetNE() == 0)
{
return;
}
const Operator* elem_restrict_ho = fes_ho->GetElementRestriction(
ElementDofOrdering::NATIVE);
const Operator* elem_restrict_lor = fes_lor->GetElementRestriction(
ElementDofOrdering::NATIVE);
const int vdim = fes_ho->GetVDim();
const int iho = 0;
const int nref = ho2lor->RowSize(iho);
const int ndof_ho = fes_ho->GetFE(iho)->GetDof();
const int ndof_lor = fes_lor->GetFE(ho2lor->GetRow(iho)[0])->GetDof();
const Mesh *mesh_ho = fes_ho->GetMesh();
const int nel_ho = mesh_ho->GetNE();
Vector tempx(elem_restrict_ho->Height());
elem_restrict_ho->Mult(x, tempx);
Vector tempy(ndof_lor*nref*vdim*nel_ho);
auto v_M_mixed_ea = Reshape(M_LH_ea->Read(), ndof_lor, ndof_ho, nref,
nel_ho);
auto v_tempx = Reshape(tempx.Read(), ndof_ho, vdim, nel_ho);
auto v_tempy = Reshape(tempy.Write(), ndof_lor, nref, vdim, nel_ho);
mfem::forall(ndof_lor * nref * vdim * nel_ho, [=] MFEM_HOST_DEVICE (int tid)
{
const int j = tid % ndof_lor;
const int i = (tid / ndof_lor) % nref;
const int v = (tid / (ndof_lor * nref)) % vdim;
const int iho = (tid / (ndof_lor * nref * vdim)) % nel_ho;
real_t dot = 0.0;
for (int k=0; k<ndof_ho; ++k)
{
dot += v_M_mixed_ea(j, k, i, iho) * v_tempx(k, v, iho);
}
v_tempy(j, i, v, iho) = dot;
});
elem_restrict_lor->MultTranspose(tempy, y);
}
void L2ProjectionGridTransfer::H1SpaceMixedMassOperator::MultTranspose(
const Vector &x, Vector &y) const
{
if (fes_ho->GetNE() == 0)
{
return;
}
const Operator* elem_restrict_ho = fes_ho->GetElementRestriction(
ElementDofOrdering::NATIVE);
const Operator* elem_restrict_lor = fes_lor->GetElementRestriction(
ElementDofOrdering::NATIVE);
const int vdim = fes_ho->GetVDim();
const int iho = 0;
const int nref = ho2lor->RowSize(iho);
const int ndof_ho = fes_ho->GetFE(iho)->GetDof();
const int ndof_lor = fes_lor->GetFE(ho2lor->GetRow(iho)[0])->GetDof();
const Mesh *mesh_ho = fes_ho->GetMesh();
const int nel_ho = mesh_ho->GetNE();
Vector tempx(elem_restrict_lor->Height());
elem_restrict_lor->Mult(x, tempx);
Vector tempy(ndof_ho*vdim*nel_ho);
auto v_M_mixed_ea = Reshape(M_LH_ea->Read(), ndof_lor, ndof_ho, nref,
nel_ho);
auto v_tempx = Reshape(tempx.Read(), ndof_lor, nref, vdim, nel_ho);
auto v_tempy = Reshape(tempy.Write(), ndof_ho, vdim, nel_ho);
mfem::forall(ndof_ho * vdim * nel_ho, [=] MFEM_HOST_DEVICE (int tid)
{
const int k = tid % ndof_ho;
const int v = (tid / ndof_ho) % vdim;
const int iho = (tid / (ndof_ho * vdim)) % nel_ho;
real_t dot = 0.0;
for (int i=0; i<nref; ++i)
{
for (int j=0; j<ndof_lor; ++j)
{
dot += v_M_mixed_ea(j, k, i, iho) * v_tempx(j, i, v, iho);
}
v_tempy(k, v, iho) = dot;
}
});
elem_restrict_ho->MultTranspose(tempy, y);
}
L2ProjectionGridTransfer::H1SpaceLumpedMassOperator::H1SpaceLumpedMassOperator(
const FiniteElementSpace* fes_ho_,
const FiniteElementSpace* fes_lor_,
Vector& ML_inv_) :
Operator(ML_inv_.Size(), ML_inv_.Size()),
fes_ho(fes_ho_), fes_lor(fes_lor_),
ML_inv(&ML_inv_)
{ }
void L2ProjectionGridTransfer::H1SpaceLumpedMassOperator::Mult(const Vector &x,
Vector &y) const
{
MFEM_ASSERT(ML_inv->Size() == x.Size(), "sizes not the same");
auto v_ML_inv = Reshape(ML_inv->Read(), ML_inv->Size());
auto v_x = Reshape(x.Read(), x.Size());
auto v_y = Reshape(y.Write(), y.Size());
mfem::forall(ML_inv->Size(), [=] MFEM_HOST_DEVICE(int i)
{ v_y(i) = v_ML_inv(i) * v_x(i); });
}
void L2ProjectionGridTransfer::H1SpaceLumpedMassOperator::MultTranspose(
const Vector &x, Vector &y) const
{
this->Mult(x,y); // lumped diagonal has the same Mult and MultTranspose behavior
}
L2ProjectionGridTransfer::~L2ProjectionGridTransfer()
{
delete F;
delete B;
}
const Operator &L2ProjectionGridTransfer::ForwardOperator()
{
if (!F) { BuildF(); }
return *F;
}
const Operator &L2ProjectionGridTransfer::BackwardOperator()
{
if (!B)
{
if (!F) { BuildF(); }
B = new L2Prolongation(*F);
}
return *B;
}
void L2ProjectionGridTransfer::BuildF()
{
if (!force_l2_space &&
dom_fes.FEColl()->GetContType() == FiniteElementCollection::CONTINUOUS)
{
if (!Parallel())
{
F = new L2ProjectionH1Space(dom_fes, ran_fes,
use_ea, d_mt);
}
else
{
#ifdef MFEM_USE_MPI
const mfem::ParFiniteElementSpace& dom_pfes =
static_cast<mfem::ParFiniteElementSpace&>(dom_fes);
const mfem::ParFiniteElementSpace& ran_pfes =
static_cast<mfem::ParFiniteElementSpace&>(ran_fes);
F = new L2ProjectionH1Space(dom_pfes, ran_pfes,
use_ea, d_mt);
#endif
}
}
else
{
F = new L2ProjectionL2Space(dom_fes, ran_fes,
use_ea, d_mt);
}
}
bool L2ProjectionGridTransfer::SupportsBackwardsOperator() const
{
return ran_fes.GetTrueVSize() >= dom_fes.GetTrueVSize();
}
TransferOperator::TransferOperator(const FiniteElementSpace& lFESpace_,
const FiniteElementSpace& hFESpace_)
: Operator(hFESpace_.GetVSize(), lFESpace_.GetVSize())
{
bool isvar_order = lFESpace_.IsVariableOrder() || hFESpace_.IsVariableOrder();
if (lFESpace_.FEColl() == hFESpace_.FEColl() && !isvar_order)
{
OperatorPtr P(Operator::ANY_TYPE);
hFESpace_.GetTransferOperator(lFESpace_, P);
P.SetOperatorOwner(false);
opr = P.Ptr();
}
else if (lFESpace_.GetVDim() == 1
&& hFESpace_.GetVDim() == 1
&& dynamic_cast<const TensorBasisElement*>(lFESpace_.GetTypicalFE())
&& dynamic_cast<const TensorBasisElement*>(hFESpace_.GetTypicalFE())
&& !isvar_order
&& (hFESpace_.FEColl()->GetContType() ==
mfem::FiniteElementCollection::CONTINUOUS ||
hFESpace_.FEColl()->GetContType() ==
mfem::FiniteElementCollection::DISCONTINUOUS))
{
opr = new TensorProductPRefinementTransferOperator(lFESpace_, hFESpace_);
}
else
{
opr = new PRefinementTransferOperator(lFESpace_, hFESpace_);
}
}
TransferOperator::~TransferOperator() { delete opr; }
void TransferOperator::Mult(const Vector& x, Vector& y) const
{
opr->Mult(x, y);
}
void TransferOperator::MultTranspose(const Vector& x, Vector& y) const
{
opr->MultTranspose(x, y);
}
PRefinementTransferOperator::PRefinementTransferOperator(
const FiniteElementSpace& lFESpace_, const FiniteElementSpace& hFESpace_)
: Operator(hFESpace_.GetVSize(), lFESpace_.GetVSize()), lFESpace(lFESpace_),
hFESpace(hFESpace_)
{
isvar_order = lFESpace_.IsVariableOrder() || hFESpace_.IsVariableOrder();
}
void PRefinementTransferOperator::Mult(const Vector& x, Vector& y) const
{
Mesh* mesh = hFESpace.GetMesh();
Array<int> l_dofs, h_dofs, l_vdofs, h_vdofs;
DenseMatrix loc_prol;
Vector subY, subX;
Geometry::Type cached_geom = Geometry::INVALID;
const FiniteElement* h_fe = NULL;
const FiniteElement* l_fe = NULL;
IsoparametricTransformation T;
int vdim = lFESpace.GetVDim();
y = 0.0;
DofTransformation doftrans_h, doftrans_l;
for (int i = 0; i < mesh->GetNE(); i++)
{
hFESpace.GetElementDofs(i, h_dofs, doftrans_h);
lFESpace.GetElementDofs(i, l_dofs, doftrans_l);
const Geometry::Type geom = mesh->GetElementBaseGeometry(i);
if (geom != cached_geom || isvar_order)
{
h_fe = hFESpace.GetFE(i);
l_fe = lFESpace.GetFE(i);
T.SetIdentityTransformation(h_fe->GetGeomType());
h_fe->GetTransferMatrix(*l_fe, T, loc_prol);
subY.SetSize(loc_prol.Height());
cached_geom = geom;
}
for (int vd = 0; vd < vdim; vd++)
{
l_dofs.Copy(l_vdofs);
lFESpace.DofsToVDofs(vd, l_vdofs);
h_dofs.Copy(h_vdofs);
hFESpace.DofsToVDofs(vd, h_vdofs);
x.GetSubVector(l_vdofs, subX);
doftrans_l.InvTransformPrimal(subX);
loc_prol.Mult(subX, subY);
doftrans_h.TransformPrimal(subY);
y.SetSubVector(h_vdofs, subY);
}
}
}
void PRefinementTransferOperator::MultTranspose(const Vector& x,
Vector& y) const
{
y = 0.0;
Mesh* mesh = hFESpace.GetMesh();
Array<int> l_dofs, h_dofs, l_vdofs, h_vdofs;
DenseMatrix loc_prol;
Vector subY, subX;
Array<char> processed(hFESpace.GetVSize());
processed = 0;
Geometry::Type cached_geom = Geometry::INVALID;
const FiniteElement* h_fe = NULL;
const FiniteElement* l_fe = NULL;
IsoparametricTransformation T;
int vdim = lFESpace.GetVDim();
DofTransformation doftrans_h, doftrans_l;
for (int i = 0; i < mesh->GetNE(); i++)
{
hFESpace.GetElementDofs(i, h_dofs, doftrans_h);
lFESpace.GetElementDofs(i, l_dofs, doftrans_l);
const Geometry::Type geom = mesh->GetElementBaseGeometry(i);
if (geom != cached_geom || isvar_order)
{
h_fe = hFESpace.GetFE(i);
l_fe = lFESpace.GetFE(i);
T.SetIdentityTransformation(h_fe->GetGeomType());
h_fe->GetTransferMatrix(*l_fe, T, loc_prol);
loc_prol.Transpose();
subY.SetSize(loc_prol.Height());
cached_geom = geom;
}
for (int vd = 0; vd < vdim; vd++)
{
l_dofs.Copy(l_vdofs);
lFESpace.DofsToVDofs(vd, l_vdofs);
h_dofs.Copy(h_vdofs);
hFESpace.DofsToVDofs(vd, h_vdofs);
x.GetSubVector(h_vdofs, subX);
doftrans_h.InvTransformDual(subX);
for (int p = 0; p < h_dofs.Size(); ++p)
{
if (processed[lFESpace.DecodeDof(h_dofs[p])])
{
subX[p] = 0.0;
}
}
loc_prol.Mult(subX, subY);
doftrans_l.TransformDual(subY);
y.AddElementVector(l_vdofs, subY);
}
for (int p = 0; p < h_dofs.Size(); ++p)
{
processed[lFESpace.DecodeDof(h_dofs[p])] = 1;
}
}
}
TensorProductPRefinementTransferOperator::
TensorProductPRefinementTransferOperator(
const FiniteElementSpace& lFESpace_,
const FiniteElementSpace& hFESpace_)
: Operator(hFESpace_.GetVSize(), lFESpace_.GetVSize()), lFESpace(lFESpace_),
hFESpace(hFESpace_)
{
// Assuming the same element type
Mesh* mesh = lFESpace.GetMesh();
dim = mesh->Dimension();
const FiniteElement& el = *lFESpace.GetTypicalFE();
const TensorBasisElement* ltel =
dynamic_cast<const TensorBasisElement*>(&el);
MFEM_VERIFY(ltel, "Low order FE space must be tensor product space");
const TensorBasisElement* htel =
dynamic_cast<const TensorBasisElement*>(hFESpace.GetTypicalFE());
MFEM_VERIFY(htel, "High order FE space must be tensor product space");
const Array<int>& hdofmap = htel->GetDofMap();
const IntegrationRule& ir = hFESpace.GetTypicalFE()->GetNodes();
IntegrationRule irLex = ir;
// The quadrature points, or equivalently, the dofs of the high order space
// must be sorted in lexicographical order
for (int i = 0; i < ir.GetNPoints(); ++i)
{
int j = hdofmap[i] >=0 ? hdofmap[i] : -1 - hdofmap[i];
irLex.IntPoint(i) = ir.IntPoint(j);
}
NE = lFESpace.GetNE();
const DofToQuad& maps = el.GetDofToQuad(irLex, DofToQuad::TENSOR);
D1D = maps.ndof;
Q1D = maps.nqpt;
B = maps.B;
Bt = maps.Bt;
elem_restrict_lex_l =
lFESpace.GetElementRestriction(ElementDofOrdering::LEXICOGRAPHIC);
MFEM_VERIFY(elem_restrict_lex_l,
"Low order ElementRestriction not available");
elem_restrict_lex_h =
hFESpace.GetElementRestriction(ElementDofOrdering::LEXICOGRAPHIC);
MFEM_VERIFY(elem_restrict_lex_h,
"High order ElementRestriction not available");
localL.SetSize(elem_restrict_lex_l->Height(), Device::GetMemoryType());
localH.SetSize(elem_restrict_lex_h->Height(), Device::GetMemoryType());
localL.UseDevice(true);
localH.UseDevice(true);
MFEM_VERIFY(dynamic_cast<const ElementRestriction*>(elem_restrict_lex_h),
"High order element restriction is of unsupported type");
mask.SetSize(localH.Size(), Device::GetMemoryType());
static_cast<const ElementRestriction*>(elem_restrict_lex_h)
->BooleanMask(mask);
mask.UseDevice(true);
}
namespace TransferKernels
{
void Prolongation2D(const int NE, const int D1D, const int Q1D,
const Vector& localL, Vector& localH,
const Array<real_t>& B, const Vector& mask)
{
auto x_ = Reshape(localL.Read(), D1D, D1D, NE);
auto y_ = Reshape(localH.Write(), Q1D, Q1D, NE);
auto B_ = Reshape(B.Read(), Q1D, D1D);
auto m_ = Reshape(mask.Read(), Q1D, Q1D, NE);
mfem::forall(NE, [=] MFEM_HOST_DEVICE (int e)
{
for (int qy = 0; qy < Q1D; ++qy)
{
for (int qx = 0; qx < Q1D; ++qx)
{
y_(qx, qy, e) = 0.0;
}
}
for (int dy = 0; dy < D1D; ++dy)
{
real_t sol_x[DofQuadLimits::MAX_Q1D];
for (int qy = 0; qy < Q1D; ++qy)
{
sol_x[qy] = 0.0;
}
for (int dx = 0; dx < D1D; ++dx)
{
const real_t s = x_(dx, dy, e);
for (int qx = 0; qx < Q1D; ++qx)
{
sol_x[qx] += B_(qx, dx) * s;
}
}
for (int qy = 0; qy < Q1D; ++qy)
{
const real_t d2q = B_(qy, dy);
for (int qx = 0; qx < Q1D; ++qx)
{
y_(qx, qy, e) += d2q * sol_x[qx];
}
}
}
for (int qy = 0; qy < Q1D; ++qy)
{
for (int qx = 0; qx < Q1D; ++qx)
{
y_(qx, qy, e) *= m_(qx, qy, e);
}
}
});
}
void Prolongation3D(const int NE, const int D1D, const int Q1D,
const Vector& localL, Vector& localH,
const Array<real_t>& B, const Vector& mask)
{
auto x_ = Reshape(localL.Read(), D1D, D1D, D1D, NE);
auto y_ = Reshape(localH.Write(), Q1D, Q1D, Q1D, NE);
auto B_ = Reshape(B.Read(), Q1D, D1D);
auto m_ = Reshape(mask.Read(), Q1D, Q1D, Q1D, NE);
mfem::forall(NE, [=] MFEM_HOST_DEVICE (int e)
{
for (int qz = 0; qz < Q1D; ++qz)
{
for (int qy = 0; qy < Q1D; ++qy)
{
for (int qx = 0; qx < Q1D; ++qx)
{
y_(qx, qy, qz, e) = 0.0;
}
}
}
for (int dz = 0; dz < D1D; ++dz)
{
real_t sol_xy[DofQuadLimits::MAX_Q1D][DofQuadLimits::MAX_Q1D];
for (int qy = 0; qy < Q1D; ++qy)
{
for (int qx = 0; qx < Q1D; ++qx)
{
sol_xy[qy][qx] = 0.0;
}
}
for (int dy = 0; dy < D1D; ++dy)
{
real_t sol_x[DofQuadLimits::MAX_Q1D];
for (int qx = 0; qx < Q1D; ++qx)
{
sol_x[qx] = 0;
}
for (int dx = 0; dx < D1D; ++dx)
{
const real_t s = x_(dx, dy, dz, e);
for (int qx = 0; qx < Q1D; ++qx)
{
sol_x[qx] += B_(qx, dx) * s;
}
}
for (int qy = 0; qy < Q1D; ++qy)
{
const real_t wy = B_(qy, dy);
for (int qx = 0; qx < Q1D; ++qx)
{
sol_xy[qy][qx] += wy * sol_x[qx];
}
}
}
for (int qz = 0; qz < Q1D; ++qz)
{
const real_t wz = B_(qz, dz);
for (int qy = 0; qy < Q1D; ++qy)
{
for (int qx = 0; qx < Q1D; ++qx)
{
y_(qx, qy, qz, e) += wz * sol_xy[qy][qx];
}
}
}
}
for (int qz = 0; qz < Q1D; ++qz)
{
for (int qy = 0; qy < Q1D; ++qy)
{
for (int qx = 0; qx < Q1D; ++qx)
{
y_(qx, qy, qz, e) *= m_(qx, qy, qz, e);
}
}
}
});
}
void Restriction2D(const int NE, const int D1D, const int Q1D,
const Vector& localH, Vector& localL,
const Array<real_t>& Bt, const Vector& mask)
{
auto x_ = Reshape(localH.Read(), Q1D, Q1D, NE);
auto y_ = Reshape(localL.Write(), D1D, D1D, NE);
auto Bt_ = Reshape(Bt.Read(), D1D, Q1D);
auto m_ = Reshape(mask.Read(), Q1D, Q1D, NE);
mfem::forall(NE, [=] MFEM_HOST_DEVICE (int e)
{
for (int dy = 0; dy < D1D; ++dy)
{
for (int dx = 0; dx < D1D; ++dx)
{
y_(dx, dy, e) = 0.0;
}
}
for (int qy = 0; qy < Q1D; ++qy)
{
real_t sol_x[DofQuadLimits::MAX_D1D];
for (int dx = 0; dx < D1D; ++dx)
{
sol_x[dx] = 0.0;
}
for (int qx = 0; qx < Q1D; ++qx)
{
const real_t s = m_(qx, qy, e) * x_(qx, qy, e);
for (int dx = 0; dx < D1D; ++dx)
{
sol_x[dx] += Bt_(dx, qx) * s;
}
}
for (int dy = 0; dy < D1D; ++dy)
{
const real_t q2d = Bt_(dy, qy);
for (int dx = 0; dx < D1D; ++dx)
{
y_(dx, dy, e) += q2d * sol_x[dx];
}
}
}
});
}
void Restriction3D(const int NE, const int D1D, const int Q1D,
const Vector& localH, Vector& localL,
const Array<real_t>& Bt, const Vector& mask)
{
auto x_ = Reshape(localH.Read(), Q1D, Q1D, Q1D, NE);
auto y_ = Reshape(localL.Write(), D1D, D1D, D1D, NE);
auto Bt_ = Reshape(Bt.Read(), D1D, Q1D);
auto m_ = Reshape(mask.Read(), Q1D, Q1D, Q1D, NE);
mfem::forall(NE, [=] MFEM_HOST_DEVICE (int e)
{
for (int dz = 0; dz < D1D; ++dz)
{
for (int dy = 0; dy < D1D; ++dy)
{
for (int dx = 0; dx < D1D; ++dx)
{
y_(dx, dy, dz, e) = 0.0;
}
}
}
for (int qz = 0; qz < Q1D; ++qz)
{
real_t sol_xy[DofQuadLimits::MAX_D1D][DofQuadLimits::MAX_D1D];
for (int dy = 0; dy < D1D; ++dy)
{
for (int dx = 0; dx < D1D; ++dx)
{
sol_xy[dy][dx] = 0;
}
}
for (int qy = 0; qy < Q1D; ++qy)
{
real_t sol_x[DofQuadLimits::MAX_D1D];
for (int dx = 0; dx < D1D; ++dx)
{
sol_x[dx] = 0;
}
for (int qx = 0; qx < Q1D; ++qx)
{
const real_t s = m_(qx, qy, qz, e) * x_(qx, qy, qz, e);
for (int dx = 0; dx < D1D; ++dx)
{
sol_x[dx] += Bt_(dx, qx) * s;
}
}
for (int dy = 0; dy < D1D; ++dy)
{
const real_t wy = Bt_(dy, qy);
for (int dx = 0; dx < D1D; ++dx)
{
sol_xy[dy][dx] += wy * sol_x[dx];
}
}
}
for (int dz = 0; dz < D1D; ++dz)
{
const real_t wz = Bt_(dz, qz);
for (int dy = 0; dy < D1D; ++dy)
{
for (int dx = 0; dx < D1D; ++dx)
{
y_(dx, dy, dz, e) += wz * sol_xy[dy][dx];
}
}
}
}
});
}
} // namespace TransferKernels
void TensorProductPRefinementTransferOperator::Mult(const Vector& x,
Vector& y) const
{
if (lFESpace.GetMesh()->GetNE() == 0)
{
return;
}
elem_restrict_lex_l->Mult(x, localL);
if (dim == 2)
{
TransferKernels::Prolongation2D(NE, D1D, Q1D, localL, localH, B, mask);
}
else if (dim == 3)
{
TransferKernels::Prolongation3D(NE, D1D, Q1D, localL, localH, B, mask);
}
else
{
MFEM_ABORT("TensorProductPRefinementTransferOperator::Mult not "
"implemented for dim = "
<< dim);
}
elem_restrict_lex_h->MultTranspose(localH, y);
}
void TensorProductPRefinementTransferOperator::MultTranspose(const Vector& x,
Vector& y) const
{
if (lFESpace.GetMesh()->GetNE() == 0)
{
return;
}
elem_restrict_lex_h->Mult(x, localH);
if (dim == 2)
{
TransferKernels::Restriction2D(NE, D1D, Q1D, localH, localL, Bt, mask);
}
else if (dim == 3)
{
TransferKernels::Restriction3D(NE, D1D, Q1D, localH, localL, Bt, mask);
}
else
{
MFEM_ABORT("TensorProductPRefinementTransferOperator::MultTranspose not "
"implemented for dim = "
<< dim);
}
elem_restrict_lex_l->MultTranspose(localL, y);
}
TrueTransferOperator::TrueTransferOperator(const FiniteElementSpace& lFESpace_,
const FiniteElementSpace& hFESpace_)
: Operator(hFESpace_.GetTrueVSize(), lFESpace_.GetTrueVSize()),
lFESpace(lFESpace_),
hFESpace(hFESpace_)
{
localTransferOperator = new TransferOperator(lFESpace_, hFESpace_);
P = lFESpace.GetProlongationMatrix();
R = hFESpace.IsVariableOrder() ? hFESpace.GetHpRestrictionMatrix() :
hFESpace.GetRestrictionMatrix();
// P and R can be both null
// P can be null and R not null
// If P is not null it is assumed that R is not null as well
if (P) { MFEM_VERIFY(R, "Both P and R have to be not NULL") }
if (P)
{
tmpL.SetSize(lFESpace_.GetVSize());
tmpH.SetSize(hFESpace_.GetVSize());
}
// P can be null and R not null
else if (R)
{
tmpH.SetSize(hFESpace_.GetVSize());
}
}
TrueTransferOperator::~TrueTransferOperator()
{
delete localTransferOperator;
}
void TrueTransferOperator::Mult(const Vector& x, Vector& y) const
{
if (P)
{
P->Mult(x, tmpL);
localTransferOperator->Mult(tmpL, tmpH);
R->Mult(tmpH, y);
}
else if (R)
{
localTransferOperator->Mult(x, tmpH);
R->Mult(tmpH, y);
}
else
{
localTransferOperator->Mult(x, y);
}
}
void TrueTransferOperator::MultTranspose(const Vector& x, Vector& y) const
{
if (P)
{
R->MultTranspose(x, tmpH);
localTransferOperator->MultTranspose(tmpH, tmpL);
P->MultTranspose(tmpL, y);
}
else if (R)
{
R->MultTranspose(x, tmpH);
localTransferOperator->MultTranspose(tmpH, y);
}
else
{
localTransferOperator->MultTranspose(x, y);
}
}
} // namespace mfem