feat: adopt lumped mass and reduced-coordinate shell HHT

Integrate Hex8 mass verification and fixed-null-basis shell dynamics with numerical and physics handoff evidence. Preserve interrupted reference and full-validation status; full integration validation is deferred until after performance optimization.
This commit is contained in:
KOKO\Mimi
2026-10-13 16:57:19 +09:00
parent ede137c6b4
commit eaa5cbeb21
110 changed files with 15274 additions and 971 deletions
@@ -79,8 +79,8 @@ fesa::ModelDefinition TwoBricks(double h, unsigned count, bool nonlinear) {
fesa::SourceLocation{});
return d;
}
// Closed polynomial brick integrals: integral Na*X and integral grad Na.
// These do not call the production stiffness, mass, quadrature or response.
// Closed polynomial brick integrals and pre-restriction row-sum masses. These
// do not call the production stiffness, mass, quadrature or response.
std::vector<double> ExactForce(const fesa::ModelDefinition& d, double t,
bool nonlinear) {
const auto m = Motion(t, nonlinear);
@@ -89,14 +89,14 @@ std::vector<double> ExactForce(const fesa::ModelDefinition& d, double t,
const auto& nodes = d.solid_elements[e].node_indices;
for (unsigned a = 0; a < 8; ++a) {
const auto& x = d.nodes[nodes[a]].coordinates;
std::array<double, 3> integral_x{}, gradient{};
std::array<double, 3> gradient{};
for (unsigned c = 0; c < 3; ++c) {
const double origin = c == 0 ? static_cast<double>(e) : 0;
const double local = x[c] - origin;
integral_x[c] = 2 * (origin / 8 + (1 + local) / 24);
gradient[c] = (2 * local - 1) / 4;
}
const auto inertia = Apply(m.acceleration, integral_x);
const auto inertia = Apply(
m.acceleration, {.25 * x[0], .25 * x[1], .25 * x[2]});
const auto internal = Apply(m.piola, gradient);
for (unsigned c = 0; c < 3; ++c)
f[3 * nodes[a] + c] += inertia[c] + internal[c];
@@ -110,16 +110,8 @@ std::vector<double> ExactMassProduct(const fesa::Domain& d,
for (unsigned e = 0; e < 2; ++e) {
const auto& nodes = d.SolidElements()[e].node_indices;
for (unsigned a = 0; a < 8; ++a)
for (unsigned b = 0; b < 8; ++b) {
double coefficient = 2.0 / 216;
for (unsigned c = 0; c < 3; ++c)
coefficient *= d.Nodes()[nodes[a]].coordinates[c] ==
d.Nodes()[nodes[b]].coordinates[c]
? 2
: 1;
for (unsigned c = 0; c < 3; ++c)
result[3 * nodes[a] + c] += coefficient * v[3 * nodes[b] + c];
}
for (unsigned c = 0; c < 3; ++c)
result[3 * nodes[a] + c] += .25 * v[3 * nodes[a] + c];
}
return result;
}
@@ -505,8 +497,8 @@ TEST(SolidHhtTemporal,
}
TEST(SolidHhtTemporal, IndependentThreeDofBrickOscillatorHasSecondOrderUVA) {
// Node0's three translations are free. Exact polynomial integrals yield
// M=(rho/27)I, Kii=41/18, Kij=19/30 for mu=4.8 and direct kappa=8.
// Node0's three translations are free. Pre-restriction row-sum lumping
// yields M=(rho/8)I, Kii=41/18, Kij=19/30 for mu=4.8 and kappa=8.
std::array<std::array<double, 3>, 3> errors{};
for (unsigned level = 0; level < 3; ++level) {
const double h = .02 / static_cast<double>(1U << level);
@@ -528,7 +520,7 @@ TEST(SolidHhtTemporal, IndependentThreeDofBrickOscillatorHasSecondOrderUVA) {
ASSERT_TRUE(result.HasValue())
<< solid_hht_test::Message(result.GetStatus());
const auto& f = result.Value().state.CommittedState();
const double m = 2.0 / 27;
const double m = .25;
const double diagonal = 41.0 / 18;
const double off = 19.0 / 30;
const std::array<double, 2> k{diagonal + 2 * off, diagonal - off};
@@ -562,7 +554,7 @@ TEST(SolidHhtTemporal, IndependentThreeDofBrickOscillatorHasSecondOrderUVA) {
TEST(SolidHhtTemporal, IndependentScalarBrickOscillatorHasSecondOrderUVA) {
std::array<std::array<double, 3>, 3> errors{};
const double m = 2.0 / 27;
const double m = .25;
const double k = 41.0 / 18;
const double w = std::sqrt(k / m);
for (unsigned level = 0; level < 3; ++level) {