Audit physical compensation artifacts and verify quartic affine displacement representation
This commit is contained in:
@@ -556,3 +556,120 @@ TEST_CASE("Tensor Gradient Transposes Match Dense Assembly And Adjoint Products"
|
||||
mfem::IntRules.Get(mfem::Geometry::CUBE, 2*order+3));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(
|
||||
"Quartic Curved Hex Geometry Distinguishes Q3 And Q4 Affine Physical Displacements",
|
||||
tags::unit &tags::geometry &tags::quadrature
|
||||
) {
|
||||
constexpr int dimension = 3;
|
||||
constexpr double warpAmplitude = 0.2;
|
||||
constexpr double dilation = 0.2;
|
||||
constexpr double exactDeterminant = (1.0 + dilation) * (1.0 + dilation) * (1.0 + dilation);
|
||||
int ranks = 1;
|
||||
MPI_Comm_size(MPI_COMM_WORLD, &ranks);
|
||||
|
||||
// Subdivide only y so every rank can retain the full quartic x variation.
|
||||
// Its coordinate Jacobian is 1 + 2*a*x*(1-x)*(1-2*x) >= 1-a/2 > 0.
|
||||
auto serial = mfem::Mesh::MakeCartesian3D(1, ranks, 1, mfem::Element::HEXAHEDRON);
|
||||
serial.SetCurvature(4, false, dimension, mfem::Ordering::byNODES);
|
||||
mfem::VectorFunctionCoefficient warp(dimension, [](const mfem::Vector &x, mfem::Vector &y) {
|
||||
y = x;
|
||||
const double bubble = x(0) * (1.0 - x(0));
|
||||
y(0) += warpAmplitude * bubble * bubble;
|
||||
});
|
||||
serial.Transform(warp);
|
||||
mfem::ParMesh mesh(MPI_COMM_WORLD, serial);
|
||||
REQUIRE(mesh.GetNodes() != nullptr);
|
||||
CHECK(mesh.GetNodes()->FESpace()->GetMaxElementOrder() == 4);
|
||||
|
||||
// u(x)=s*x pulls back to the quartic coordinate polynomial. This tests the
|
||||
// ambient vector space, not the production surface prescription's r^2 lift.
|
||||
mfem::VectorFunctionCoefficient affineDisplacement(
|
||||
dimension, [](const mfem::Vector &x, mfem::Vector &u) {
|
||||
u = x;
|
||||
u *= dilation;
|
||||
}
|
||||
);
|
||||
const auto &rule = mfem::IntRules.Get(mfem::Geometry::CUBE, 24);
|
||||
for (const int order : {3, 4}) {
|
||||
CAPTURE(order);
|
||||
mfem::H1_FECollection collection(order, dimension, mfem::BasisType::GaussLobatto);
|
||||
mfem::ParFiniteElementSpace space(&mesh, &collection, dimension, mfem::Ordering::byNODES);
|
||||
CHECK(collection.FiniteElementForGeometry(mfem::Geometry::CUBE)->GetDof() ==
|
||||
(order + 1) * (order + 1) * (order + 1));
|
||||
mfem::ParGridFunction field(&space);
|
||||
field.ProjectCoefficient(affineDisplacement);
|
||||
mfem::Vector state;
|
||||
field.GetTrueDofs(state);
|
||||
field.SetFromTrueDofs(state);
|
||||
|
||||
// Accumulate rather than assert per quadrature point, and reject
|
||||
// non-finite samples explicitly so a max reduction cannot hide them.
|
||||
enum Error { coordinates, coordinateJacobian, values, gradients, determinants, count };
|
||||
std::array<double, count> localErrors{};
|
||||
int localFinite = 1;
|
||||
double localGeometryMinimum = std::numeric_limits<double>::infinity();
|
||||
auto recordError = [&](const Error error, const double value) {
|
||||
if (!std::isfinite(value)) localFinite = 0;
|
||||
else localErrors[error] = std::max(localErrors[error], std::abs(value));
|
||||
};
|
||||
mfem::Vector position(dimension), value(dimension);
|
||||
mfem::DenseMatrix gradient(dimension);
|
||||
for (int element = 0; element < mesh.GetNE(); ++element) {
|
||||
auto &transformation = *mesh.GetElementTransformation(element);
|
||||
for (int point = 0; point < rule.GetNPoints(); ++point) {
|
||||
const auto &ip = rule.IntPoint(point);
|
||||
transformation.SetIntPoint(&ip);
|
||||
transformation.Transform(ip, position);
|
||||
const double bubble = ip.x * (1.0 - ip.x);
|
||||
recordError(coordinates, position(0) - (ip.x + warpAmplitude * bubble * bubble));
|
||||
const double expectedJacobian = 1.0 + 2.0 * warpAmplitude * bubble * (1.0 - 2.0 * ip.x);
|
||||
const double coordinateDeterminant = ranks * transformation.Jacobian().Det();
|
||||
recordError(coordinateJacobian, coordinateDeterminant - expectedJacobian);
|
||||
if (std::isfinite(coordinateDeterminant))
|
||||
localGeometryMinimum = std::min(localGeometryMinimum, coordinateDeterminant);
|
||||
|
||||
field.GetVectorValue(transformation, ip, value);
|
||||
field.GetVectorGradient(transformation, gradient);
|
||||
for (int component = 0; component < dimension; ++component) {
|
||||
recordError(values, value(component) - dilation * position(component));
|
||||
for (int axis = 0; axis < dimension; ++axis)
|
||||
recordError(gradients, gradient(component, axis) -
|
||||
(component == axis ? dilation : 0.0));
|
||||
}
|
||||
for (int component = 0; component < dimension; ++component) gradient(component, component) += 1.0;
|
||||
recordError(determinants, gradient.Det() - exactDeterminant);
|
||||
}
|
||||
}
|
||||
std::array<double, count> errors{};
|
||||
int finite = 0;
|
||||
double geometryMinimum = 0.0;
|
||||
MPI_Allreduce(localErrors.data(), errors.data(), count, MPI_DOUBLE, MPI_MAX, MPI_COMM_WORLD);
|
||||
MPI_Allreduce(&localFinite, &finite, 1, MPI_INT, MPI_MIN, MPI_COMM_WORLD);
|
||||
MPI_Allreduce(&localGeometryMinimum, &geometryMinimum, 1, MPI_DOUBLE, MPI_MIN, MPI_COMM_WORLD);
|
||||
REQUIRE(finite == 1);
|
||||
CHECK(errors[coordinates] < 2.0e-13);
|
||||
CHECK(errors[coordinateJacobian] < 2.0e-12);
|
||||
CHECK(geometryMinimum >= 1.0 - warpAmplitude / 2.0 - 2.0e-12);
|
||||
|
||||
mean_field::fem::PreparedGeometryInspector inspector;
|
||||
const double fastMinimum = inspector.Inspect(space, state);
|
||||
const double referenceMinimum = inspector.Inspect(space, state, true);
|
||||
REQUIRE(std::isfinite(fastMinimum));
|
||||
REQUIRE(std::isfinite(referenceMinimum));
|
||||
CHECK(fastMinimum > 0.0);
|
||||
CHECK(std::abs(fastMinimum - referenceMinimum) < 2.0e-12);
|
||||
CHECK(inspector.GetTensorElementCount() == mesh.GetNE());
|
||||
if (order == 4) {
|
||||
CHECK(errors[values] < 2.0e-13);
|
||||
CHECK(errors[gradients] < 2.0e-12);
|
||||
CHECK(errors[determinants] < 5.0e-12);
|
||||
CHECK(std::abs(fastMinimum - exactDeterminant) < 5.0e-12);
|
||||
} else {
|
||||
CHECK(errors[values] > 1.0e-5);
|
||||
CHECK(errors[gradients] > 1.0e-4);
|
||||
CHECK(errors[determinants] > 1.0e-4);
|
||||
CHECK(std::abs(fastMinimum - exactDeterminant) > 1.0e-4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user