// 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(&dom_fes); const bool par_ran = dynamic_cast(&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(&oper); MFEM_VERIFY(mat != NULL, "Operator is not a SparseMatrix"); if (!out_cR) { t_oper.Reset(const_cast(mat), false); } else { t_oper.Reset(mfem::Mult(*out_cR, *mat)); } if (in_cP) { t_oper.Reset(mfem::Mult(*t_oper.As(), *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(&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(&fes_in); const ParFiniteElementSpace *pfes_out = dynamic_cast(&fes_out); const SparseMatrix *sp_mat = dynamic_cast(&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(&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; jGetNE(); 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 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 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 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; qiGetNE(); 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 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 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 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 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 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; vdGetNE(); 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 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; irefGetNE(); 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 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 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; vdGetDof(); 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 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(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(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 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 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 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 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 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 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 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 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 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, std::unique_ptr> L2ProjectionGridTransfer::L2ProjectionH1Space::ComputeSparseRAndM_LH() { std::pair, std::unique_ptr> 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(new SparseMatrix), std::unique_ptr(new SparseMatrix) ); } const CoarseFineTransformations& cf_tr = mesh_lor->GetRefinementTransforms(); int nref_max = 0; Array 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 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 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 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 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( 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 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 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 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& vdofs_list) const { const SparseMatrix *R_mat = fes.GetRestrictionMatrix(); if (R_mat) { Array x_vdofs_list(fes.GetNDofs()); Array x_vdofs_marker(fes.GetVSize()); Array 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 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 I(ndof_lor + 1); // figure out the size of J Array 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(dof_dofI[ndof_lor]); std::unique_ptr 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; kMultTranspose(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; iMultTranspose(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(dom_fes); const mfem::ParFiniteElementSpace& ran_pfes = static_cast(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(lFESpace_.GetTypicalFE()) && dynamic_cast(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 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 l_dofs, h_dofs, l_vdofs, h_vdofs; DenseMatrix loc_prol; Vector subY, subX; Array 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(&el); MFEM_VERIFY(ltel, "Low order FE space must be tensor product space"); const TensorBasisElement* htel = dynamic_cast(hFESpace.GetTypicalFE()); MFEM_VERIFY(htel, "High order FE space must be tensor product space"); const Array& 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(elem_restrict_lex_h), "High order element restriction is of unsupported type"); mask.SetSize(localH.Size(), Device::GetMemoryType()); static_cast(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& 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& 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& 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& 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