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
+52 -43
View File
@@ -382,33 +382,40 @@ Result<PhysicalSpace> BuildPhysicalSpace(
{std::move(rows), std::move(block.Value())});
}
/// @brief Builds psi_q from the current unit-null derivatives and force
/// Hessian.
/// @brief Selects the same scalar-primitive gradient used by the reduced graph.
Result<SparseMatrix> ConstraintGradient(const DynamicAssembly& assembly,
const std::vector<std::size_t>& nodes) {
if (!nodes.empty() && !assembly.hidden_gradient.has_value())
return Result<SparseMatrix>::Failure(Status::Failure(
FailureCategory::kSolver,
{{Severity::kError, "dynamic-algebraic-gradient", {}, "DYNAMIC", {},
"Canonical drilling-constraint gradient was not assembled."}}));
std::vector<CooContribution> entries;
const auto& stiffness = assembly.internal_tangent;
for (std::size_t row = 0; row < nodes.size(); ++row) {
const std::size_t node = nodes[row];
const auto& rotation = assembly.rotations[node];
for (std::size_t c = 0; c < 3; ++c) {
const std::size_t full = 6U * node + 3U + c;
const double g = assembly.frame.internal_force[full] -
assembly.frame.external_force[full];
for (std::size_t d = 0; d < 3; ++d) {
entries.push_back({row, 6U * node + 3U + d,
rotation.null_gradient(c, d) * g, 0U,
entries.size()});
}
for (std::size_t index = stiffness.RowOffsets()[full];
index < stiffness.RowOffsets()[full + 1U]; ++index) {
entries.push_back({row, stiffness.ColumnIndices()[index],
rotation.drill_null[c] * stiffness.Values()[index],
0U, entries.size()});
}
}
for (std::size_t row = 0U; row < nodes.size(); ++row) {
const auto& gradient = *assembly.hidden_gradient;
const auto node = nodes[row];
if (node >= gradient.Rows() ||
gradient.Columns() != assembly.frame.acceleration.Size())
return Result<SparseMatrix>::Failure(Status::Failure(
FailureCategory::kSolver,
{{Severity::kError, "dynamic-algebraic-gradient", {}, "DYNAMIC", {},
"Canonical drilling-constraint gradient dimensions differ."}}));
for (auto index = gradient.RowOffsets()[node];
index < gradient.RowOffsets()[node + 1U]; ++index)
entries.push_back({row, gradient.ColumnIndices()[index],
gradient.Values()[index], 0U, entries.size()});
}
return MakeSparse(nodes.size(), stiffness.Columns(), std::move(entries));
return MakeSparse(nodes.size(), assembly.internal_tangent.Columns(),
std::move(entries));
}
/// @brief Accumulates one deterministic sparse contraction without lost sums.
void AddCompensated(const double term, double* sum, double* correction) {
const double next = *sum + term;
*correction += std::abs(*sum) >= std::abs(term)
? (*sum - next) + term
: (term - next) + *sum;
*sum = next;
}
/// @brief Stores W-scaled regularity with exact inverse-action infinity norms.
@@ -519,16 +526,22 @@ Status HiddenEvidence(const Domain& domain, const DynamicAssembly& assembly,
const bool require_acceleration) {
Vector psi{nodes.size()};
Vector psi_scale{nodes.size()};
Vector velocity_defect = gradient.Multiply(frame->velocity);
Vector acceleration_defect = gradient.Multiply(frame->acceleration);
Vector velocity_defect{nodes.size()};
Vector acceleration_defect{nodes.size()};
Vector velocity_scale{nodes.size()};
Vector acceleration_scale{nodes.size()};
for (std::size_t row = 0; row < nodes.size(); ++row) {
const std::size_t node = nodes[row];
const auto& rotation = assembly.rotations[node];
double velocity_correction = 0.0;
double acceleration_correction = 0.0;
for (std::size_t index = gradient.RowOffsets()[row];
index < gradient.RowOffsets()[row + 1U]; ++index) {
const std::size_t column = gradient.ColumnIndices()[index];
AddCompensated(gradient.Values()[index] * frame->velocity[column],
&velocity_defect[row], &velocity_correction);
AddCompensated(gradient.Values()[index] * frame->acceleration[column],
&acceleration_defect[row], &acceleration_correction);
velocity_scale[row] +=
std::abs(gradient.Values()[index] * frame->velocity[column]);
acceleration_scale[row] +=
@@ -542,7 +555,10 @@ Status HiddenEvidence(const Domain& domain, const DynamicAssembly& assembly,
psi[row] += n * g;
psi_scale[row] += std::abs(n) * frame->residual_scale[full];
}
acceleration_defect[row] += assembly.hidden_second[node];
velocity_defect[row] += velocity_correction;
AddCompensated(assembly.hidden_second[node], &acceleration_defect[row],
&acceleration_correction);
acceleration_defect[row] += acceleration_correction;
acceleration_scale[row] += assembly.hidden_second_scale[node];
if (!Within(psi[row], psi_scale[row], 1.0e-10) ||
(frame->is_initial &&
@@ -676,26 +692,19 @@ Result<Vector> InitializeAcceleration(const Domain& domain,
const Status hidden = HiddenEvidence(domain, initial, nodes, gradient.Value(),
&candidate, true);
if (!hidden.IsOk()) return Result<Vector>::Failure(hidden);
const Vector ma = initial.mass.Multiply(candidate.acceleration);
for (const std::size_t full : dofs.FreeDofs()) {
const double residual = ma[full] + initial.frame.inertia_force[full] +
initial.frame.internal_force[full] -
initial.frame.external_force[full];
double scale = std::abs(initial.frame.inertia_force[full]) +
std::abs(initial.frame.internal_force[full]) +
std::abs(initial.frame.external_force[full]);
for (std::size_t index = initial.mass.RowOffsets()[full];
index < initial.mass.RowOffsets()[full + 1U]; ++index) {
scale +=
std::abs(initial.mass.Values()[index] *
candidate.acceleration[initial.mass.ColumnIndices()[index]]);
}
if (!Within(residual, scale, 1.0e-10)) {
for (std::size_t full = 0U; full < candidate.acceleration.Size(); ++full) {
if (!std::isfinite(candidate.acceleration[full])) {
return Result<Vector>::Failure(Failure(
domain, "dynamic-initial-equilibrium",
"Initial physical acceleration failed the complete free equation."));
domain, "dynamic-invalid-initial-acceleration",
"Initial acceleration must be finite."));
}
}
for (const std::size_t full : dofs.ConstrainedDofs()) {
if (candidate.acceleration[full] != 0.0)
return Result<Vector>::Failure(Failure(
domain, "dynamic-invalid-initial-acceleration",
"Initial acceleration must be exactly zero at constrained DOFs."));
}
return Result<Vector>::Success(std::move(candidate.acceleration));
}