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:
@@ -225,6 +225,92 @@ Status CheckLinearSolve(const SparseMatrix& matrix, const Vector& rhs,
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
Result<DynamicAssembly> EvaluateNewtonStart(
|
||||
const Domain& domain, const DofManager& dofs,
|
||||
const std::vector<RuntimeShell>& shells, DynamicAcceptedFrameState frame,
|
||||
const HhtPredictor& predictor, const Vector& old_acceleration,
|
||||
const Vector& load_scale, const double time_increment,
|
||||
const HhtParameters& parameters, const ParallelFor& parallel_for) {
|
||||
const Vector accepted_coordinates = frame.generalized_coordinates;
|
||||
auto evaluate = [&](const Vector& coordinates) -> Result<DynamicAssembly> {
|
||||
frame.generalized_coordinates = coordinates;
|
||||
auto rates = EvaluateHhtRates(coordinates, predictor, old_acceleration,
|
||||
time_increment, parameters);
|
||||
if (!rates.HasValue())
|
||||
return Result<DynamicAssembly>::Failure(rates.GetStatus());
|
||||
frame.velocity = std::move(rates.Value().velocity);
|
||||
frame.acceleration = std::move(rates.Value().acceleration);
|
||||
frame.algorithmic_acceleration =
|
||||
std::move(rates.Value().algorithmic_acceleration);
|
||||
frame.iteration_count = 0U;
|
||||
return EvaluateAssembly(domain, dofs, shells, frame, load_scale,
|
||||
time_increment, parameters, parallel_for, false);
|
||||
};
|
||||
auto start = evaluate(predictor.coordinates);
|
||||
if (start.HasValue()) {
|
||||
bool same_coordinates =
|
||||
accepted_coordinates.Size() == predictor.coordinates.Size();
|
||||
for (std::size_t i = 0U; same_coordinates && i < accepted_coordinates.Size();
|
||||
++i)
|
||||
same_coordinates = accepted_coordinates[i] == predictor.coordinates[i];
|
||||
if (same_coordinates) return start;
|
||||
auto accepted_start = evaluate(accepted_coordinates);
|
||||
if (!accepted_start.HasValue()) return accepted_start;
|
||||
const auto length = ReferenceLength(domain);
|
||||
if (!length.HasValue())
|
||||
return Result<DynamicAssembly>::Failure(length.GetStatus());
|
||||
double predictor_residual = 0.0;
|
||||
double accepted_residual = 0.0;
|
||||
for (const auto full : dofs.FreeDofs()) {
|
||||
const double scale = full % 6U < 3U ? length.Value() : 1.0;
|
||||
predictor_residual =
|
||||
std::max(predictor_residual,
|
||||
scale * std::abs(start.Value().frame.residual[full]));
|
||||
accepted_residual =
|
||||
std::max(accepted_residual,
|
||||
scale * std::abs(accepted_start.Value().frame.residual[full]));
|
||||
}
|
||||
if (!std::isfinite(predictor_residual) || !std::isfinite(accepted_residual))
|
||||
return Result<DynamicAssembly>::Failure(
|
||||
Failure("The scaled initial Newton residual must remain finite."));
|
||||
// A geometrically valid large DIRECT predictor may still be far from the
|
||||
// endpoint. Compare only these two initial trials, in common energy units.
|
||||
return predictor_residual <= accepted_residual ? std::move(start)
|
||||
: std::move(accepted_start);
|
||||
}
|
||||
if (start.GetStatus().Diagnostics().empty()) return start;
|
||||
const auto& code = start.GetStatus().Diagnostics().front().code;
|
||||
bool invalid_geometry = code == "invalid-nonlinear-shell-geometry" ||
|
||||
code == "invalid-nonlinear-shell-director" ||
|
||||
code == "invalid-nonlinear-inertia-geometry" ||
|
||||
code == "invalid-nonlinear-inertia-director";
|
||||
if (code == "invalid-dynamic-rotation") {
|
||||
// This diagnostic also covers rate overflow. Only a finite coordinate
|
||||
// outside the existing principal margin permits another initial geometry.
|
||||
const double pi = std::acos(-1.0);
|
||||
const double margin = std::sqrt(std::numeric_limits<double>::epsilon());
|
||||
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
|
||||
const std::size_t offset = 6U * node + 3U;
|
||||
const auto phi = Triple(predictor.coordinates, offset);
|
||||
const auto rotation = EvaluateDynamicRotation(
|
||||
phi, Vector3(domain.ShellNodeInitialFrames()[node].director),
|
||||
Triple(frame.velocity, offset), Triple(frame.acceleration, offset));
|
||||
if (rotation.HasValue()) continue;
|
||||
// Match assembly's first failing node; an unrelated later chart error
|
||||
// must not turn an earlier valid-chart rate failure into a fallback.
|
||||
const double angle = phi.Norm();
|
||||
invalid_geometry =
|
||||
phi.IsFinite() &&
|
||||
(!std::isfinite(angle) || !(pi - angle > margin));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!invalid_geometry) return start;
|
||||
// This is initial-iterate selection only: neither a Newton correction nor
|
||||
// the frozen HHT predictor is changed, and accepted history is never mutated.
|
||||
return evaluate(accepted_coordinates);
|
||||
}
|
||||
|
||||
Result<DynamicAssembly> EvaluateAssembly(
|
||||
const Domain& domain, const DofManager& dofs,
|
||||
const std::vector<RuntimeShell>& shells, DynamicAcceptedFrameState frame,
|
||||
@@ -317,9 +403,12 @@ Result<DynamicAssembly> EvaluateAssembly(
|
||||
frame.angular_momentum_rate = {};
|
||||
std::vector<std::map<std::pair<std::size_t, std::size_t>, double>>
|
||||
constraint_hessians(domain.Nodes().size());
|
||||
std::vector<std::map<std::size_t, double>>
|
||||
constraint_gradients(domain.Nodes().size());
|
||||
std::vector<bool> external_done(domain.Nodes().size(), false);
|
||||
Vector third(count);
|
||||
Vector third_scale(count);
|
||||
std::array<std::vector<CooContribution>, 4> entries;
|
||||
std::array<std::vector<CooContribution>, 6> entries;
|
||||
for (auto& values : entries) values.reserve(shells.size() * 24U * 24U);
|
||||
double momentum_scale = 0.0;
|
||||
double angular_scale = 0.0;
|
||||
@@ -335,12 +424,34 @@ Result<DynamicAssembly> EvaluateAssembly(
|
||||
const auto& m = *inertia[e];
|
||||
if (directional_third) {
|
||||
for (std::size_t local = 0U; local < 4U; ++local) {
|
||||
auto& hessian = constraint_hessians[shells[e].scatter[6U * local] / 6U];
|
||||
const auto node = shells[e].scatter[6U * local] / 6U;
|
||||
auto& gradient = constraint_gradients[node];
|
||||
auto& hessian = constraint_hessians[node];
|
||||
for (std::size_t i = 0U; i < 24U; ++i)
|
||||
gradient[shells[e].scatter[i]] +=
|
||||
f.drilling_constraint_gradient(local, i);
|
||||
for (std::size_t i = 0U; i < 24U; ++i)
|
||||
for (std::size_t j = 0U; j < 24U; ++j) {
|
||||
hessian[{shells[e].scatter[i], shells[e].scatter[j]}] +=
|
||||
f.drilling_constraint_hessian[local](i, j);
|
||||
}
|
||||
if (!external_done[node]) {
|
||||
external_done[node] = true;
|
||||
const auto first = 6U * node + 3U;
|
||||
const auto external =
|
||||
shells[e].internal.EvaluateExternalDrillingConstraint(
|
||||
local, Triple(frame.generalized_coordinates, first),
|
||||
Triple(frame.velocity, first),
|
||||
Triple(frame.external_force, first), {}, {});
|
||||
if (!external.HasValue())
|
||||
return Result<DynamicAssembly>::Failure(external.GetStatus());
|
||||
for (std::size_t i = 0U; i < 3U; ++i) {
|
||||
gradient[first + i] += external.Value().gradient[i];
|
||||
for (std::size_t j = 0U; j < 3U; ++j)
|
||||
hessian[{first + i, first + j}] +=
|
||||
external.Value().hessian(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto a = Gather(frame.acceleration, shells[e]);
|
||||
@@ -356,13 +467,14 @@ Result<DynamicAssembly> EvaluateAssembly(
|
||||
for (std::size_t column = 0U; column < 24U; ++column) {
|
||||
frame.residual_scale[global] +=
|
||||
std::abs(m.mass(row, column) * a[column]);
|
||||
const std::array<double, 4> values{
|
||||
m.mass(row, column), f.internal_tangent(row, column),
|
||||
const std::array<double, 6> values{
|
||||
m.mass(row, column), m.position_tangent(row, column),
|
||||
m.velocity_tangent(row, column), f.internal_tangent(row, column),
|
||||
f.internal_tangent(row, column) + m.position_tangent(row, column) +
|
||||
v_coefficient * m.velocity_tangent(row, column) +
|
||||
a_coefficient * m.mass(row, column),
|
||||
f.drilling_metric(row, column)};
|
||||
for (std::size_t k = 0U; k < 4U; ++k)
|
||||
for (std::size_t k = 0U; k < 6U; ++k)
|
||||
entries[k].push_back({global, shells[e].scatter[column], values[k], e,
|
||||
row * 24U + column});
|
||||
}
|
||||
@@ -414,13 +526,18 @@ Result<DynamicAssembly> EvaluateAssembly(
|
||||
frame.free_residual_metrics[c] = Ratio(residual_norm[c], scale_norm[c]);
|
||||
auto mass = SparseMatrix::FromCoo(count, count, std::move(entries[0U]),
|
||||
dofs.GetSparsePattern());
|
||||
auto stiffness = SparseMatrix::FromCoo(count, count, std::move(entries[1U]),
|
||||
auto position = SparseMatrix::FromCoo(count, count, std::move(entries[1U]),
|
||||
dofs.GetSparsePattern());
|
||||
auto velocity = SparseMatrix::FromCoo(count, count, std::move(entries[2U]),
|
||||
dofs.GetSparsePattern());
|
||||
auto stiffness = SparseMatrix::FromCoo(count, count, std::move(entries[3U]),
|
||||
dofs.GetSparsePattern());
|
||||
auto effective = SparseMatrix::FromCoo(count, count, std::move(entries[2U]),
|
||||
auto effective = SparseMatrix::FromCoo(count, count, std::move(entries[4U]),
|
||||
dofs.GetSparsePattern());
|
||||
auto metric = SparseMatrix::FromCoo(count, count, std::move(entries[3U]),
|
||||
auto metric = SparseMatrix::FromCoo(count, count, std::move(entries[5U]),
|
||||
dofs.GetSparsePattern());
|
||||
for (const auto* result : {&mass, &stiffness, &effective, &metric})
|
||||
for (const auto* result : {&mass, &position, &velocity, &stiffness,
|
||||
&effective, &metric})
|
||||
if (!result->HasValue())
|
||||
return Result<DynamicAssembly>::Failure(result->GetStatus());
|
||||
Vector hidden_second(domain.Nodes().size());
|
||||
@@ -433,12 +550,26 @@ Result<DynamicAssembly> EvaluateAssembly(
|
||||
hidden_second_scale[node] += std::abs(term);
|
||||
}
|
||||
}
|
||||
std::optional<SparseMatrix> hidden_gradient;
|
||||
if (directional_third) {
|
||||
std::vector<CooContribution> gradient_entries;
|
||||
for (std::size_t node = 0U; node < constraint_gradients.size(); ++node)
|
||||
for (const auto& entry : constraint_gradients[node])
|
||||
gradient_entries.push_back(
|
||||
{node, entry.first, entry.second, node, gradient_entries.size()});
|
||||
auto gradient = MakeSparse(domain.Nodes().size(), count,
|
||||
std::move(gradient_entries));
|
||||
if (!gradient.HasValue())
|
||||
return Result<DynamicAssembly>::Failure(gradient.GetStatus());
|
||||
hidden_gradient = std::move(gradient.Value());
|
||||
}
|
||||
return Result<DynamicAssembly>::Success(
|
||||
{std::move(frame), std::move(mass.Value()), std::move(stiffness.Value()),
|
||||
{std::move(frame), std::move(mass.Value()), std::move(position.Value()),
|
||||
std::move(velocity.Value()), std::move(stiffness.Value()),
|
||||
std::move(effective.Value()), std::move(metric.Value()),
|
||||
std::move(third), std::move(third_scale), std::move(rotations),
|
||||
momentum_scale, angular_scale, std::move(hidden_second),
|
||||
std::move(hidden_second_scale)});
|
||||
std::move(hidden_second_scale), std::move(hidden_gradient)});
|
||||
}
|
||||
|
||||
Status CompleteEquilibrium(const Domain& domain, const DofManager& dofs,
|
||||
|
||||
Reference in New Issue
Block a user