628 lines
18 KiB
C++
628 lines
18 KiB
C++
// 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.
|
|
|
|
#include "../mesh/mesh_headers.hpp"
|
|
#include "fem.hpp"
|
|
#include <cmath>
|
|
|
|
namespace mfem
|
|
{
|
|
|
|
ElementTransformation::ElementTransformation()
|
|
: IntPoint(static_cast<IntegrationPoint *>(NULL)),
|
|
EvalState(0),
|
|
geom(Geometry::INVALID),
|
|
Attribute(-1),
|
|
ElementNo(-1)
|
|
{ }
|
|
|
|
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::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<double>::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<double>::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 &out = mfem::out;
|
|
|
|
// separator:
|
|
switch (mode%3)
|
|
{
|
|
case 0: out << ", "; break;
|
|
case 1: out << "Newton: "; break;
|
|
case 2: out << " "; break;
|
|
// "Newton: iter = xx, "
|
|
}
|
|
switch ((mode/3)%4)
|
|
{
|
|
case 0: out << "iter = " << std::setw(2) << int(val); break;
|
|
case 1: out << "delta_ref = " << std::setw(11) << val; break;
|
|
case 2: out << " err_phys = " << std::setw(11) << val; break;
|
|
case 3: break;
|
|
}
|
|
// ending:
|
|
switch ((mode/12)%4)
|
|
{
|
|
case 0: break;
|
|
case 1: out << '\n'; break;
|
|
case 2: out << " (converged)\n"; break;
|
|
case 3: out << " (actual)\n"; break;
|
|
}
|
|
}
|
|
|
|
void InverseElementTransformation::NewtonPrintPoint(const char *prefix,
|
|
const Vector &pt,
|
|
const char *suffix)
|
|
{
|
|
std::ostream &out = mfem::out;
|
|
|
|
out << prefix << " = (";
|
|
for (int j = 0; j < pt.Size(); j++)
|
|
{
|
|
out << (j > 0 ? ", " : "") << pt(j);
|
|
}
|
|
out << ')' << 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;
|
|
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 *ip)
|
|
{
|
|
IsoparametricTransformation::SetIntPoint(ip);
|
|
|
|
if (Elem1)
|
|
{
|
|
Loc1.Transform(*ip, eip1);
|
|
Elem1->SetIntPoint(&eip1);
|
|
}
|
|
if (Elem2)
|
|
{
|
|
Loc2.Transform(*ip, eip2);
|
|
Elem2->SetIntPoint(&eip2);
|
|
}
|
|
}
|
|
|
|
ElementTransformation &
|
|
FaceElementTransformations::GetElement1Transformation()
|
|
{
|
|
MFEM_VERIFY(mask & 1 && Elem1 != NULL, "The ElementTransformation "
|
|
"for the element has not been configured for side 1.");
|
|
return *Elem1;
|
|
}
|
|
|
|
ElementTransformation &
|
|
FaceElementTransformations::GetElement2Transformation()
|
|
{
|
|
MFEM_VERIFY(mask & 2 && Elem2 != NULL, "The ElementTransformation "
|
|
"for the element has not been configured for side 2.");
|
|
return *Elem2;
|
|
}
|
|
|
|
IntegrationPointTransformation &
|
|
FaceElementTransformations::GetIntPoint1Transformation()
|
|
{
|
|
MFEM_VERIFY(mask & 4, "The IntegrationPointTransformation "
|
|
"for the element has not been configured for side 1.");
|
|
return Loc1;
|
|
}
|
|
|
|
IntegrationPointTransformation &
|
|
FaceElementTransformations::GetIntPoint2Transformation()
|
|
{
|
|
MFEM_VERIFY(mask & 8, "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 & 16, "The ElementTransformation "
|
|
"for the face has not been configured.");
|
|
IsoparametricTransformation::Transform(ip, trans);
|
|
}
|
|
|
|
void FaceElementTransformations::Transform(const IntegrationRule &ir,
|
|
DenseMatrix &tr)
|
|
{
|
|
MFEM_VERIFY(mask & 16, "The ElementTransformation "
|
|
"for the face has not been configured.");
|
|
IsoparametricTransformation::Transform(ir, tr);
|
|
}
|
|
|
|
void FaceElementTransformations::Transform(const DenseMatrix &matrix,
|
|
DenseMatrix &result)
|
|
{
|
|
MFEM_VERIFY(mask & 16, "The ElementTransformation "
|
|
"for the face has not been configured.");
|
|
IsoparametricTransformation::Transform(matrix, result);
|
|
}
|
|
|
|
}
|