feat(geometry/deformation/backend/mfem): added radial_location implementation

This commit is contained in:
2026-10-12 14:10:52 -04:00
parent eb9da56438
commit 0a1fb8d8db

View File

@@ -0,0 +1,126 @@
#include <algorithm>
#include <cmath>
#include <limits>
#include "mfem/mesh/mesh.hpp"
#include "serif/geometry/deformation/backend/mfem/detail/radial_locator.hpp"
namespace serif::geometry::deformation::backend::mfem::detail {
double logical_radius(const ::mfem::Vector& position) {
double radius = 0.0;
for (int component = 0; component < position.Size(); ++component) {
radius = std::max(radius, std::abs(position[component]));
}
return radius;
}
std::expected<double, DeformationFailure> boundary_logical_radius(::mfem::Mesh& mesh, const int attribute) {
double minimum = std::numeric_limits<double>::infinity();
double maximum = 0.0;
int samples = 0;
::mfem::Vector position(3);
for (int boundary = 0; boundary < mesh.GetNBE(); ++boundary) {
if (mesh.GetBdrAttribute(boundary) != attribute) {
continue;
}
::mfem::ElementTransformation* transformation = mesh.GetBdrElementTransformation(boundary);
const int geometry = mesh.GetBdrElementGeometry(boundary);
if (geometry != ::mfem::Geometry::SQUARE || transformation == nullptr) {
return std::unexpected(DeformationFailure{.reason = DeformationFailureReason::incompatible_discretization, .message = "The logical radial foliation requires quadrilateral shell elements.", .element = boundary});
}
const ::mfem::IntegrationRule* vertices = ::mfem::Geometries.GetVertices(geometry);
for (int vertex = 0; vertex < vertices->GetNPoints(); ++vertex) {
transformation->Transform(vertices->IntPoint(vertex), position);
for (int component = 0; component < 3; ++component) {
if (!std::isfinite(position[component])) {
return std::unexpected(DeformationFailure{.reason = DeformationFailureReason::non_finite, .message = "A logical boundary vertex is non-finite.", .element = boundary, .point = vertex});
}
}
const double radius = logical_radius(position);
minimum = std::min(minimum, radius);
maximum = std::max(maximum, radius);
++samples;
}
}
if (samples == 0 || !(minimum > 0.0) || maximum - minimum > 1.0e-12 * std::max(1.0, maximum)) {
return std::unexpected(DeformationFailure{.reason = DeformationFailureReason::invalid_geometry, .message = "A required logical boundary is absent or is not a positive constant L-infinity-radius shell."});
}
return 0.5 * (minimum + maximum);
}
std::expected<LocatedBoundaryPoint, DeformationFailure> locate_boundary_point(::mfem::Mesh& mesh, const int attribute, const ::mfem::Vector& target) {
LocatedBoundaryPoint best{.boundary_element = -1, .point = {}};
double best_residual = std::numeric_limits<double>::infinity();
const double tolerance = 2.0e-11 * std::max(1.0, logical_radius(target));
::mfem::Vector transformed(3), residual(3);
for (int boundary = 0; boundary < mesh.GetNBE(); ++boundary) {
if (mesh.GetBdrAttribute(boundary) != attribute) {
continue;
}
::mfem::ElementTransformation* transformation = mesh.GetBdrElementTransformation(boundary);
::mfem::IntegrationPoint point = ::mfem::Geometries.GetCenter(::mfem::Geometry::SQUARE);
bool nonsingular = true;
for (int iteration = 0; iteration < 8; ++iteration) {
transformation->Transform(point, transformed);
residual = target;
residual -= transformed;
if (residual.Norml2() <= tolerance) {
break;
}
transformation->SetIntPoint(&point);
const ::mfem::DenseMatrix& jacobian = transformation->Jacobian();
double normal00 = 0.0, normal01 = 0.0, normal11 = 0.0;
double right0 = 0.0, right1 = 0.0;
for (int component = 0; component < 3; ++component) {
normal00 += jacobian(component, 0) * jacobian(component, 0);
normal01 += jacobian(component, 0) * jacobian(component, 1);
normal11 += jacobian(component, 1) * jacobian(component, 1);
right0 += jacobian(component, 0) * residual(component);
right1 += jacobian(component, 1) * residual(component);
}
const double determinant = normal00 * normal11 - normal01 * normal01;
if (!(std::abs(determinant) > std::numeric_limits<double>::epsilon())) {
nonsingular = false;
break;
}
point.x += (normal11 * right0 - normal01 * right1) / determinant;
point.y += (normal00 * right1 - normal01 * right0) / determinant;
}
if (!nonsingular || !::mfem::Geometry::CheckPoint(::mfem::Geometry::SQUARE, point, 1.0e-10)) {
continue;
}
transformation->Transform(point, transformed);
transformed -= target;
const double norm = transformed.Norml2();
if (norm <= tolerance && norm < best_residual) {
best = {.boundary_element = boundary, .point = point};
best_residual = norm;
}
}
if (best.boundary_element < 0) {
return std::unexpected(DeformationFailure{.reason = DeformationFailureReason::unresolved_surface, .message = "No logical quadrilateral contains a radial projection onto the required shell."});
}
return best;
}
}