// Copyright (c) 2010-2020, Lawrence Livermore National Security, LLC. Produced // at the Lawrence Livermore National Laboratory. All Rights reserved. See files // LICENSE and NOTICE for details. LLNL-CODE-806117. // // This file is part of the MFEM library. For more information and source code // availability visit https://mfem.org. // // MFEM is free software; you can redistribute it and/or modify it under the // terms of the BSD-3 license. We welcome feedback and contributions, see file // CONTRIBUTING.md for details. // Implementation of FiniteElementSpace #include "../general/text.hpp" #include "../general/forall.hpp" #include "../mesh/mesh_headers.hpp" #include "fem.hpp" #include #include #include using namespace std; namespace mfem { template <> void Ordering:: DofsToVDofs(int ndofs, int vdim, Array &dofs) { // static method int size = dofs.Size(); dofs.SetSize(size*vdim); for (int vd = 1; vd < vdim; vd++) { for (int i = 0; i < size; i++) { dofs[i+size*vd] = Map(ndofs, vdim, dofs[i], vd); } } } template <> void Ordering:: DofsToVDofs(int ndofs, int vdim, Array &dofs) { // static method int size = dofs.Size(); dofs.SetSize(size*vdim); for (int vd = vdim-1; vd >= 0; vd--) { for (int i = 0; i < size; i++) { dofs[i+size*vd] = Map(ndofs, vdim, dofs[i], vd); } } } FiniteElementSpace::FiniteElementSpace() : mesh(NULL), fec(NULL), vdim(0), ordering(Ordering::byNODES), ndofs(0), nvdofs(0), nedofs(0), nfdofs(0), nbdofs(0), fdofs(NULL), bdofs(NULL), elem_dof(NULL), bdrElem_dof(NULL), NURBSext(NULL), own_ext(false), cP(NULL), cR(NULL), cP_is_set(false), Th(Operator::ANY_TYPE), sequence(0) { } FiniteElementSpace::FiniteElementSpace(const FiniteElementSpace &orig, Mesh *mesh, const FiniteElementCollection *fec) { mesh = mesh ? mesh : orig.mesh; fec = fec ? fec : orig.fec; NURBSExtension *NURBSext = NULL; if (orig.NURBSext && orig.NURBSext != orig.mesh->NURBSext) { #ifdef MFEM_USE_MPI ParNURBSExtension *pNURBSext = dynamic_cast(orig.NURBSext); if (pNURBSext) { NURBSext = new ParNURBSExtension(*pNURBSext); } else #endif { NURBSext = new NURBSExtension(*orig.NURBSext); } } Constructor(mesh, NURBSext, fec, orig.vdim, orig.ordering); } int FiniteElementSpace::GetOrder(int i) const { Geometry::Type GeomType = mesh->GetElementBaseGeometry(i); return fec->FiniteElementForGeometry(GeomType)->GetOrder(); } int FiniteElementSpace::GetFaceOrder(int i) const { Geometry::Type GeomType = mesh->GetFaceBaseGeometry(i); return fec->FiniteElementForGeometry(GeomType)->GetOrder(); } void FiniteElementSpace::DofsToVDofs (Array &dofs, int ndofs) const { if (vdim == 1) { return; } if (ndofs < 0) { ndofs = this->ndofs; } if (ordering == Ordering::byNODES) { Ordering::DofsToVDofs(ndofs, vdim, dofs); } else { Ordering::DofsToVDofs(ndofs, vdim, dofs); } } void FiniteElementSpace::DofsToVDofs(int vd, Array &dofs, int ndofs) const { if (vdim == 1) { return; } if (ndofs < 0) { ndofs = this->ndofs; } if (ordering == Ordering::byNODES) { for (int i = 0; i < dofs.Size(); i++) { dofs[i] = Ordering::Map(ndofs, vdim, dofs[i], vd); } } else { for (int i = 0; i < dofs.Size(); i++) { dofs[i] = Ordering::Map(ndofs, vdim, dofs[i], vd); } } } int FiniteElementSpace::DofToVDof(int dof, int vd, int ndofs) const { if (vdim == 1) { return dof; } if (ndofs < 0) { ndofs = this->ndofs; } if (ordering == Ordering::byNODES) { return Ordering::Map(ndofs, vdim, dof, vd); } else { return Ordering::Map(ndofs, vdim, dof, vd); } } // static function void FiniteElementSpace::AdjustVDofs (Array &vdofs) { int n = vdofs.Size(), *vdof = vdofs; for (int i = 0; i < n; i++) { int j; if ((j = vdof[i]) < 0) { vdof[i] = -1-j; } } } void FiniteElementSpace::GetElementVDofs(int i, Array &vdofs) const { GetElementDofs(i, vdofs); DofsToVDofs(vdofs); } void FiniteElementSpace::GetBdrElementVDofs(int i, Array &vdofs) const { GetBdrElementDofs(i, vdofs); DofsToVDofs(vdofs); } void FiniteElementSpace::GetFaceVDofs(int i, Array &vdofs) const { GetFaceDofs(i, vdofs); DofsToVDofs(vdofs); } void FiniteElementSpace::GetEdgeVDofs(int i, Array &vdofs) const { GetEdgeDofs(i, vdofs); DofsToVDofs(vdofs); } void FiniteElementSpace::GetVertexVDofs(int i, Array &vdofs) const { GetVertexDofs(i, vdofs); DofsToVDofs(vdofs); } void FiniteElementSpace::GetElementInteriorVDofs(int i, Array &vdofs) const { GetElementInteriorDofs(i, vdofs); DofsToVDofs(vdofs); } void FiniteElementSpace::GetEdgeInteriorVDofs(int i, Array &vdofs) const { GetEdgeInteriorDofs(i, vdofs); DofsToVDofs(vdofs); } void FiniteElementSpace::BuildElementToDofTable() const { if (elem_dof) { return; } Table *el_dof = new Table; Array dofs; el_dof -> MakeI (mesh -> GetNE()); for (int i = 0; i < mesh -> GetNE(); i++) { GetElementDofs (i, dofs); el_dof -> AddColumnsInRow (i, dofs.Size()); } el_dof -> MakeJ(); for (int i = 0; i < mesh -> GetNE(); i++) { GetElementDofs (i, dofs); el_dof -> AddConnections (i, (int *)dofs, dofs.Size()); } el_dof -> ShiftUpI(); elem_dof = el_dof; } void FiniteElementSpace::RebuildElementToDofTable() { delete elem_dof; elem_dof = NULL; BuildElementToDofTable(); } void FiniteElementSpace::ReorderElementToDofTable() { Array dof_marker(ndofs); dof_marker = -1; int *J = elem_dof->GetJ(), nnz = elem_dof->Size_of_connections(); for (int k = 0, dof_counter = 0; k < nnz; k++) { const int sdof = J[k]; // signed dof const int dof = (sdof < 0) ? -1-sdof : sdof; int new_dof = dof_marker[dof]; if (new_dof < 0) { dof_marker[dof] = new_dof = dof_counter++; } J[k] = (sdof < 0) ? -1-new_dof : new_dof; // preserve the sign of sdof } } void FiniteElementSpace::BuildDofToArrays() { if (dof_elem_array.Size()) { return; } BuildElementToDofTable(); dof_elem_array.SetSize (ndofs); dof_ldof_array.SetSize (ndofs); dof_elem_array = -1; for (int i = 0; i < mesh -> GetNE(); i++) { const int *dofs = elem_dof -> GetRow(i); const int n = elem_dof -> RowSize(i); for (int j = 0; j < n; j++) { if (dof_elem_array[dofs[j]] < 0) { dof_elem_array[dofs[j]] = i; dof_ldof_array[dofs[j]] = j; } } } } static void mark_dofs(const Array &dofs, Array &mark_array) { for (int i = 0; i < dofs.Size(); i++) { int k = dofs[i]; if (k < 0) { k = -1 - k; } mark_array[k] = -1; } } void FiniteElementSpace::GetEssentialVDofs(const Array &bdr_attr_is_ess, Array &ess_vdofs, int component) const { Array vdofs, dofs; ess_vdofs.SetSize(GetVSize()); ess_vdofs = 0; for (int i = 0; i < GetNBE(); i++) { if (bdr_attr_is_ess[GetBdrAttribute(i)-1]) { if (component < 0) { // Mark all components. GetBdrElementVDofs(i, vdofs); mark_dofs(vdofs, ess_vdofs); } else { GetBdrElementDofs(i, dofs); for (int d = 0; d < dofs.Size(); d++) { dofs[d] = DofToVDof(dofs[d], component); } mark_dofs(dofs, ess_vdofs); } } } // mark possible hidden boundary edges in a non-conforming mesh, also // local DOFs affected by boundary elements on other processors if (Nonconforming()) { Array bdr_verts, bdr_edges; mesh->ncmesh->GetBoundaryClosure(bdr_attr_is_ess, bdr_verts, bdr_edges); for (int i = 0; i < bdr_verts.Size(); i++) { if (component < 0) { GetVertexVDofs(bdr_verts[i], vdofs); mark_dofs(vdofs, ess_vdofs); } else { GetVertexDofs(bdr_verts[i], dofs); for (int d = 0; d < dofs.Size(); d++) { dofs[d] = DofToVDof(dofs[d], component); } mark_dofs(dofs, ess_vdofs); } } for (int i = 0; i < bdr_edges.Size(); i++) { if (component < 0) { GetEdgeVDofs(bdr_edges[i], vdofs); mark_dofs(vdofs, ess_vdofs); } else { GetEdgeDofs(bdr_edges[i], dofs); for (int d = 0; d < dofs.Size(); d++) { dofs[d] = DofToVDof(dofs[d], component); } mark_dofs(dofs, ess_vdofs); } } } } void FiniteElementSpace::GetEssentialTrueDofs(const Array &bdr_attr_is_ess, Array &ess_tdof_list, int component) { Array ess_vdofs, ess_tdofs; GetEssentialVDofs(bdr_attr_is_ess, ess_vdofs, component); const SparseMatrix *R = GetConformingRestriction(); if (!R) { ess_tdofs.MakeRef(ess_vdofs); } else { R->BooleanMult(ess_vdofs, ess_tdofs); } MarkerToList(ess_tdofs, ess_tdof_list); } // static method void FiniteElementSpace::MarkerToList(const Array &marker, Array &list) { int num_marked = 0; marker.HostRead(); // make sure we can read the array on host for (int i = 0; i < marker.Size(); i++) { if (marker[i]) { num_marked++; } } list.SetSize(0); list.Reserve(num_marked); for (int i = 0; i < marker.Size(); i++) { if (marker[i]) { list.Append(i); } } } // static method void FiniteElementSpace::ListToMarker(const Array &list, int marker_size, Array &marker, int mark_val) { marker.SetSize(marker_size); marker = 0; for (int i = 0; i < list.Size(); i++) { marker[list[i]] = mark_val; } } void FiniteElementSpace::ConvertToConformingVDofs(const Array &dofs, Array &cdofs) { GetConformingProlongation(); if (cP) { cP->BooleanMultTranspose(dofs, cdofs); } else { dofs.Copy(cdofs); } } void FiniteElementSpace::ConvertFromConformingVDofs(const Array &cdofs, Array &dofs) { GetConformingRestriction(); if (cR) { cR->BooleanMultTranspose(cdofs, dofs); } else { cdofs.Copy(dofs); } } SparseMatrix * FiniteElementSpace::D2C_GlobalRestrictionMatrix (FiniteElementSpace *cfes) { int i, j; Array d_vdofs, c_vdofs; SparseMatrix *R; R = new SparseMatrix (cfes -> GetVSize(), GetVSize()); for (i = 0; i < mesh -> GetNE(); i++) { this -> GetElementVDofs (i, d_vdofs); cfes -> GetElementVDofs (i, c_vdofs); #ifdef MFEM_DEBUG if (d_vdofs.Size() != c_vdofs.Size()) { mfem_error ("FiniteElementSpace::D2C_GlobalRestrictionMatrix (...)"); } #endif for (j = 0; j < d_vdofs.Size(); j++) { R -> Set (c_vdofs[j], d_vdofs[j], 1.0); } } R -> Finalize(); return R; } SparseMatrix * FiniteElementSpace::D2Const_GlobalRestrictionMatrix(FiniteElementSpace *cfes) { int i, j; Array d_dofs, c_dofs; SparseMatrix *R; R = new SparseMatrix (cfes -> GetNDofs(), ndofs); for (i = 0; i < mesh -> GetNE(); i++) { this -> GetElementDofs (i, d_dofs); cfes -> GetElementDofs (i, c_dofs); #ifdef MFEM_DEBUG if (c_dofs.Size() != 1) mfem_error ("FiniteElementSpace::" "D2Const_GlobalRestrictionMatrix (...)"); #endif for (j = 0; j < d_dofs.Size(); j++) { R -> Set (c_dofs[0], d_dofs[j], 1.0); } } R -> Finalize(); return R; } SparseMatrix * FiniteElementSpace::H2L_GlobalRestrictionMatrix (FiniteElementSpace *lfes) { SparseMatrix *R; DenseMatrix loc_restr; Array l_dofs, h_dofs, l_vdofs, h_vdofs; int vdim = lfes->GetVDim(); R = new SparseMatrix (vdim * lfes -> GetNDofs(), vdim * ndofs); Geometry::Type cached_geom = Geometry::INVALID; const FiniteElement *h_fe = NULL; const FiniteElement *l_fe = NULL; IsoparametricTransformation T; for (int i = 0; i < mesh -> GetNE(); i++) { this -> GetElementDofs (i, h_dofs); lfes -> GetElementDofs (i, l_dofs); // Assuming 'loc_restr' depends only on the Geometry::Type. const Geometry::Type geom = mesh->GetElementBaseGeometry(i); if (geom != cached_geom) { h_fe = this -> GetFE (i); l_fe = lfes -> GetFE (i); T.SetIdentityTransformation(h_fe->GetGeomType()); h_fe->Project(*l_fe, T, loc_restr); cached_geom = geom; } for (int vd = 0; vd < vdim; vd++) { l_dofs.Copy(l_vdofs); lfes->DofsToVDofs(vd, l_vdofs); h_dofs.Copy(h_vdofs); this->DofsToVDofs(vd, h_vdofs); R -> SetSubMatrix (l_vdofs, h_vdofs, loc_restr, 1); } } R -> Finalize(); return R; } void FiniteElementSpace::AddDependencies(SparseMatrix& deps, Array& master_dofs, Array& slave_dofs, DenseMatrix& I) { for (int i = 0; i < slave_dofs.Size(); i++) { int sdof = slave_dofs[i]; if (!deps.RowSize(sdof)) // not processed yet? { for (int j = 0; j < master_dofs.Size(); j++) { double coef = I(i, j); if (std::abs(coef) > 1e-12) { int mdof = master_dofs[j]; if (mdof != sdof && mdof != (-1-sdof)) { deps.Add(sdof, mdof, coef); } } } } } } bool FiniteElementSpace::DofFinalizable(int dof, const Array& finalized, const SparseMatrix& deps) { const int* dep = deps.GetRowColumns(dof); int ndep = deps.RowSize(dof); // are all constraining DOFs finalized? for (int i = 0; i < ndep; i++) { if (!finalized[dep[i]]) { return false; } } return true; } void FiniteElementSpace::GetDegenerateFaceDofs(int index, Array &dofs, Geometry::Type master_geom) const { // In NC meshes with prisms/tets, a special constraint occurs where a // prism/tet edge is slave to another element's face. Rather than introduce a // new edge-face constraint type, we handle such cases as degenerate // face-face constraints, where the point-matrix rectangle has zero height. // This method returns DOFs for the first edge of the rectangle, duplicated // in the orthogonal direction, to resemble DOFs for a quadrilateral face. // The extra DOFs are ignored by FiniteElementSpace::AddDependencies. Array edof; GetEdgeDofs(-1 - index, edof); int nv = fec->DofForGeometry(Geometry::POINT); int ne = fec->DofForGeometry(Geometry::SEGMENT); int nn = 2*nv + ne; dofs.SetSize(nn*nn); if (!dofs.Size()) { return; } dofs = edof[0]; // copy first two vertex DOFs for (int i = 0; i < nv; i++) { dofs[i] = edof[i]; dofs[nv+i] = edof[nv+i]; } // copy first edge DOFs int face_vert = Geometry::NumVerts[master_geom]; for (int i = 0; i < ne; i++) { dofs[face_vert*nv + i] = edof[2*nv + i]; } } void FiniteElementSpace::GetEntityDofs(int entity, int index, Array &dofs, Geometry::Type master_geom) const { switch (entity) { case 0: GetVertexDofs(index, dofs); break; case 1: GetEdgeDofs(index, dofs); break; case 2: (index >= 0) ? GetFaceDofs(index, dofs) /* */ : GetDegenerateFaceDofs(index, dofs, master_geom); } } void FiniteElementSpace::BuildConformingInterpolation() const { #ifdef MFEM_USE_MPI MFEM_VERIFY(dynamic_cast(this) == NULL, "This method should not be used with a ParFiniteElementSpace!"); #endif if (cP_is_set) { return; } cP_is_set = true; // For each slave DOF, the dependency matrix will contain a row that // expresses the slave DOF as a linear combination of its immediate master // DOFs. Rows of independent DOFs will remain empty. SparseMatrix deps(ndofs); // collect local edge/face dependencies for (int entity = 1; entity <= 2; entity++) { const NCMesh::NCList &list = mesh->ncmesh->GetNCList(entity); if (!list.masters.size()) { continue; } Array master_dofs, slave_dofs; IsoparametricTransformation T; DenseMatrix I; // loop through all master edges/faces, constrain their slave edges/faces for (unsigned mi = 0; mi < list.masters.size(); mi++) { const NCMesh::Master &master = list.masters[mi]; GetEntityDofs(entity, master.index, master_dofs); if (!master_dofs.Size()) { continue; } const FiniteElement* fe = fec->FiniteElementForGeometry(master.Geom()); if (!fe) { continue; } switch (master.geom) { case Geometry::SQUARE: T.SetFE(&QuadrilateralFE); break; case Geometry::TRIANGLE: T.SetFE(&TriangleFE); break; case Geometry::SEGMENT: T.SetFE(&SegmentFE); break; default: MFEM_ABORT("unsupported geometry"); } for (int si = master.slaves_begin; si < master.slaves_end; si++) { const NCMesh::Slave &slave = list.slaves[si]; GetEntityDofs(entity, slave.index, slave_dofs, master.Geom()); if (!slave_dofs.Size()) { continue; } slave.OrientedPointMatrix(T.GetPointMat()); fe->GetLocalInterpolation(T, I); // make each slave DOF dependent on all master DOFs AddDependencies(deps, master_dofs, slave_dofs, I); } } } deps.Finalize(); // DOFs that stayed independent are true DOFs int n_true_dofs = 0; for (int i = 0; i < ndofs; i++) { if (!deps.RowSize(i)) { n_true_dofs++; } } // if all dofs are true dofs leave cP and cR NULL if (n_true_dofs == ndofs) { cP = cR = NULL; // will be treated as identities return; } // create the conforming restriction matrix cR int *cR_J; { int *cR_I = Memory(n_true_dofs+1); double *cR_A = Memory(n_true_dofs); cR_J = Memory(n_true_dofs); for (int i = 0; i < n_true_dofs; i++) { cR_I[i] = i; cR_A[i] = 1.0; } cR_I[n_true_dofs] = n_true_dofs; cR = new SparseMatrix(cR_I, cR_J, cR_A, n_true_dofs, ndofs); } // create the conforming prolongation matrix cP cP = new SparseMatrix(ndofs, n_true_dofs); Array finalized(ndofs); finalized = false; // put identity in the restriction and prolongation matrices for true DOFs for (int i = 0, true_dof = 0; i < ndofs; i++) { if (!deps.RowSize(i)) { cR_J[true_dof] = i; cP->Add(i, true_dof++, 1.0); finalized[i] = true; } } // Now calculate cP rows of slave DOFs as combinations of cP rows of their // master DOFs. It is possible that some slave DOFs depend on DOFs that are // themselves slaves. Here we resolve such indirect constraints by first // calculating rows of the cP matrix for DOFs whose master DOF cP rows are // already known (in the first iteration these are the true DOFs). In the // second iteration, slaves of slaves can be 'finalized' (given a row in the // cP matrix), in the third iteration slaves of slaves of slaves, etc. bool finished; int n_finalized = n_true_dofs; Array cols; Vector srow; do { finished = true; for (int dof = 0; dof < ndofs; dof++) { if (!finalized[dof] && DofFinalizable(dof, finalized, deps)) { const int* dep_col = deps.GetRowColumns(dof); const double* dep_coef = deps.GetRowEntries(dof); int n_dep = deps.RowSize(dof); for (int j = 0; j < n_dep; j++) { cP->GetRow(dep_col[j], cols, srow); srow *= dep_coef[j]; cP->AddRow(dof, cols, srow); } finalized[dof] = true; n_finalized++; finished = false; } } } while (!finished); // if everything is consistent (mesh, face orientations, etc.), we should // be able to finalize all slave DOFs, otherwise it's a serious error if (n_finalized != ndofs) { MFEM_ABORT("Error creating cP matrix."); } cP->Finalize(); if (vdim > 1) { MakeVDimMatrix(*cP); MakeVDimMatrix(*cR); } if (Device::IsEnabled()) { cP->BuildTranspose(); } } void FiniteElementSpace::MakeVDimMatrix(SparseMatrix &mat) const { if (vdim == 1) { return; } int height = mat.Height(); int width = mat.Width(); SparseMatrix *vmat = new SparseMatrix(vdim*height, vdim*width); Array dofs, vdofs; Vector srow; for (int i = 0; i < height; i++) { mat.GetRow(i, dofs, srow); for (int vd = 0; vd < vdim; vd++) { dofs.Copy(vdofs); DofsToVDofs(vd, vdofs, width); vmat->SetRow(DofToVDof(i, vd, height), vdofs, srow); } } vmat->Finalize(); mat.Swap(*vmat); delete vmat; } const SparseMatrix* FiniteElementSpace::GetConformingProlongation() const { if (Conforming()) { return NULL; } if (!cP_is_set) { BuildConformingInterpolation(); } return cP; } const SparseMatrix* FiniteElementSpace::GetConformingRestriction() const { if (Conforming()) { return NULL; } if (!cP_is_set) { BuildConformingInterpolation(); } return cR; } int FiniteElementSpace::GetNConformingDofs() const { const SparseMatrix* P = GetConformingProlongation(); return P ? (P->Width() / vdim) : ndofs; } const Operator *FiniteElementSpace::GetElementRestriction( ElementDofOrdering e_ordering) const { // Check if we have a discontinuous space using the FE collection: if (IsDGSpace()) { if (L2E_nat.Ptr() == NULL) { L2E_nat.Reset(new L2ElementRestriction(*this)); } return L2E_nat.Ptr(); } if (e_ordering == ElementDofOrdering::LEXICOGRAPHIC) { if (L2E_lex.Ptr() == NULL) { L2E_lex.Reset(new ElementRestriction(*this, e_ordering)); } return L2E_lex.Ptr(); } // e_ordering == ElementDofOrdering::NATIVE if (L2E_nat.Ptr() == NULL) { L2E_nat.Reset(new ElementRestriction(*this, e_ordering)); } return L2E_nat.Ptr(); } const Operator *FiniteElementSpace::GetFaceRestriction( ElementDofOrdering e_ordering, FaceType type, L2FaceValues mul) const { const bool is_dg_space = IsDGSpace(); const L2FaceValues m = (is_dg_space && mul==L2FaceValues::DoubleValued) ? L2FaceValues::DoubleValued : L2FaceValues::SingleValued; key_face key = std::make_tuple(is_dg_space, e_ordering, type, m); auto itr = L2F.find(key); if (itr != L2F.end()) { return itr->second; } else { Operator* res; if (is_dg_space) { res = new L2FaceRestriction(*this, e_ordering, type, m); } else { res = new H1FaceRestriction(*this, e_ordering, type); } L2F[key] = res; return res; } } const QuadratureInterpolator *FiniteElementSpace::GetQuadratureInterpolator( const IntegrationRule &ir) const { for (int i = 0; i < E2Q_array.Size(); i++) { const QuadratureInterpolator *qi = E2Q_array[i]; if (qi->IntRule == &ir) { return qi; } } QuadratureInterpolator *qi = new QuadratureInterpolator(*this, ir); E2Q_array.Append(qi); return qi; } const QuadratureInterpolator *FiniteElementSpace::GetQuadratureInterpolator( const QuadratureSpace &qs) const { for (int i = 0; i < E2Q_array.Size(); i++) { const QuadratureInterpolator *qi = E2Q_array[i]; if (qi->qspace == &qs) { return qi; } } QuadratureInterpolator *qi = new QuadratureInterpolator(*this, qs); E2Q_array.Append(qi); return qi; } const FaceQuadratureInterpolator *FiniteElementSpace::GetFaceQuadratureInterpolator( const IntegrationRule &ir, FaceType type) const { if (type==FaceType::Interior) { for (int i = 0; i < E2IFQ_array.Size(); i++) { const FaceQuadratureInterpolator *qi = E2IFQ_array[i]; if (qi->IntRule == &ir) { return qi; } } FaceQuadratureInterpolator *qi = new FaceQuadratureInterpolator(*this, ir, type); E2IFQ_array.Append(qi); return qi; } else //Boundary { for (int i = 0; i < E2BFQ_array.Size(); i++) { const FaceQuadratureInterpolator *qi = E2BFQ_array[i]; if (qi->IntRule == &ir) { return qi; } } FaceQuadratureInterpolator *qi = new FaceQuadratureInterpolator(*this, ir, type); E2BFQ_array.Append(qi); return qi; } } SparseMatrix *FiniteElementSpace::RefinementMatrix_main( const int coarse_ndofs, const Table &coarse_elem_dof, const DenseTensor localP[]) const { MFEM_VERIFY(mesh->GetLastOperation() == Mesh::REFINE, ""); Array dofs, coarse_dofs, coarse_vdofs; Vector row; Mesh::GeometryList elem_geoms(*mesh); SparseMatrix *P; if (elem_geoms.Size() == 1) { const int coarse_ldof = localP[elem_geoms[0]].SizeJ(); P = new SparseMatrix(GetVSize(), coarse_ndofs*vdim, coarse_ldof); } else { P = new SparseMatrix(GetVSize(), coarse_ndofs*vdim); } Array mark(P->Height()); mark = 0; const CoarseFineTransformations &rtrans = mesh->GetRefinementTransforms(); for (int k = 0; k < mesh->GetNE(); k++) { const Embedding &emb = rtrans.embeddings[k]; const Geometry::Type geom = mesh->GetElementBaseGeometry(k); const DenseMatrix &lP = localP[geom](emb.matrix); const int fine_ldof = localP[geom].SizeI(); elem_dof->GetRow(k, dofs); coarse_elem_dof.GetRow(emb.parent, coarse_dofs); for (int vd = 0; vd < vdim; vd++) { coarse_dofs.Copy(coarse_vdofs); DofsToVDofs(vd, coarse_vdofs, coarse_ndofs); for (int i = 0; i < fine_ldof; i++) { int r = DofToVDof(dofs[i], vd); int m = (r >= 0) ? r : (-1 - r); if (!mark[m]) { lP.GetRow(i, row); P->SetRow(r, coarse_vdofs, row); mark[m] = 1; } } } } MFEM_ASSERT(mark.Sum() == P->Height(), "Not all rows of P set."); if (elem_geoms.Size() != 1) { P->Finalize(); } return P; } void FiniteElementSpace::GetLocalRefinementMatrices( Geometry::Type geom, DenseTensor &localP) const { const FiniteElement *fe = fec->FiniteElementForGeometry(geom); const CoarseFineTransformations &rtrans = mesh->GetRefinementTransforms(); const DenseTensor &pmats = rtrans.point_matrices[geom]; int nmat = pmats.SizeK(); int ldof = fe->GetDof(); IsoparametricTransformation isotr; isotr.SetIdentityTransformation(geom); // calculate local interpolation matrices for all refinement types localP.SetSize(ldof, ldof, nmat); for (int i = 0; i < nmat; i++) { isotr.SetPointMat(pmats(i)); fe->GetLocalInterpolation(isotr, localP(i)); } } SparseMatrix* FiniteElementSpace::RefinementMatrix(int old_ndofs, const Table* old_elem_dof) { MFEM_VERIFY(GetNE() >= old_elem_dof->Size(), "Previous mesh is not coarser."); Mesh::GeometryList elem_geoms(*mesh); DenseTensor localP[Geometry::NumGeom]; for (int i = 0; i < elem_geoms.Size(); i++) { GetLocalRefinementMatrices(elem_geoms[i], localP[elem_geoms[i]]); } return RefinementMatrix_main(old_ndofs, *old_elem_dof, localP); } FiniteElementSpace::RefinementOperator::RefinementOperator (const FiniteElementSpace* fespace, Table* old_elem_dof, int old_ndofs) : fespace(fespace) , old_elem_dof(old_elem_dof) { MFEM_VERIFY(fespace->GetNE() >= old_elem_dof->Size(), "Previous mesh is not coarser."); width = old_ndofs * fespace->GetVDim(); height = fespace->GetVSize(); Mesh::GeometryList elem_geoms(*fespace->GetMesh()); for (int i = 0; i < elem_geoms.Size(); i++) { fespace->GetLocalRefinementMatrices(elem_geoms[i], localP[elem_geoms[i]]); } } FiniteElementSpace::RefinementOperator::RefinementOperator( const FiniteElementSpace *fespace, const FiniteElementSpace *coarse_fes) : Operator(fespace->GetVSize(), coarse_fes->GetVSize()), fespace(fespace), old_elem_dof(NULL) { Mesh::GeometryList elem_geoms(*fespace->GetMesh()); for (int i = 0; i < elem_geoms.Size(); i++) { fespace->GetLocalRefinementMatrices(*coarse_fes, elem_geoms[i], localP[elem_geoms[i]]); } // Make a copy of the coarse elem_dof Table. old_elem_dof = new Table(coarse_fes->GetElementToDofTable()); } FiniteElementSpace::RefinementOperator::~RefinementOperator() { delete old_elem_dof; } void FiniteElementSpace::RefinementOperator ::Mult(const Vector &x, Vector &y) const { Mesh* mesh = fespace->GetMesh(); const CoarseFineTransformations &rtrans = mesh->GetRefinementTransforms(); Array dofs, vdofs, old_dofs, old_vdofs; int vdim = fespace->GetVDim(); int old_ndofs = width / vdim; Vector subY, subX; for (int k = 0; k < mesh->GetNE(); k++) { const Embedding &emb = rtrans.embeddings[k]; const Geometry::Type geom = mesh->GetElementBaseGeometry(k); const DenseMatrix &lP = localP[geom](emb.matrix); subY.SetSize(lP.Height()); fespace->GetElementDofs(k, dofs); old_elem_dof->GetRow(emb.parent, old_dofs); for (int vd = 0; vd < vdim; vd++) { dofs.Copy(vdofs); fespace->DofsToVDofs(vd, vdofs); old_dofs.Copy(old_vdofs); fespace->DofsToVDofs(vd, old_vdofs, old_ndofs); x.GetSubVector(old_vdofs, subX); lP.Mult(subX, subY); y.SetSubVector(vdofs, subY); } } } void FiniteElementSpace::RefinementOperator ::MultTranspose(const Vector &x, Vector &y) const { y = 0.0; Mesh* mesh = fespace->GetMesh(); const CoarseFineTransformations &rtrans = mesh->GetRefinementTransforms(); Array processed(fespace->GetVSize()); processed = 0; Array f_dofs, c_dofs, f_vdofs, c_vdofs; int vdim = fespace->GetVDim(); int old_ndofs = width / vdim; Vector subY, subX; for (int k = 0; k < mesh->GetNE(); k++) { const Embedding &emb = rtrans.embeddings[k]; const Geometry::Type geom = mesh->GetElementBaseGeometry(k); const DenseMatrix &lP = localP[geom](emb.matrix); fespace->GetElementDofs(k, f_dofs); old_elem_dof->GetRow(emb.parent, c_dofs); subY.SetSize(lP.Width()); for (int vd = 0; vd < vdim; vd++) { f_dofs.Copy(f_vdofs); fespace->DofsToVDofs(vd, f_vdofs); c_dofs.Copy(c_vdofs); fespace->DofsToVDofs(vd, c_vdofs, old_ndofs); x.GetSubVector(f_vdofs, subX); for (int p = 0; p < f_dofs.Size(); ++p) { if (processed[DecodeDof(f_dofs[p])]) { subX[p] = 0.0; } } lP.MultTranspose(subX, subY); y.AddElementVector(c_vdofs, subY); } for (int p = 0; p < f_dofs.Size(); ++p) { processed[DecodeDof(f_dofs[p])] = 1; } } } FiniteElementSpace::DerefinementOperator::DerefinementOperator( const FiniteElementSpace *f_fes, const FiniteElementSpace *c_fes, BilinearFormIntegrator *mass_integ) : Operator(c_fes->GetVSize(), f_fes->GetVSize()), fine_fes(f_fes) { MFEM_VERIFY(c_fes->GetOrdering() == f_fes->GetOrdering() && c_fes->GetVDim() == f_fes->GetVDim(), "incompatible coarse and fine FE spaces"); IsoparametricTransformation emb_tr; Mesh *f_mesh = f_fes->GetMesh(); const CoarseFineTransformations &rtrans = f_mesh->GetRefinementTransforms(); Mesh::GeometryList elem_geoms(*f_mesh); DenseTensor localP[Geometry::NumGeom], localM[Geometry::NumGeom]; for (int gi = 0; gi < elem_geoms.Size(); gi++) { const Geometry::Type geom = elem_geoms[gi]; DenseTensor &lP = localP[geom], &lM = localM[geom]; const FiniteElement *fine_fe = f_fes->fec->FiniteElementForGeometry(geom); const FiniteElement *coarse_fe = c_fes->fec->FiniteElementForGeometry(geom); const DenseTensor &pmats = rtrans.point_matrices[geom]; lP.SetSize(fine_fe->GetDof(), coarse_fe->GetDof(), pmats.SizeK()); lM.SetSize(fine_fe->GetDof(), fine_fe->GetDof(), pmats.SizeK()); emb_tr.SetIdentityTransformation(geom); for (int i = 0; i < pmats.SizeK(); i++) { emb_tr.SetPointMat(pmats(i)); // Get the local interpolation matrix for this refinement type fine_fe->GetTransferMatrix(*coarse_fe, emb_tr, lP(i)); // Get the local mass matrix for this refinement type mass_integ->AssembleElementMatrix(*fine_fe, emb_tr, lM(i)); } } Table ref_type_to_matrix; rtrans.GetCoarseToFineMap(*f_mesh, coarse_to_fine, coarse_to_ref_type, ref_type_to_matrix, ref_type_to_geom); MFEM_ASSERT(coarse_to_fine.Size() == c_fes->GetNE(), ""); const int total_ref_types = ref_type_to_geom.Size(); int num_ref_types[Geometry::NumGeom], num_fine_elems[Geometry::NumGeom]; Array ref_type_to_coarse_elem_offset(total_ref_types); ref_type_to_fine_elem_offset.SetSize(total_ref_types); std::fill(num_ref_types, num_ref_types+Geometry::NumGeom, 0); std::fill(num_fine_elems, num_fine_elems+Geometry::NumGeom, 0); for (int i = 0; i < total_ref_types; i++) { Geometry::Type g = ref_type_to_geom[i]; ref_type_to_coarse_elem_offset[i] = num_ref_types[g]; ref_type_to_fine_elem_offset[i] = num_fine_elems[g]; num_ref_types[g]++; num_fine_elems[g] += ref_type_to_matrix.RowSize(i); } DenseTensor localPtMP[Geometry::NumGeom]; for (int g = 0; g < Geometry::NumGeom; g++) { if (num_ref_types[g] == 0) { continue; } const int fine_dofs = localP[g].SizeI(); const int coarse_dofs = localP[g].SizeJ(); localPtMP[g].SetSize(coarse_dofs, coarse_dofs, num_ref_types[g]); localR[g].SetSize(coarse_dofs, fine_dofs, num_fine_elems[g]); } for (int i = 0; i < total_ref_types; i++) { Geometry::Type g = ref_type_to_geom[i]; DenseMatrix &lPtMP = localPtMP[g](ref_type_to_coarse_elem_offset[i]); int lR_offset = ref_type_to_fine_elem_offset[i]; // offset in localR[g] const int *mi = ref_type_to_matrix.GetRow(i); const int nm = ref_type_to_matrix.RowSize(i); lPtMP = 0.0; for (int s = 0; s < nm; s++) { DenseMatrix &lP = localP[g](mi[s]); DenseMatrix &lM = localM[g](mi[s]); DenseMatrix &lR = localR[g](lR_offset+s); MultAtB(lP, lM, lR); // lR = lP^T lM AddMult(lR, lP, lPtMP); // lPtMP += lP^T lM lP } DenseMatrixInverse lPtMP_inv(lPtMP); for (int s = 0; s < nm; s++) { DenseMatrix &lR = localR[g](lR_offset+s); lPtMP_inv.Mult(lR); // lR <- (P^T M P)^{-1} P^T M } } // Make a copy of the coarse element-to-dof Table. coarse_elem_dof = new Table(c_fes->GetElementToDofTable()); } FiniteElementSpace::DerefinementOperator::~DerefinementOperator() { delete coarse_elem_dof; } void FiniteElementSpace::DerefinementOperator ::Mult(const Vector &x, Vector &y) const { Array c_vdofs, f_vdofs; Vector loc_x, loc_y; DenseMatrix loc_x_mat, loc_y_mat; const int vdim = fine_fes->GetVDim(); const int coarse_ndofs = height/vdim; for (int coarse_el = 0; coarse_el < coarse_to_fine.Size(); coarse_el++) { coarse_elem_dof->GetRow(coarse_el, c_vdofs); fine_fes->DofsToVDofs(c_vdofs, coarse_ndofs); loc_y.SetSize(c_vdofs.Size()); loc_y = 0.0; loc_y_mat.UseExternalData(loc_y.GetData(), c_vdofs.Size()/vdim, vdim); const int ref_type = coarse_to_ref_type[coarse_el]; const Geometry::Type geom = ref_type_to_geom[ref_type]; const int *fine_elems = coarse_to_fine.GetRow(coarse_el); const int num_fine_elems = coarse_to_fine.RowSize(coarse_el); const int lR_offset = ref_type_to_fine_elem_offset[ref_type]; for (int s = 0; s < num_fine_elems; s++) { const DenseMatrix &lR = localR[geom](lR_offset+s); fine_fes->GetElementVDofs(fine_elems[s], f_vdofs); x.GetSubVector(f_vdofs, loc_x); loc_x_mat.UseExternalData(loc_x.GetData(), f_vdofs.Size()/vdim, vdim); AddMult(lR, loc_x_mat, loc_y_mat); } y.SetSubVector(c_vdofs, loc_y); } } void FiniteElementSpace::GetLocalDerefinementMatrices(Geometry::Type geom, DenseTensor &localR) const { const FiniteElement *fe = fec->FiniteElementForGeometry(geom); const CoarseFineTransformations &dtrans = mesh->ncmesh->GetDerefinementTransforms(); const DenseTensor &pmats = dtrans.point_matrices[geom]; const int nmat = pmats.SizeK(); const int ldof = fe->GetDof(); IsoparametricTransformation isotr; isotr.SetIdentityTransformation(geom); // calculate local restriction matrices for all refinement types localR.SetSize(ldof, ldof, nmat); for (int i = 0; i < nmat; i++) { isotr.SetPointMat(pmats(i)); fe->GetLocalRestriction(isotr, localR(i)); } } SparseMatrix* FiniteElementSpace::DerefinementMatrix(int old_ndofs, const Table* old_elem_dof) { MFEM_VERIFY(Nonconforming(), "Not implemented for conforming meshes."); MFEM_VERIFY(old_ndofs, "Missing previous (finer) space."); MFEM_VERIFY(ndofs <= old_ndofs, "Previous space is not finer."); Array dofs, old_dofs, old_vdofs; Vector row; Mesh::GeometryList elem_geoms(*mesh); DenseTensor localR[Geometry::NumGeom]; for (int i = 0; i < elem_geoms.Size(); i++) { GetLocalDerefinementMatrices(elem_geoms[i], localR[elem_geoms[i]]); } SparseMatrix *R = (elem_geoms.Size() != 1) ? new SparseMatrix(ndofs*vdim, old_ndofs*vdim) // variable row size : new SparseMatrix(ndofs*vdim, old_ndofs*vdim, localR[elem_geoms[0]].SizeI()); Array mark(R->Height()); mark = 0; const CoarseFineTransformations &dtrans = mesh->ncmesh->GetDerefinementTransforms(); MFEM_ASSERT(dtrans.embeddings.Size() == old_elem_dof->Size(), ""); int num_marked = 0; for (int k = 0; k < dtrans.embeddings.Size(); k++) { const Embedding &emb = dtrans.embeddings[k]; Geometry::Type geom = mesh->GetElementBaseGeometry(emb.parent); DenseMatrix &lR = localR[geom](emb.matrix); elem_dof->GetRow(emb.parent, dofs); old_elem_dof->GetRow(k, old_dofs); for (int vd = 0; vd < vdim; vd++) { old_dofs.Copy(old_vdofs); DofsToVDofs(vd, old_vdofs, old_ndofs); for (int i = 0; i < lR.Height(); i++) { if (!std::isfinite(lR(i, 0))) { continue; } int r = DofToVDof(dofs[i], vd); int m = (r >= 0) ? r : (-1 - r); if (!mark[m]) { lR.GetRow(i, row); R->SetRow(r, old_vdofs, row); mark[m] = 1; num_marked++; } } } } MFEM_VERIFY(num_marked == R->Height(), "internal error: not all rows of R were set."); R->Finalize(); // no-op if fixed width return R; } void FiniteElementSpace::GetLocalRefinementMatrices( const FiniteElementSpace &coarse_fes, Geometry::Type geom, DenseTensor &localP) const { // Assumptions: see the declaration of the method. const FiniteElement *fine_fe = fec->FiniteElementForGeometry(geom); const FiniteElement *coarse_fe = coarse_fes.fec->FiniteElementForGeometry(geom); const CoarseFineTransformations &rtrans = mesh->GetRefinementTransforms(); const DenseTensor &pmats = rtrans.point_matrices[geom]; int nmat = pmats.SizeK(); IsoparametricTransformation isotr; isotr.SetIdentityTransformation(geom); // Calculate the local interpolation matrices for all refinement types localP.SetSize(fine_fe->GetDof(), coarse_fe->GetDof(), nmat); for (int i = 0; i < nmat; i++) { isotr.SetPointMat(pmats(i)); fine_fe->GetTransferMatrix(*coarse_fe, isotr, localP(i)); } } void FiniteElementSpace::Constructor(Mesh *mesh, NURBSExtension *NURBSext, const FiniteElementCollection *fec, int vdim, int ordering) { this->mesh = mesh; this->fec = fec; this->vdim = vdim; this->ordering = (Ordering::Type) ordering; elem_dof = NULL; sequence = mesh->GetSequence(); Th.SetType(Operator::ANY_TYPE); const NURBSFECollection *nurbs_fec = dynamic_cast(fec); if (nurbs_fec) { if (!mesh->NURBSext) { mfem_error("FiniteElementSpace::FiniteElementSpace :\n" " NURBS FE space requires NURBS mesh."); } if (NURBSext == NULL) { this->NURBSext = mesh->NURBSext; own_ext = 0; } else { this->NURBSext = NURBSext; own_ext = 1; } UpdateNURBS(); cP = cR = NULL; cP_is_set = false; } else { this->NURBSext = NULL; own_ext = 0; Construct(); } BuildElementToDofTable(); } NURBSExtension *FiniteElementSpace::StealNURBSext() { if (NURBSext && !own_ext) { mfem_error("FiniteElementSpace::StealNURBSext"); } own_ext = 0; return NURBSext; } void FiniteElementSpace::UpdateNURBS() { nvdofs = 0; nedofs = 0; nfdofs = 0; nbdofs = 0; fdofs = NULL; bdofs = NULL; dynamic_cast(fec)->Reset(); ndofs = NURBSext->GetNDof(); elem_dof = NURBSext->GetElementDofTable(); bdrElem_dof = NURBSext->GetBdrElementDofTable(); } void FiniteElementSpace::Construct() { // This method should be used only for non-NURBS spaces. MFEM_VERIFY(!NURBSext, "internal error"); elem_dof = NULL; bdrElem_dof = NULL; ndofs = 0; nedofs = nfdofs = nbdofs = 0; bdofs = NULL; fdofs = NULL; cP = NULL; cR = NULL; cP_is_set = false; // 'Th' is initialized/destroyed before this method is called. nvdofs = mesh->GetNV() * fec->DofForGeometry(Geometry::POINT); if (mesh->Dimension() > 1) { nedofs = mesh->GetNEdges() * fec->DofForGeometry(Geometry::SEGMENT); } if (mesh->GetNFaces() > 0) { bool have_face_dofs = false; for (int g = Geometry::DimStart[2]; g < Geometry::DimStart[3]; g++) { if (mesh->HasGeometry(Geometry::Type(g)) && fec->DofForGeometry(Geometry::Type(g)) > 0) { have_face_dofs = true; break; } } if (have_face_dofs) { fdofs = new int[mesh->GetNFaces()+1]; fdofs[0] = 0; for (int i = 0; i < mesh->GetNFaces(); i++) { nfdofs += fec->DofForGeometry(mesh->GetFaceBaseGeometry(i)); fdofs[i+1] = nfdofs; } } } if (mesh->Dimension() > 0) { bdofs = new int[mesh->GetNE()+1]; bdofs[0] = 0; for (int i = 0; i < mesh->GetNE(); i++) { nbdofs += fec->DofForGeometry(mesh->GetElementBaseGeometry(i)); bdofs[i+1] = nbdofs; } } ndofs = nvdofs + nedofs + nfdofs + nbdofs; // Do not build elem_dof Table here: in parallel it has to be constructed // later. } void FiniteElementSpace::GetElementDofs(int i, Array &dofs) const { if (elem_dof) { elem_dof -> GetRow (i, dofs); } else { Array V, E, Eo, F, Fo; int k, j, nv, ne, nf, nb, nfd, nd, dim; const int *ind; dim = mesh->Dimension(); nv = fec->DofForGeometry(Geometry::POINT); ne = (dim > 1) ? ( fec->DofForGeometry(Geometry::SEGMENT) ) : ( 0 ); nb = (dim > 0) ? fec->DofForGeometry(mesh->GetElementBaseGeometry(i)) : 0; if (nv > 0) { mesh->GetElementVertices(i, V); } if (ne > 0) { mesh->GetElementEdges(i, E, Eo); } nfd = 0; if (dim == 3) { if (fec->HasFaceDofs(mesh->GetElementBaseGeometry(i))) { mesh->GetElementFaces(i, F, Fo); for (k = 0; k < F.Size(); k++) { nfd += fec->DofForGeometry(mesh->GetFaceBaseGeometry(F[k])); } } } nd = V.Size() * nv + E.Size() * ne + nfd + nb; dofs.SetSize(nd); if (nv > 0) { for (k = 0; k < V.Size(); k++) { for (j = 0; j < nv; j++) { dofs[k*nv+j] = V[k]*nv+j; } } nv *= V.Size(); } if (ne > 0) { // if (dim > 1) for (k = 0; k < E.Size(); k++) { ind = fec->DofOrderForOrientation(Geometry::SEGMENT, Eo[k]); for (j = 0; j < ne; j++) { if (ind[j] < 0) { dofs[nv+k*ne+j] = -1 - ( nvdofs+E[k]*ne+(-1-ind[j]) ); } else { dofs[nv+k*ne+j] = nvdofs+E[k]*ne+ind[j]; } } } } ne = nv + ne * E.Size(); if (nfd > 0) // if (dim == 3) { for (k = 0; k < F.Size(); k++) { ind = fec->DofOrderForOrientation(mesh->GetFaceBaseGeometry(F[k]), Fo[k]); nf = fec->DofForGeometry(mesh->GetFaceBaseGeometry(F[k])); for (j = 0; j < nf; j++) { if (ind[j] < 0) { dofs[ne+j] = -1 - ( nvdofs+nedofs+fdofs[F[k]]+(-1-ind[j]) ); } else { dofs[ne+j] = nvdofs+nedofs+fdofs[F[k]]+ind[j]; } } ne += nf; } } if (nb > 0) { k = nvdofs + nedofs + nfdofs + bdofs[i]; for (j = 0; j < nb; j++) { dofs[ne+j] = k + j; } } } } const FiniteElement *FiniteElementSpace::GetFE(int i) const { if (i < 0 || !mesh->GetNE()) { return NULL; } MFEM_VERIFY(i < mesh->GetNE(), "Invalid element id " << i << ", maximum allowed " << mesh->GetNE()-1); const FiniteElement *FE = fec->FiniteElementForGeometry(mesh->GetElementBaseGeometry(i)); if (NURBSext) { NURBSext->LoadFE(i, FE); } return FE; } void FiniteElementSpace::GetBdrElementDofs(int i, Array &dofs) const { if (bdrElem_dof) { bdrElem_dof->GetRow(i, dofs); } else { Array V, E, Eo; int k, j, nv, ne, nf, nd, iF, oF, dim; const int *ind; dim = mesh->Dimension(); nv = fec->DofForGeometry(Geometry::POINT); if (nv > 0) { mesh->GetBdrElementVertices(i, V); } ne = (dim > 1) ? ( fec->DofForGeometry(Geometry::SEGMENT) ) : ( 0 ); if (ne > 0) { mesh->GetBdrElementEdges(i, E, Eo); } nd = V.Size() * nv + E.Size() * ne; nf = (dim == 3) ? (fec->DofForGeometry( mesh->GetBdrElementBaseGeometry(i))) : (0); if (nf > 0) { nd += nf; mesh->GetBdrElementFace(i, &iF, &oF); } dofs.SetSize(nd); if (nv > 0) { for (k = 0; k < V.Size(); k++) { for (j = 0; j < nv; j++) { dofs[k*nv+j] = V[k]*nv+j; } } nv *= V.Size(); } if (ne > 0) { // if (dim > 1) for (k = 0; k < E.Size(); k++) { ind = fec->DofOrderForOrientation(Geometry::SEGMENT, Eo[k]); for (j = 0; j < ne; j++) { if (ind[j] < 0) { dofs[nv+k*ne+j] = -1 - ( nvdofs+E[k]*ne+(-1-ind[j]) ); } else { dofs[nv+k*ne+j] = nvdofs+E[k]*ne+ind[j]; } } } } if (nf > 0) // if (dim == 3) { ne = nv + ne * E.Size(); ind = fec->DofOrderForOrientation( mesh->GetBdrElementBaseGeometry(i), oF); for (j = 0; j < nf; j++) { if (ind[j] < 0) { dofs[ne+j] = -1 - ( nvdofs+nedofs+fdofs[iF]+(-1-ind[j]) ); } else { dofs[ne+j] = nvdofs+nedofs+fdofs[iF]+ind[j]; } } } } } void FiniteElementSpace::GetFaceDofs(int i, Array &dofs) const { int j, k, nv, ne, nf, nd, dim = mesh->Dimension(); Array V, E, Eo; const int *ind; // for 1D, 2D and 3D faces nv = fec->DofForGeometry(Geometry::POINT); ne = (dim > 1) ? fec->DofForGeometry(Geometry::SEGMENT) : 0; if (nv > 0) { mesh->GetFaceVertices(i, V); } if (ne > 0) { mesh->GetFaceEdges(i, E, Eo); } nf = (fdofs) ? (fdofs[i+1]-fdofs[i]) : (0); nd = V.Size() * nv + E.Size() * ne + nf; dofs.SetSize(nd); if (nv > 0) { for (k = 0; k < V.Size(); k++) { for (j = 0; j < nv; j++) { dofs[k*nv+j] = V[k]*nv+j; } } } nv *= V.Size(); if (ne > 0) { for (k = 0; k < E.Size(); k++) { ind = fec->DofOrderForOrientation(Geometry::SEGMENT, Eo[k]); for (j = 0; j < ne; j++) { if (ind[j] < 0) { dofs[nv+k*ne+j] = -1 - ( nvdofs+E[k]*ne+(-1-ind[j]) ); } else { dofs[nv+k*ne+j] = nvdofs+E[k]*ne+ind[j]; } } } } ne = nv + ne * E.Size(); if (nf > 0) { for (j = nvdofs+nedofs+fdofs[i], k = 0; k < nf; j++, k++) { dofs[ne+k] = j; } } } void FiniteElementSpace::GetEdgeDofs(int i, Array &dofs) const { int j, k, nv, ne; Array V; nv = fec->DofForGeometry(Geometry::POINT); if (nv > 0) { mesh->GetEdgeVertices(i, V); } ne = fec->DofForGeometry(Geometry::SEGMENT); dofs.SetSize(2*nv+ne); if (nv > 0) { for (k = 0; k < 2; k++) { for (j = 0; j < nv; j++) { dofs[k*nv+j] = V[k]*nv+j; } } } nv *= 2; for (j = 0, k = nvdofs+i*ne; j < ne; j++, k++) { dofs[nv+j] = k; } } void FiniteElementSpace::GetVertexDofs(int i, Array &dofs) const { int j, nv; nv = fec->DofForGeometry(Geometry::POINT); dofs.SetSize(nv); for (j = 0; j < nv; j++) { dofs[j] = i*nv+j; } } void FiniteElementSpace::GetElementInteriorDofs (int i, Array &dofs) const { int j, k, nb; if (mesh->Dimension() == 0) { dofs.SetSize(0); return; } nb = fec -> DofForGeometry (mesh -> GetElementBaseGeometry (i)); dofs.SetSize (nb); k = nvdofs + nedofs + nfdofs + bdofs[i]; for (j = 0; j < nb; j++) { dofs[j] = k + j; } } void FiniteElementSpace::GetEdgeInteriorDofs (int i, Array &dofs) const { int j, k, ne; ne = fec -> DofForGeometry (Geometry::SEGMENT); dofs.SetSize (ne); for (j = 0, k = nvdofs+i*ne; j < ne; j++, k++) { dofs[j] = k; } } void FiniteElementSpace::GetFaceInteriorDofs (int i, Array &dofs) const { int j, k, nf; nf = (fdofs) ? (fdofs[i+1]-fdofs[i]) : (0); dofs.SetSize (nf); if (nf > 0) { for (j = 0, k = nvdofs+nedofs+fdofs[i]; j < nf; j++, k++) { dofs[j] = k; } } } const FiniteElement *FiniteElementSpace::GetBE (int i) const { const FiniteElement *BE; switch ( mesh->Dimension() ) { case 1: BE = fec->FiniteElementForGeometry(Geometry::POINT); break; case 2: BE = fec->FiniteElementForGeometry(Geometry::SEGMENT); break; case 3: default: BE = fec->FiniteElementForGeometry( mesh->GetBdrElementBaseGeometry(i)); } if (NURBSext) { NURBSext->LoadBE(i, BE); } return BE; } const FiniteElement *FiniteElementSpace::GetFaceElement(int i) const { const FiniteElement *fe; switch (mesh->Dimension()) { case 1: fe = fec->FiniteElementForGeometry(Geometry::POINT); break; case 2: fe = fec->FiniteElementForGeometry(Geometry::SEGMENT); break; case 3: default: fe = fec->FiniteElementForGeometry(mesh->GetFaceBaseGeometry(i)); } // if (NURBSext) // NURBSext->LoadFaceElement(i, fe); return fe; } const FiniteElement *FiniteElementSpace::GetEdgeElement(int i) const { MFEM_ASSERT(mesh->Dimension() > 1, "No edges with a mesh dimension < 2"); return fec->FiniteElementForGeometry(Geometry::SEGMENT); } const FiniteElement *FiniteElementSpace::GetTraceElement( int i, Geometry::Type geom_type) const { return fec->TraceFiniteElementForGeometry(geom_type); } FiniteElementSpace::~FiniteElementSpace() { Destroy(); } void FiniteElementSpace::Destroy() { delete cR; delete cP; Th.Clear(); L2E_nat.Clear(); L2E_lex.Clear(); for (int i = 0; i < E2Q_array.Size(); i++) { delete E2Q_array[i]; } E2Q_array.SetSize(0); for (auto &x : L2F) { delete x.second; } for (int i = 0; i < E2IFQ_array.Size(); i++) { delete E2IFQ_array[i]; } E2IFQ_array.SetSize(0); for (int i = 0; i < E2BFQ_array.Size(); i++) { delete E2BFQ_array[i]; } E2BFQ_array.SetSize(0); dof_elem_array.DeleteAll(); dof_ldof_array.DeleteAll(); if (NURBSext) { if (own_ext) { delete NURBSext; } } else { delete elem_dof; delete bdrElem_dof; delete [] bdofs; delete [] fdofs; } } void FiniteElementSpace::GetTransferOperator( const FiniteElementSpace &coarse_fes, OperatorHandle &T) const { // Assumptions: see the declaration of the method. if (T.Type() == Operator::MFEM_SPARSEMAT) { Mesh::GeometryList elem_geoms(*mesh); DenseTensor localP[Geometry::NumGeom]; for (int i = 0; i < elem_geoms.Size(); i++) { GetLocalRefinementMatrices(coarse_fes, elem_geoms[i], localP[elem_geoms[i]]); } T.Reset(RefinementMatrix_main(coarse_fes.GetNDofs(), coarse_fes.GetElementToDofTable(), localP)); } else { T.Reset(new RefinementOperator(this, &coarse_fes)); } } void FiniteElementSpace::GetTrueTransferOperator( const FiniteElementSpace &coarse_fes, OperatorHandle &T) const { const SparseMatrix *coarse_P = coarse_fes.GetConformingProlongation(); Operator::Type req_type = T.Type(); GetTransferOperator(coarse_fes, T); if (req_type == Operator::MFEM_SPARSEMAT) { if (GetConformingRestriction()) { T.Reset(mfem::Mult(*cR, *T.As())); } if (coarse_P) { T.Reset(mfem::Mult(*T.As(), *coarse_P)); } } else { const int RP_case = bool(GetConformingRestriction()) + 2*bool(coarse_P); if (RP_case == 0) { return; } const bool owner = T.OwnsOperator(); T.SetOperatorOwner(false); switch (RP_case) { case 1: T.Reset(new ProductOperator(cR, T.Ptr(), false, owner)); break; case 2: T.Reset(new ProductOperator(T.Ptr(), coarse_P, owner, false)); break; case 3: T.Reset(new TripleProductOperator( cR, T.Ptr(), coarse_P, false, owner, false)); break; } } } void FiniteElementSpace::Update(bool want_transform) { if (mesh->GetSequence() == sequence) { return; // mesh and space are in sync, no-op } if (want_transform && mesh->GetSequence() != sequence + 1) { MFEM_ABORT("Error in update sequence. Space needs to be updated after " "each mesh modification."); } sequence = mesh->GetSequence(); if (NURBSext) { UpdateNURBS(); return; } Table* old_elem_dof = NULL; int old_ndofs; // save old DOF table if (want_transform) { old_elem_dof = elem_dof; elem_dof = NULL; old_ndofs = ndofs; } Destroy(); // calls Th.Clear() Construct(); BuildElementToDofTable(); if (want_transform) { // calculate appropriate GridFunction transformation switch (mesh->GetLastOperation()) { case Mesh::REFINE: { if (Th.Type() != Operator::MFEM_SPARSEMAT) { Th.Reset(new RefinementOperator(this, old_elem_dof, old_ndofs)); // The RefinementOperator takes ownership of 'old_elem_dof', so // we no longer own it: old_elem_dof = NULL; } else { // calculate fully assembled matrix Th.Reset(RefinementMatrix(old_ndofs, old_elem_dof)); } break; } case Mesh::DEREFINE: { BuildConformingInterpolation(); Th.Reset(DerefinementMatrix(old_ndofs, old_elem_dof)); if (cP && cR) { Th.SetOperatorOwner(false); Th.Reset(new TripleProductOperator(cP, cR, Th.Ptr(), false, false, true)); } break; } default: break; } delete old_elem_dof; } } void FiniteElementSpace::Save(std::ostream &out) const { int fes_format = 90; // the original format, v0.9 bool nurbs_unit_weights = false; // Determine the format that should be used. if (!NURBSext) { // TODO: if this is a variable-order FE space, use fes_format = 100. } else { const NURBSFECollection *nurbs_fec = dynamic_cast(fec); MFEM_VERIFY(nurbs_fec, "invalid FE collection"); nurbs_fec->SetOrder(NURBSext->GetOrder()); const double eps = 5e-14; nurbs_unit_weights = (NURBSext->GetWeights().Min() >= 1.0-eps && NURBSext->GetWeights().Max() <= 1.0+eps); if ((NURBSext->GetOrder() == NURBSFECollection::VariableOrder) || (NURBSext != mesh->NURBSext && !nurbs_unit_weights) || (NURBSext->GetMaster().Size() != 0 )) { fes_format = 100; // v1.0 format } } out << (fes_format == 90 ? "FiniteElementSpace\n" : "MFEM FiniteElementSpace v1.0\n") << "FiniteElementCollection: " << fec->Name() << '\n' << "VDim: " << vdim << '\n' << "Ordering: " << ordering << '\n'; if (fes_format == 100) // v1.0 { if (!NURBSext) { // TODO: this is a variable-order FE space --> write 'element_orders'. } else if (NURBSext != mesh->NURBSext) { if (NURBSext->GetOrder() != NURBSFECollection::VariableOrder) { out << "NURBS_order\n" << NURBSext->GetOrder() << '\n'; } else { out << "NURBS_orders\n"; // 1 = do not write the size, just the entries: NURBSext->GetOrders().Save(out, 1); } // If periodic BCs are given, write connectivity if (NURBSext->GetMaster().Size() != 0 ) { out <<"NURBS_periodic\n"; NURBSext->GetMaster().Save(out); NURBSext->GetSlave().Save(out); } // If the weights are not unit, write them to the output: if (!nurbs_unit_weights) { out << "NURBS_weights\n"; NURBSext->GetWeights().Print(out, 1); } } out << "End: MFEM FiniteElementSpace v1.0\n"; } } FiniteElementCollection *FiniteElementSpace::Load(Mesh *m, std::istream &input) { string buff; int fes_format = 0, ord; FiniteElementCollection *r_fec; Destroy(); input >> std::ws; getline(input, buff); // 'FiniteElementSpace' filter_dos(buff); if (buff == "FiniteElementSpace") { fes_format = 90; /* v0.9 */ } else if (buff == "MFEM FiniteElementSpace v1.0") { fes_format = 100; } else { MFEM_ABORT("input stream is not a FiniteElementSpace!"); } getline(input, buff, ' '); // 'FiniteElementCollection:' input >> std::ws; getline(input, buff); filter_dos(buff); r_fec = FiniteElementCollection::New(buff.c_str()); getline(input, buff, ' '); // 'VDim:' input >> vdim; getline(input, buff, ' '); // 'Ordering:' input >> ord; NURBSFECollection *nurbs_fec = dynamic_cast(r_fec); NURBSExtension *NURBSext = NULL; if (fes_format == 90) // original format, v0.9 { if (nurbs_fec) { MFEM_VERIFY(m->NURBSext, "NURBS FE collection requires a NURBS mesh!"); const int order = nurbs_fec->GetOrder(); if (order != m->NURBSext->GetOrder() && order != NURBSFECollection::VariableOrder) { NURBSext = new NURBSExtension(m->NURBSext, order); } } } else if (fes_format == 100) // v1.0 { while (1) { skip_comment_lines(input, '#'); MFEM_VERIFY(input.good(), "error reading FiniteElementSpace v1.0"); getline(input, buff); filter_dos(buff); if (buff == "NURBS_order" || buff == "NURBS_orders") { MFEM_VERIFY(nurbs_fec, buff << ": NURBS FE collection is required!"); MFEM_VERIFY(m->NURBSext, buff << ": NURBS mesh is required!"); MFEM_VERIFY(!NURBSext, buff << ": order redefinition!"); if (buff == "NURBS_order") { int order; input >> order; NURBSext = new NURBSExtension(m->NURBSext, order); } else { Array orders; orders.Load(m->NURBSext->GetNKV(), input); NURBSext = new NURBSExtension(m->NURBSext, orders); } } else if (buff == "NURBS_periodic") { Array master, slave; master.Load(input); slave.Load(input); NURBSext->ConnectBoundaries(master,slave); } else if (buff == "NURBS_weights") { MFEM_VERIFY(NURBSext, "NURBS_weights: NURBS_orders have to be " "specified before NURBS_weights!"); NURBSext->GetWeights().Load(input, NURBSext->GetNDof()); } else if (buff == "element_orders") { MFEM_VERIFY(!nurbs_fec, "section element_orders cannot be used " "with a NURBS FE collection"); MFEM_ABORT("element_orders: not implemented yet!"); } else if (buff == "End: MFEM FiniteElementSpace v1.0") { break; } else { MFEM_ABORT("unknown section: " << buff); } } } Constructor(m, NURBSext, r_fec, vdim, ord); return r_fec; } void QuadratureSpace::Construct() { // protected method int offset = 0; const int num_elem = mesh->GetNE(); element_offsets = new int[num_elem + 1]; for (int g = 0; g < Geometry::NumGeom; g++) { int_rule[g] = NULL; } for (int i = 0; i < num_elem; i++) { element_offsets[i] = offset; int geom = mesh->GetElementBaseGeometry(i); if (int_rule[geom] == NULL) { int_rule[geom] = &IntRules.Get(geom, order); } offset += int_rule[geom]->GetNPoints(); } element_offsets[num_elem] = size = offset; } QuadratureSpace::QuadratureSpace(Mesh *mesh_, std::istream &in) : mesh(mesh_) { const char *msg = "invalid input stream"; string ident; in >> ident; MFEM_VERIFY(ident == "QuadratureSpace", msg); in >> ident; MFEM_VERIFY(ident == "Type:", msg); in >> ident; if (ident == "default_quadrature") { in >> ident; MFEM_VERIFY(ident == "Order:", msg); in >> order; } else { MFEM_ABORT("unknown QuadratureSpace type: " << ident); return; } Construct(); } void QuadratureSpace::Save(std::ostream &out) const { out << "QuadratureSpace\n" << "Type: default_quadrature\n" << "Order: " << order << '\n'; } 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() { #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 const SparseMatrix *out_R = fes_out.GetRestrictionMatrix(); if (oper_type == Operator::Hypre_ParCSR) { 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) { 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(); } // Costruct 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(), 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 && ran_fes.GetNE() > 0) { const FiniteElement *f_fe_0 = ran_fes.GetFE(0); 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_) : Operator(fes_lor_.GetVSize(), fes_ho_.GetVSize()), fes_ho(fes_ho_), fes_lor(fes_lor_) { Mesh *mesh_ho = fes_ho.GetMesh(); MFEM_VERIFY(mesh_ho->GetNumGeometries(mesh_ho->Dimension()) <= 1, "mixed meshes are not supported"); // If the local mesh is empty, skip all computations if (mesh_ho->GetNE() == 0) { return; } const FiniteElement *fe_lor = fes_lor.GetFE(0); const FiniteElement *fe_ho = fes_ho.GetFE(0); ndof_lor = fe_lor->GetDof(); ndof_ho = fe_ho->GetDof(); const int nel_lor = fes_lor.GetNE(); const int nel_ho = fes_ho.GetNE(); nref = nel_lor/nel_ho; // Construct the mapping from HO to LOR // ho2lor.GetRow(iho) will give all the LOR elements contained in iho ho2lor.SetSize(nel_ho, nref); const CoarseFineTransformations &cf_tr = fes_lor.GetMesh()->GetRefinementTransforms(); for (int ilor=0; ilorGetGeomType(); const DenseTensor &pmats = cf_tr.point_matrices[geom]; emb_tr.SetIdentityTransformation(geom); for (int iho=0; ihoGetOrder() + fe_ho->GetOrder() + el_tr->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); fe_lor->CalcShape(ip_lor, shape_lor); fe_ho->CalcShape(ip_ho, shape_ho); el_tr->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 double w = el_tr->Weight()*ip_lor.weight; shape_lor *= w; AddMultVWt(shape_lor, shape_ho, M_mixed_el); } M_mixed.CopyMN(M_mixed_el, iref*ndof_lor, 0); } mfem::Mult(Minv_lor, M_mixed, R(iho)); mfem::MultAtB(R(iho), M_lor, RtMlor); mfem::Mult(RtMlor, R(iho), RtMlorR); RtMlorR_inv.Factor(); RtMlorR_inv.Mult(RtMlor, P(iho)); } } void L2ProjectionGridTransfer::L2Projection::Mult( const Vector &x, Vector &y) const { int vdim = fes_ho.GetVDim(); Array vdofs; DenseMatrix xel_mat(ndof_ho, vdim); DenseMatrix yel_mat(ndof_lor*nref, vdim); for (int iho=0; iho vdofs; DenseMatrix xel_mat(ndof_lor*nref, vdim); DenseMatrix yel_mat(ndof_ho, vdim); for (int iho=0; iho