feat: add mitc4 geometry directors
This commit is contained in:
@@ -124,6 +124,22 @@ std::size_t diagnosticCount(const std::vector<fesa::Diagnostic>& diagnostics, co
|
||||
return count;
|
||||
}
|
||||
|
||||
void checkVecNear(const fesa::Vec3& actual, const fesa::Vec3& expected, fesa::Real tolerance) {
|
||||
FESA_CHECK_NEAR(actual.x, expected.x, tolerance);
|
||||
FESA_CHECK_NEAR(actual.y, expected.y, tolerance);
|
||||
FESA_CHECK_NEAR(actual.z, expected.z, tolerance);
|
||||
}
|
||||
|
||||
void checkRightHandedOrthonormal(const fesa::Vec3& e1, const fesa::Vec3& e2, const fesa::Vec3& e3) {
|
||||
FESA_CHECK_NEAR(fesa::norm(e1), 1.0, 1.0e-14);
|
||||
FESA_CHECK_NEAR(fesa::norm(e2), 1.0, 1.0e-14);
|
||||
FESA_CHECK_NEAR(fesa::norm(e3), 1.0, 1.0e-14);
|
||||
FESA_CHECK_NEAR(fesa::dot(e1, e2), 0.0, 1.0e-14);
|
||||
FESA_CHECK_NEAR(fesa::dot(e1, e3), 0.0, 1.0e-14);
|
||||
FESA_CHECK_NEAR(fesa::dot(e2, e3), 0.0, 1.0e-14);
|
||||
FESA_CHECK_NEAR(fesa::dot(fesa::cross(e1, e2), e3), 1.0, 1.0e-14);
|
||||
}
|
||||
|
||||
fesa::Domain singleElementValidationDomain() {
|
||||
fesa::Domain domain;
|
||||
domain.nodes[1] = {1, {0, 0, 0}};
|
||||
@@ -800,6 +816,111 @@ FESA_TEST(displacement_comparator_reports_duplicate_actual_nodes) {
|
||||
FESA_CHECK(fesa::containsDiagnostic(compared.diagnostics, "FESA-COMPARE-DUPLICATE-ACTUAL"));
|
||||
}
|
||||
|
||||
FESA_TEST(mitc4_shape_functions_node_order_and_tying_points) {
|
||||
auto center = fesa::shapeFunctions(0.0, 0.0);
|
||||
const fesa::Real sum = center.n[0] + center.n[1] + center.n[2] + center.n[3];
|
||||
FESA_CHECK_NEAR(sum, 1.0, 1.0e-15);
|
||||
FESA_CHECK_NEAR(center.dr[0], -0.25, 1.0e-15);
|
||||
FESA_CHECK_NEAR(center.dr[1], 0.25, 1.0e-15);
|
||||
FESA_CHECK_NEAR(center.dr[2], 0.25, 1.0e-15);
|
||||
FESA_CHECK_NEAR(center.dr[3], -0.25, 1.0e-15);
|
||||
FESA_CHECK_NEAR(center.ds[0], -0.25, 1.0e-15);
|
||||
FESA_CHECK_NEAR(center.ds[1], -0.25, 1.0e-15);
|
||||
FESA_CHECK_NEAR(center.ds[2], 0.25, 1.0e-15);
|
||||
FESA_CHECK_NEAR(center.ds[3], 0.25, 1.0e-15);
|
||||
|
||||
const auto node_points = fesa::mitc4NodeNaturalCoordinates();
|
||||
FESA_CHECK_NEAR(node_points[0].xi, -1.0, 1.0e-15);
|
||||
FESA_CHECK_NEAR(node_points[0].eta, -1.0, 1.0e-15);
|
||||
FESA_CHECK_NEAR(node_points[1].xi, 1.0, 1.0e-15);
|
||||
FESA_CHECK_NEAR(node_points[1].eta, -1.0, 1.0e-15);
|
||||
FESA_CHECK_NEAR(node_points[2].xi, 1.0, 1.0e-15);
|
||||
FESA_CHECK_NEAR(node_points[2].eta, 1.0, 1.0e-15);
|
||||
FESA_CHECK_NEAR(node_points[3].xi, -1.0, 1.0e-15);
|
||||
FESA_CHECK_NEAR(node_points[3].eta, 1.0, 1.0e-15);
|
||||
|
||||
for (std::size_t i = 0; i < 4; ++i) {
|
||||
const auto corner = fesa::shapeFunctions(node_points[i].xi, node_points[i].eta);
|
||||
FESA_CHECK_NEAR(corner.n[i], 1.0, 1.0e-15);
|
||||
}
|
||||
|
||||
const auto tying_points = fesa::mitc4TyingPoints();
|
||||
FESA_CHECK(tying_points[0].label == "A");
|
||||
FESA_CHECK_NEAR(tying_points[0].natural.xi, 0.0, 1.0e-15);
|
||||
FESA_CHECK_NEAR(tying_points[0].natural.eta, -1.0, 1.0e-15);
|
||||
FESA_CHECK((tying_points[0].edge_node_indices == std::array<fesa::LocalIndex, 2>{0, 1}));
|
||||
FESA_CHECK(tying_points[1].label == "B");
|
||||
FESA_CHECK_NEAR(tying_points[1].natural.xi, -1.0, 1.0e-15);
|
||||
FESA_CHECK_NEAR(tying_points[1].natural.eta, 0.0, 1.0e-15);
|
||||
FESA_CHECK((tying_points[1].edge_node_indices == std::array<fesa::LocalIndex, 2>{0, 3}));
|
||||
FESA_CHECK(tying_points[2].label == "C");
|
||||
FESA_CHECK_NEAR(tying_points[2].natural.xi, 0.0, 1.0e-15);
|
||||
FESA_CHECK_NEAR(tying_points[2].natural.eta, 1.0, 1.0e-15);
|
||||
FESA_CHECK((tying_points[2].edge_node_indices == std::array<fesa::LocalIndex, 2>{3, 2}));
|
||||
FESA_CHECK(tying_points[3].label == "D");
|
||||
FESA_CHECK_NEAR(tying_points[3].natural.xi, 1.0, 1.0e-15);
|
||||
FESA_CHECK_NEAR(tying_points[3].natural.eta, 0.0, 1.0e-15);
|
||||
FESA_CHECK((tying_points[3].edge_node_indices == std::array<fesa::LocalIndex, 2>{1, 2}));
|
||||
}
|
||||
|
||||
FESA_TEST(mitc4_geometry_builds_flat_directors_and_integration_basis) {
|
||||
const std::array<fesa::Vec3, 4> coords = {{{0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {0, 1, 0}}};
|
||||
auto geometry = fesa::buildMITC4Geometry(coords, 0.2);
|
||||
FESA_CHECK(geometry.ok());
|
||||
checkVecNear(geometry.center_normal, {0, 0, 1}, 1.0e-14);
|
||||
checkVecNear(geometry.g1_center, {0.5, 0, 0}, 1.0e-14);
|
||||
checkVecNear(geometry.g2_center, {0, 0.5, 0}, 1.0e-14);
|
||||
for (const auto& frame : geometry.nodal_frames) {
|
||||
checkVecNear(frame.v1, {1, 0, 0}, 1.0e-14);
|
||||
checkVecNear(frame.v2, {0, 1, 0}, 1.0e-14);
|
||||
checkVecNear(frame.vn, {0, 0, 1}, 1.0e-14);
|
||||
checkRightHandedOrthonormal(frame.v1, frame.v2, frame.vn);
|
||||
}
|
||||
|
||||
auto basis = fesa::computeMITC4IntegrationBasis(geometry, 0.0, 0.0, 0.0);
|
||||
FESA_CHECK(basis.ok());
|
||||
checkVecNear(basis.g1, {0.5, 0, 0}, 1.0e-14);
|
||||
checkVecNear(basis.g2, {0, 0.5, 0}, 1.0e-14);
|
||||
checkVecNear(basis.g3, {0, 0, 0.1}, 1.0e-14);
|
||||
checkVecNear(basis.local.e1, {1, 0, 0}, 1.0e-14);
|
||||
checkVecNear(basis.local.e2, {0, 1, 0}, 1.0e-14);
|
||||
checkVecNear(basis.local.e3, {0, 0, 1}, 1.0e-14);
|
||||
checkRightHandedOrthonormal(basis.local.e1, basis.local.e2, basis.local.e3);
|
||||
FESA_CHECK_NEAR(basis.jacobian, 0.025, 1.0e-14);
|
||||
}
|
||||
|
||||
FESA_TEST(mitc4_geometry_uses_deterministic_director_fallback_axis) {
|
||||
const std::array<fesa::Vec3, 4> coords = {{{0, 0, 0}, {1, 0, 0}, {1, 0, -1}, {0, 0, -1}}};
|
||||
auto geometry = fesa::buildMITC4Geometry(coords, 0.1);
|
||||
FESA_CHECK(geometry.ok());
|
||||
checkVecNear(geometry.center_normal, {0, 1, 0}, 1.0e-14);
|
||||
for (const auto& frame : geometry.nodal_frames) {
|
||||
checkVecNear(frame.v1, {-1, 0, 0}, 1.0e-14);
|
||||
checkVecNear(frame.v2, {0, 0, 1}, 1.0e-14);
|
||||
checkVecNear(frame.vn, {0, 1, 0}, 1.0e-14);
|
||||
checkRightHandedOrthonormal(frame.v1, frame.v2, frame.vn);
|
||||
}
|
||||
}
|
||||
|
||||
FESA_TEST(mitc4_geometry_reports_singular_geometry_and_thickness) {
|
||||
const std::array<fesa::Vec3, 4> flat = {{{0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {0, 1, 0}}};
|
||||
auto invalid_thickness = fesa::buildMITC4Geometry(flat, 0.0);
|
||||
FESA_CHECK(!invalid_thickness.ok());
|
||||
FESA_CHECK(fesa::containsDiagnostic(invalid_thickness.diagnostics, "FESA-MITC4-THICKNESS"));
|
||||
|
||||
const std::array<fesa::Vec3, 4> collinear = {{{0, 0, 0}, {1, 0, 0}, {2, 0, 0}, {3, 0, 0}}};
|
||||
auto singular = fesa::buildMITC4Geometry(collinear, 0.1);
|
||||
FESA_CHECK(!singular.ok());
|
||||
FESA_CHECK(fesa::containsDiagnostic(singular.diagnostics, "FESA-MITC4-SINGULAR-NORMAL"));
|
||||
|
||||
const std::array<fesa::Vec3, 4> collapsed_corner = {{{0, 0, 0}, {0, 0, 0}, {1, 1, 0}, {0, 1, 0}}};
|
||||
auto geometry = fesa::buildMITC4Geometry(collapsed_corner, 0.1);
|
||||
FESA_CHECK(geometry.ok());
|
||||
auto corner_basis = fesa::computeMITC4IntegrationBasis(geometry, -1.0, -1.0, 0.0);
|
||||
FESA_CHECK(!corner_basis.ok());
|
||||
FESA_CHECK(fesa::containsDiagnostic(corner_basis.diagnostics, "FESA-MITC4-SINGULAR-JACOBIAN"));
|
||||
}
|
||||
|
||||
FESA_TEST(mitc4_shape_functions_and_stiffness_baseline) {
|
||||
auto shape = fesa::shapeFunctions(0.25, -0.5);
|
||||
const fesa::Real sum = shape.n[0] + shape.n[1] + shape.n[2] + shape.n[3];
|
||||
|
||||
Reference in New Issue
Block a user