feat: rebuild mitc4 stiffness drilling

This commit is contained in:
NINI
2026-05-04 22:55:52 +09:00
parent 2e1bd99d05
commit 5465ea61d8
5 changed files with 344 additions and 176 deletions
+113
View File
@@ -147,6 +147,19 @@ std::array<fesa::Real, 24> zeroElementDofs() {
return values;
}
std::vector<fesa::Real> zeroElementVector() {
return std::vector<fesa::Real>(24, 0.0);
}
fesa::Real quadraticEnergy(const fesa::DenseMatrix& stiffness, const std::vector<fesa::Real>& values) {
const auto internal = stiffness.multiply(values);
fesa::Real energy = 0.0;
for (std::size_t i = 0; i < values.size(); ++i) {
energy += values[i] * internal[i];
}
return energy;
}
fesa::Domain singleElementValidationDomain() {
fesa::Domain domain;
domain.nodes[1] = {1, {0, 0, 0}};
@@ -1219,6 +1232,106 @@ FESA_TEST(mitc4_material_integration_samples_carry_tied_rows_basis_and_transform
}
}
FESA_TEST(mitc4_element_stiffness_result_is_symmetric_and_uses_2x2x2_samples) {
const std::array<fesa::Vec3, 4> coords = {{{0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {0, 1, 0}}};
const auto result = fesa::mitc4ElementStiffness(coords, 1000.0, 0.25, 0.2);
FESA_CHECK(result.ok());
FESA_CHECK(result.integration_point_count == 8);
FESA_CHECK(result.local_without_drilling.rows() == 24);
FESA_CHECK(result.local_with_drilling.rows() == 24);
FESA_CHECK(result.global.rows() == 24);
FESA_CHECK(result.global.cols() == 24);
for (fesa::LocalIndex i = 0; i < 24; ++i) {
for (fesa::LocalIndex j = 0; j < 24; ++j) {
FESA_CHECK_NEAR(result.local_without_drilling(i, j), result.local_without_drilling(j, i), 1.0e-8);
FESA_CHECK_NEAR(result.local_with_drilling(i, j), result.local_with_drilling(j, i), 1.0e-8);
FESA_CHECK_NEAR(result.global(i, j), result.global(j, i), 1.0e-8);
}
}
}
FESA_TEST(mitc4_drilling_stiffness_uses_minimum_positive_physical_local_diagonal) {
const std::array<fesa::Vec3, 4> coords = {{{0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {0, 1, 0}}};
const auto result = fesa::mitc4ElementStiffness(coords, 1000.0, 0.25, 0.2);
FESA_CHECK(result.ok());
fesa::Real expected_reference = std::numeric_limits<fesa::Real>::infinity();
for (fesa::LocalIndex node = 0; node < 4; ++node) {
for (fesa::LocalIndex local_dof = 0; local_dof < 5; ++local_dof) {
const fesa::Real diagonal = result.local_without_drilling(6 * node + local_dof, 6 * node + local_dof);
if (std::isfinite(diagonal) && diagonal > 0.0) {
expected_reference = std::min(expected_reference, diagonal);
}
}
}
FESA_CHECK(std::isfinite(expected_reference));
FESA_CHECK_NEAR(result.drilling_reference_diagonal, expected_reference, 1.0e-10);
FESA_CHECK_NEAR(result.drilling_stiffness_scale, 1.0e-3, 1.0e-15);
FESA_CHECK_NEAR(result.drilling_stiffness, 1.0e-3 * expected_reference, 1.0e-10);
for (fesa::LocalIndex node = 0; node < 4; ++node) {
const fesa::LocalIndex gamma = 6 * node + 5;
FESA_CHECK_NEAR(result.local_with_drilling(gamma, gamma) - result.local_without_drilling(gamma, gamma),
result.drilling_stiffness, 1.0e-12);
}
fesa::ElementStiffnessOptions options;
options.drilling_stiffness_scale = 2.0e-3;
const auto scaled = fesa::mitc4ElementStiffness(coords, 1000.0, 0.25, 0.2, options);
FESA_CHECK(scaled.ok());
FESA_CHECK_NEAR(scaled.drilling_reference_diagonal, result.drilling_reference_diagonal, 1.0e-10);
FESA_CHECK_NEAR(scaled.drilling_stiffness, 2.0 * result.drilling_stiffness, 1.0e-10);
}
FESA_TEST(mitc4_drilling_reports_invalid_scale_and_missing_reference_diagonal) {
fesa::DenseMatrix zero_physical(24, 24);
auto missing_reference = fesa::mitc4ApplyDrillingStabilization(zero_physical, 1.0e-3);
FESA_CHECK(!missing_reference.ok());
FESA_CHECK(fesa::containsDiagnostic(missing_reference.diagnostics, "FESA-MITC4-DRILLING-REFERENCE"));
auto invalid_scale = fesa::mitc4ApplyDrillingStabilization(zero_physical, -1.0);
FESA_CHECK(!invalid_scale.ok());
FESA_CHECK(fesa::containsDiagnostic(invalid_scale.diagnostics, "FESA-MITC4-DRILLING-SCALE"));
}
FESA_TEST(mitc4_rigid_translation_energy_is_zero_and_drilling_energy_is_documented) {
const std::array<fesa::Vec3, 4> coords = {{{0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {0, 1, 0}}};
const auto result = fesa::mitc4ElementStiffness(coords, 1000.0, 0.25, 0.2);
FESA_CHECK(result.ok());
auto translate_x = zeroElementVector();
auto translate_y = zeroElementVector();
auto translate_z = zeroElementVector();
for (int node = 0; node < 4; ++node) {
translate_x[static_cast<std::size_t>(6 * node + 0)] = 1.0;
translate_y[static_cast<std::size_t>(6 * node + 1)] = 1.0;
translate_z[static_cast<std::size_t>(6 * node + 2)] = 1.0;
}
FESA_CHECK(std::fabs(quadraticEnergy(result.global, translate_x)) < 1.0e-8);
FESA_CHECK(std::fabs(quadraticEnergy(result.global, translate_y)) < 1.0e-8);
FESA_CHECK(std::fabs(quadraticEnergy(result.global, translate_z)) < 1.0e-8);
auto drilling = zeroElementVector();
for (int node = 0; node < 4; ++node) {
drilling[static_cast<std::size_t>(6 * node + 5)] = 1.0;
}
FESA_CHECK_NEAR(quadraticEnergy(result.global, drilling), 4.0 * result.drilling_stiffness, 1.0e-8);
}
FESA_TEST(mitc4_internal_force_is_stiffness_times_displacement_for_linear_phase1) {
const std::array<fesa::Vec3, 4> coords = {{{0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {0, 1, 0}}};
const auto result = fesa::mitc4ElementStiffness(coords, 1000.0, 0.25, 0.2);
FESA_CHECK(result.ok());
std::vector<fesa::Real> displacement(24, 0.0);
for (std::size_t i = 0; i < displacement.size(); ++i) {
displacement[i] = 0.001 * static_cast<fesa::Real>(i + 1);
}
const auto expected = result.global.multiply(displacement);
const auto actual = fesa::mitc4ElementInternalForce(result, displacement);
FESA_CHECK(actual.size() == expected.size());
for (std::size_t i = 0; i < expected.size(); ++i) {
FESA_CHECK_NEAR(actual[i], expected[i], 1.0e-12);
}
}
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];