// Copyright (c) 2010-2023, 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 "../mesh/mesh_headers.hpp" #include "fem.hpp" #include namespace mfem { ElementTransformation::ElementTransformation() : IntPoint(static_cast(NULL)), EvalState(0), geom(Geometry::INVALID), Attribute(-1), ElementNo(-1), mesh(nullptr) { } double ElementTransformation::EvalWeight() { MFEM_ASSERT((EvalState & WEIGHT_MASK) == 0, ""); Jacobian(); EvalState |= WEIGHT_MASK; return (Wght = (dFdx.Width() == 0) ? 1.0 : dFdx.Weight()); } const DenseMatrix &ElementTransformation::EvalAdjugateJ() { MFEM_ASSERT((EvalState & ADJUGATE_MASK) == 0, ""); Jacobian(); adjJ.SetSize(dFdx.Width(), dFdx.Height()); if (dFdx.Width() > 0) { CalcAdjugate(dFdx, adjJ); } EvalState |= ADJUGATE_MASK; return adjJ; } const DenseMatrix &ElementTransformation::EvalTransAdjugateJ() { MFEM_ASSERT((EvalState & TRANS_ADJUGATE_MASK) == 0, ""); Jacobian(); adjJT.SetSize(dFdx.Height(), dFdx.Width()); if (dFdx.Width() == dFdx.Height()) { CalcAdjugateTranspose(dFdx, adjJT); } else { AdjugateJacobian(); adjJT.Transpose(adjJ); } EvalState |= TRANS_ADJUGATE_MASK; return adjJT; } const DenseMatrix &ElementTransformation::EvalInverseJ() { // TODO: compute as invJ = / adjJ/Weight, if J is square, // \ adjJ/Weight^2, otherwise. MFEM_ASSERT((EvalState & INVERSE_MASK) == 0, ""); Jacobian(); invJ.SetSize(dFdx.Width(), dFdx.Height()); if (dFdx.Width() > 0) { CalcInverse(dFdx, invJ); } EvalState |= INVERSE_MASK; return invJ; } int InverseElementTransformation::FindClosestPhysPoint( const Vector& pt, const IntegrationRule &ir) { MFEM_VERIFY(T != NULL, "invalid ElementTransformation"); MFEM_VERIFY(pt.Size() == T->GetSpaceDim(), "invalid point"); DenseMatrix physPts; T->Transform(ir, physPts); // Initialize distance and index of closest point int minIndex = -1; double minDist = std::numeric_limits::max(); // Check all integration points in ir const int npts = ir.GetNPoints(); for (int i = 0; i < npts; ++i) { double dist = pt.DistanceTo(physPts.GetColumn(i)); if (dist < minDist) { minDist = dist; minIndex = i; } } return minIndex; } int InverseElementTransformation::FindClosestRefPoint( const Vector& pt, const IntegrationRule &ir) { MFEM_VERIFY(T != NULL, "invalid ElementTransformation"); MFEM_VERIFY(pt.Size() == T->GetSpaceDim(), "invalid point"); // Initialize distance and index of closest point int minIndex = -1; double minDist = std::numeric_limits::max(); // Check all integration points in ir using the local metric at each point // induced by the transformation. Vector dp(T->GetSpaceDim()), dr(T->GetDimension()); const int npts = ir.GetNPoints(); for (int i = 0; i < npts; ++i) { const IntegrationPoint &ip = ir.IntPoint(i); T->Transform(ip, dp); dp -= pt; T->SetIntPoint(&ip); T->InverseJacobian().Mult(dp, dr); double dist = dr.Norml2(); // double dist = dr.Normlinf(); if (dist < minDist) { minDist = dist; minIndex = i; } } return minIndex; } void InverseElementTransformation::NewtonPrint(int mode, double val) { std::ostream &os = mfem::out; // separator: switch (mode%3) { case 0: os << ", "; break; case 1: os << "Newton: "; break; case 2: os << " "; break; // "Newton: iter = xx, " } switch ((mode/3)%4) { case 0: os << "iter = " << std::setw(2) << int(val); break; case 1: os << "delta_ref = " << std::setw(11) << val; break; case 2: os << " err_phys = " << std::setw(11) << val; break; case 3: break; } // ending: switch ((mode/12)%4) { case 0: break; case 1: os << '\n'; break; case 2: os << " (converged)\n"; break; case 3: os << " (actual)\n"; break; } } void InverseElementTransformation::NewtonPrintPoint(const char *prefix, const Vector &pt, const char *suffix) { std::ostream &os = mfem::out; os << prefix << " = ("; for (int j = 0; j < pt.Size(); j++) { os << (j > 0 ? ", " : "") << pt(j); } os << ')' << suffix; } int InverseElementTransformation::NewtonSolve(const Vector &pt, IntegrationPoint &ip) { MFEM_ASSERT(pt.Size() == T->GetSpaceDim(), "invalid point"); const double phys_tol = phys_rtol*pt.Normlinf(); const int geom = T->GetGeometryType(); const int dim = T->GetDimension(); const int sdim = T->GetSpaceDim(); IntegrationPoint xip, prev_xip; double xd[3], yd[3], dxd[3], dx_norm = -1.0, err_phys, real_dx_norm = -1.0; Vector x(xd, dim), y(yd, sdim), dx(dxd, dim); bool hit_bdr = false, prev_hit_bdr = false; // Use ip0 as initial guess: xip = *ip0; xip.Get(xd, dim); // xip -> x if (print_level >= 3) { NewtonPrint(1, 0.); // iter 0 NewtonPrintPoint(", ref_pt", x, "\n"); } for (int it = 0; true; ) { // Remarks: // If f(x) := 1/2 |pt-F(x)|^2, then grad(f)(x) = -J^t(x) [pt-F(x)]. // Linearize F(y) at y=x: F(y) ~ L[x](y) := F(x) + J(x) [y-x]. // Newton iteration for F(y)=b is given by L[x_old](x_new) = b, i.e. // F(x_old) + J(x_old) [x_new-x_old] = b. // // To minimize: 1/2 |F(y)-b|^2, subject to: l(y) >= 0, we may consider the // iteration: minimize: |L[x_old](x_new)-b|^2, subject to l(x_new) >= 0, // i.e. minimize: |F(x_old) + J(x_old) [x_new-x_old] - b|^2. // This method uses: // Newton iteration: x := x + J(x)^{-1} [pt-F(x)] // or when dim != sdim: x := x + [J^t.J]^{-1}.J^t [pt-F(x)] // Compute the physical coordinates of the current point: T->Transform(xip, y); if (print_level >= 3) { NewtonPrint(11, 0.); // continuation line NewtonPrintPoint("approx_pt", y, ", "); NewtonPrintPoint("exact_pt", pt, "\n"); } subtract(pt, y, y); // y = pt-y // Check for convergence in physical coordinates: err_phys = y.Normlinf(); if (err_phys < phys_tol) { if (print_level >= 1) { NewtonPrint(1, (double)it); NewtonPrint(3, dx_norm); NewtonPrint(30, err_phys); } ip = xip; if (solver_type != Newton) { return Inside; } return Geometry::CheckPoint(geom, ip, ip_tol) ? Inside : Outside; } if (print_level >= 1) { if (it == 0 || print_level >= 2) { NewtonPrint(1, (double)it); NewtonPrint(3, dx_norm); NewtonPrint(18, err_phys); } } if (hit_bdr) { xip.Get(xd, dim); // xip -> x if (prev_hit_bdr || it == max_iter || print_level >= 2) { prev_xip.Get(dxd, dim); // prev_xip -> dx subtract(x, dx, dx); // dx = xip - prev_xip real_dx_norm = dx.Normlinf(); if (print_level >= 2) { NewtonPrint(41, real_dx_norm); } if (prev_hit_bdr && real_dx_norm < ref_tol) { if (print_level >= 0) { if (print_level <= 1) { NewtonPrint(1, (double)it); NewtonPrint(3, dx_norm); NewtonPrint(18, err_phys); NewtonPrint(41, real_dx_norm); } mfem::out << "Newton: *** stuck on boundary!\n"; } return Outside; } } } if (it == max_iter) { break; } // Perform a Newton step: T->SetIntPoint(&xip); T->InverseJacobian().Mult(y, dx); x += dx; it++; if (solver_type != Newton) { prev_xip = xip; prev_hit_bdr = hit_bdr; } xip.Set(xd, dim); // x -> xip // Perform projection based on solver_type: switch (solver_type) { case Newton: break; case NewtonSegmentProject: hit_bdr = !Geometry::ProjectPoint(geom, prev_xip, xip); break; case NewtonElementProject: hit_bdr = !Geometry::ProjectPoint(geom, xip); break; default: MFEM_ABORT("invalid solver type"); } if (print_level >= 3) { NewtonPrint(1, double(it)); xip.Get(xd, dim); // xip -> x NewtonPrintPoint(", ref_pt", x, "\n"); } // Check for convergence in reference coordinates: dx_norm = dx.Normlinf(); if (dx_norm < ref_tol) { if (print_level >= 1) { NewtonPrint(1, (double)it); NewtonPrint(27, dx_norm); } ip = xip; if (solver_type != Newton) { return Inside; } return Geometry::CheckPoint(geom, ip, ip_tol) ? Inside : Outside; } } if (print_level >= 0) { if (print_level <= 1) { NewtonPrint(1, (double)max_iter); NewtonPrint(3, dx_norm); NewtonPrint(18, err_phys); if (hit_bdr) { NewtonPrint(41, real_dx_norm); } } mfem::out << "Newton: *** iteration did not converge!\n"; } ip = xip; return Unknown; } int InverseElementTransformation::Transform(const Vector &pt, IntegrationPoint &ip) { MFEM_VERIFY(T != NULL, "invalid ElementTransformation"); // Select initial guess ... switch (init_guess_type) { case Center: ip0 = &Geometries.GetCenter(T->GetGeometryType()); break; case ClosestPhysNode: case ClosestRefNode: { const int order = std::max(T->Order()+rel_qpts_order, 0); if (order == 0) { ip0 = &Geometries.GetCenter(T->GetGeometryType()); } else { const int old_type = GlobGeometryRefiner.GetType(); GlobGeometryRefiner.SetType(qpts_type); RefinedGeometry &RefG = *GlobGeometryRefiner.Refine(T->GetGeometryType(), order); int closest_idx = (init_guess_type == ClosestPhysNode) ? FindClosestPhysPoint(pt, RefG.RefPts) : FindClosestRefPoint(pt, RefG.RefPts); ip0 = &RefG.RefPts.IntPoint(closest_idx); GlobGeometryRefiner.SetType(old_type); } break; } case GivenPoint: break; default: MFEM_ABORT("invalid initial guess type"); } // Call the solver ... return NewtonSolve(pt, ip); } void IsoparametricTransformation::SetIdentityTransformation( Geometry::Type GeomType) { switch (GeomType) { case Geometry::POINT : FElem = &PointFE; break; case Geometry::SEGMENT : FElem = &SegmentFE; break; case Geometry::TRIANGLE : FElem = &TriangleFE; break; case Geometry::SQUARE : FElem = &QuadrilateralFE; break; case Geometry::TETRAHEDRON : FElem = &TetrahedronFE; break; case Geometry::CUBE : FElem = &HexahedronFE; break; case Geometry::PRISM : FElem = &WedgeFE; break; case Geometry::PYRAMID : FElem = &PyramidFE; break; default: MFEM_ABORT("unknown Geometry::Type!"); } int dim = FElem->GetDim(); int dof = FElem->GetDof(); const IntegrationRule &nodes = FElem->GetNodes(); PointMat.SetSize(dim, dof); for (int j = 0; j < dof; j++) { nodes.IntPoint(j).Get(&PointMat(0,j), dim); } geom = GeomType; } const DenseMatrix &IsoparametricTransformation::EvalJacobian() { MFEM_ASSERT((EvalState & JACOBIAN_MASK) == 0, ""); dshape.SetSize(FElem->GetDof(), FElem->GetDim()); dFdx.SetSize(PointMat.Height(), dshape.Width()); if (dshape.Width() > 0) { FElem->CalcDShape(*IntPoint, dshape); Mult(PointMat, dshape, dFdx); } EvalState |= JACOBIAN_MASK; return dFdx; } const DenseMatrix &IsoparametricTransformation::EvalHessian() { MFEM_ASSERT((EvalState & HESSIAN_MASK) == 0, ""); int Dim = FElem->GetDim(); d2shape.SetSize(FElem->GetDof(), (Dim*(Dim+1))/2); d2Fdx2.SetSize(PointMat.Height(), d2shape.Width()); if (d2shape.Width() > 0) { FElem->CalcHessian(*IntPoint, d2shape); Mult(PointMat, d2shape, d2Fdx2); } EvalState |= HESSIAN_MASK; return d2Fdx2; } int IsoparametricTransformation::OrderJ() const { switch (FElem->Space()) { case FunctionSpace::Pk: return (FElem->GetOrder()-1); case FunctionSpace::Qk: return (FElem->GetOrder()); default: MFEM_ABORT("unsupported finite element"); } return 0; } int IsoparametricTransformation::OrderW() const { switch (FElem->Space()) { case FunctionSpace::Pk: return (FElem->GetOrder() - 1) * FElem->GetDim(); case FunctionSpace::Qk: return (FElem->GetOrder() * FElem->GetDim() - 1); default: MFEM_ABORT("unsupported finite element"); } return 0; } int IsoparametricTransformation::OrderGrad(const FiniteElement *fe) const { if (FElem->Space() == fe->Space()) { int k = FElem->GetOrder(); int d = FElem->GetDim(); int l = fe->GetOrder(); switch (fe->Space()) { case FunctionSpace::Pk: return ((k-1)*(d-1)+(l-1)); case FunctionSpace::Qk: return (k*(d-1)+(l-1)); default: MFEM_ABORT("unsupported finite element"); } } MFEM_ABORT("incompatible finite elements"); return 0; } void IsoparametricTransformation::Transform (const IntegrationPoint &ip, Vector &trans) { shape.SetSize(FElem->GetDof()); trans.SetSize(PointMat.Height()); FElem -> CalcShape(ip, shape); PointMat.Mult(shape, trans); } void IsoparametricTransformation::Transform (const IntegrationRule &ir, DenseMatrix &tr) { int dof, n, dim, i, j, k; dim = PointMat.Height(); dof = FElem->GetDof(); n = ir.GetNPoints(); shape.SetSize(dof); tr.SetSize(dim, n); for (j = 0; j < n; j++) { FElem -> CalcShape (ir.IntPoint(j), shape); for (i = 0; i < dim; i++) { tr(i, j) = 0.0; for (k = 0; k < dof; k++) { tr(i, j) += PointMat(i, k) * shape(k); } } } } void IsoparametricTransformation::Transform (const DenseMatrix &matrix, DenseMatrix &result) { MFEM_ASSERT(matrix.Height() == GetDimension(), "invalid input"); result.SetSize(PointMat.Height(), matrix.Width()); IntegrationPoint ip; Vector col; for (int j = 0; j < matrix.Width(); j++) { ip.Set(matrix.GetColumn(j), matrix.Height()); result.GetColumnReference(j, col); Transform(ip, col); } } void IntegrationPointTransformation::Transform (const IntegrationPoint &ip1, IntegrationPoint &ip2) { double vec[3]; Vector v (vec, Transf.GetPointMat().Height()); Transf.Transform (ip1, v); ip2.Set(vec, v.Size()); } void IntegrationPointTransformation::Transform (const IntegrationRule &ir1, IntegrationRule &ir2) { int i, n; n = ir1.GetNPoints(); for (i = 0; i < n; i++) { Transform (ir1.IntPoint(i), ir2.IntPoint(i)); } } void FaceElementTransformations::SetIntPoint(const IntegrationPoint *face_ip) { IsoparametricTransformation::SetIntPoint(face_ip); if (mask & 4) { Loc1.Transform(*face_ip, eip1); if (Elem1) { Elem1->SetIntPoint(&eip1); } } if (mask & 8) { Loc2.Transform(*face_ip, eip2); if (Elem2) { Elem2->SetIntPoint(&eip2); } } } ElementTransformation & FaceElementTransformations::GetElement1Transformation() { MFEM_VERIFY(mask & HAVE_ELEM1 && Elem1 != NULL, "The ElementTransformation " "for the element has not been configured for side 1."); return *Elem1; } ElementTransformation & FaceElementTransformations::GetElement2Transformation() { MFEM_VERIFY(mask & HAVE_ELEM2 && Elem2 != NULL, "The ElementTransformation " "for the element has not been configured for side 2."); return *Elem2; } IntegrationPointTransformation & FaceElementTransformations::GetIntPoint1Transformation() { MFEM_VERIFY(mask & HAVE_LOC1, "The IntegrationPointTransformation " "for the element has not been configured for side 1."); return Loc1; } IntegrationPointTransformation & FaceElementTransformations::GetIntPoint2Transformation() { MFEM_VERIFY(mask & HAVE_LOC2, "The IntegrationPointTransformation " "for the element has not been configured for side 2."); return Loc2; } void FaceElementTransformations::Transform(const IntegrationPoint &ip, Vector &trans) { MFEM_VERIFY(mask & HAVE_FACE, "The ElementTransformation " "for the face has not been configured."); IsoparametricTransformation::Transform(ip, trans); } void FaceElementTransformations::Transform(const IntegrationRule &ir, DenseMatrix &tr) { MFEM_VERIFY(mask & HAVE_FACE, "The ElementTransformation " "for the face has not been configured."); IsoparametricTransformation::Transform(ir, tr); } void FaceElementTransformations::Transform(const DenseMatrix &matrix, DenseMatrix &result) { MFEM_VERIFY(mask & HAVE_FACE, "The ElementTransformation " "for the face has not been configured."); IsoparametricTransformation::Transform(matrix, result); } double FaceElementTransformations::CheckConsistency(int print_level, std::ostream &os) { // Check that the face vertices are mapped to the same physical location // when using the following three transformations: // - the face transformation, *this // - Loc1 + Elem1 // - Loc2 + Elem2, if present. const bool have_face = (mask & 16); const bool have_el1 = (mask & 1) && (mask & 4); const bool have_el2 = (mask & 2) && (mask & 8) && (Elem2No >= 0); if (int(have_face) + int(have_el1) + int(have_el2) < 2) { // need at least two different transformations to perform a check return 0.0; } const IntegrationRule &v_ir = *Geometries.GetVertices(GetGeometryType()); double max_dist = 0.0; Vector dist(v_ir.GetNPoints()); DenseMatrix coords_base, coords_el; IntegrationRule v_eir(v_ir.GetNPoints()); if (have_face) { Transform(v_ir, coords_base); if (print_level > 0) { os << "\nface vertex coordinates (from face transform):\n" << "----------------------------------------------\n"; coords_base.PrintT(os, coords_base.Height()); } } if (have_el1) { Loc1.Transform(v_ir, v_eir); Elem1->Transform(v_eir, coords_el); if (print_level > 0) { os << "\nface vertex coordinates (from element 1 transform):\n" << "---------------------------------------------------\n"; coords_el.PrintT(os, coords_el.Height()); } if (have_face) { coords_el -= coords_base; coords_el.Norm2(dist); max_dist = std::max(max_dist, dist.Normlinf()); } else { coords_base = coords_el; } } if (have_el2) { Loc2.Transform(v_ir, v_eir); Elem2->Transform(v_eir, coords_el); if (print_level > 0) { os << "\nface vertex coordinates (from element 2 transform):\n" << "---------------------------------------------------\n"; coords_el.PrintT(os, coords_el.Height()); } coords_el -= coords_base; coords_el.Norm2(dist); max_dist = std::max(max_dist, dist.Normlinf()); } return max_dist; } }