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:
@@ -8,6 +8,8 @@ add_library(
|
||||
analysis/hht_time_integration.cpp
|
||||
analysis/hht_dynamic_algebra.cpp
|
||||
analysis/hht_dynamic_assembly.cpp
|
||||
analysis/hht_reduced_operators.cpp
|
||||
analysis/hht_reduced_procedure.cpp
|
||||
analysis/hht_dynamic_procedure.cpp
|
||||
analysis/linear_frequency_procedure.cpp
|
||||
analysis/linear_static_analysis.cpp
|
||||
|
||||
@@ -9,7 +9,7 @@ Analysis는 준비된 Domain을 procedure별로 실행하고 equation·backend·
|
||||
수행한다. Empty free를 singular로 오인하지 않는다.
|
||||
- Nonlinear/HHT는 trial을 committed state/history와 분리한다. 수렴·finite·recovery 검증을
|
||||
모두 마친 candidate만 반영하고 실패한 trial의 일부 값이 history에 남지 않게 한다.
|
||||
- HHT physical a, auxiliary A, 공간 angular acceleration을 구분하고 consistent kinetic
|
||||
- HHT physical a, auxiliary A, 공간 angular acceleration을 구분하고 승인된 lumped kinetic
|
||||
mass·analytic null·부분구속 의미를 유지한다. 시간수렴 실패를 기준 완화로 숨기지 않는다.
|
||||
- Frequency reduction은 null/finite 공간과 공개 mode 수·내부 검증 수를 구분한다.
|
||||
- 초기화, nonzero BC, all-constrained, 증분·반복 종료 경계, 실패 보존, temporal convergence,
|
||||
|
||||
@@ -190,6 +190,16 @@ Status DynamicAnalysisState::CommitAcceptedFrame(
|
||||
return Failure(FailureCategory::kSolver,
|
||||
"A default dynamic state cannot accept frames.");
|
||||
}
|
||||
if ((frame.time_integration_formulation !=
|
||||
DynamicTimeIntegrationFormulation::kFullCoordinateHht &&
|
||||
frame.time_integration_formulation !=
|
||||
DynamicTimeIntegrationFormulation::kReducedCoordinateHht) ||
|
||||
(!accepted_frames_.empty() &&
|
||||
frame.time_integration_formulation !=
|
||||
accepted_frames_.front().time_integration_formulation)) {
|
||||
return Failure(FailureCategory::kSolver,
|
||||
"Dynamic integration formulation is invalid or changed.");
|
||||
}
|
||||
const std::size_t expected_frame = accepted_frames_.size();
|
||||
if (expected_frame > (std::numeric_limits<std::uint32_t>::max)() ||
|
||||
frame.frame_index != expected_frame ||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define FESA_ANALYSIS_HHT_DYNAMIC_INTERNAL_H_
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/analysis/dynamic_analysis_state.h"
|
||||
@@ -28,6 +29,8 @@ struct RuntimeShell {
|
||||
struct DynamicAssembly {
|
||||
DynamicAcceptedFrameState frame;
|
||||
SparseMatrix mass;
|
||||
SparseMatrix position_tangent;
|
||||
SparseMatrix velocity_tangent;
|
||||
SparseMatrix internal_tangent;
|
||||
SparseMatrix effective_tangent;
|
||||
SparseMatrix drilling_metric;
|
||||
@@ -38,6 +41,8 @@ struct DynamicAssembly {
|
||||
double angular_momentum_rate_scale;
|
||||
Vector hidden_second{0U};
|
||||
Vector hidden_second_scale{0U};
|
||||
// Canonical scalar drilling-constraint derivative, indexed by global node.
|
||||
std::optional<SparseMatrix> hidden_gradient;
|
||||
};
|
||||
|
||||
/// @brief Builds checked shell evaluators in stable source order.
|
||||
@@ -59,6 +64,20 @@ Result<DynamicAssembly> EvaluateAssembly(
|
||||
const HhtParameters& parameters, const ParallelFor& parallel_for,
|
||||
bool directional_third);
|
||||
|
||||
/// @brief Evaluates an increment's initial iterate without committing history.
|
||||
/// @param frame New-time trial containing the last accepted coordinates.
|
||||
/// @param predictor Frozen HHT predictor used for both possible starting points.
|
||||
/// @param old_acceleration Last accepted physical coordinate acceleration.
|
||||
/// @return The valid initial assembly with smaller free ||D^T E||_infinity,
|
||||
/// preferring the predictor on a tie. Invalid predictor chart/geometry selects
|
||||
/// accepted coordinates; rate, mass and arithmetic failures remain failures.
|
||||
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, double time_increment,
|
||||
const HhtParameters& parameters, const ParallelFor& parallel_for);
|
||||
|
||||
/// @brief Builds deterministic CSR structure from an ordered local list.
|
||||
Result<SparseMatrix> MakeSparse(std::size_t rows, std::size_t columns,
|
||||
std::vector<CooContribution> contributions);
|
||||
@@ -77,6 +96,10 @@ std::vector<std::size_t> NullNodes(
|
||||
const std::vector<DynamicRotationKinematics>& rotations);
|
||||
|
||||
/// @brief Solves the positive-mass and differentiated-null initial equations.
|
||||
///
|
||||
/// The returned candidate is finite and exact-zero at constrained DOFs. Its
|
||||
/// complete free-equation N2-R certificate requires reassembly with that
|
||||
/// acceleration and the caller's source-uncancelled residual scale.
|
||||
Result<Vector> InitializeAcceleration(const Domain& domain,
|
||||
const DofManager& dofs,
|
||||
const DynamicAssembly& initial,
|
||||
|
||||
@@ -1,118 +1,8 @@
|
||||
#include "fesa/analysis/hht_dynamic_procedure.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "analysis/hht_dynamic_internal.h"
|
||||
#include "fesa/analysis/analysis_model.h"
|
||||
#include "fesa/analysis/newton_iteration_log.h"
|
||||
#include "fesa/solvers/linear/tangent_solver.h"
|
||||
#include "analysis/hht_reduced_procedure.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
using namespace hht_internal;
|
||||
|
||||
/// @brief Creates one procedure-owned diagnostic with the source step.
|
||||
Status Failure(const Domain& domain, const std::string& code,
|
||||
const std::string& message) {
|
||||
return Status::Failure(
|
||||
FailureCategory::kSolver,
|
||||
{{Severity::kError, code, domain.Dynamic()->dynamic_location, "DYNAMIC",
|
||||
domain.Dynamic()->source_step_name_raw, message}});
|
||||
}
|
||||
|
||||
/// @brief Checks one update against its uncancelled, dimensionally equal terms.
|
||||
bool UpdateEqual(const double left, const std::initializer_list<double> terms) {
|
||||
double right = 0.0;
|
||||
double scale = 0.0;
|
||||
for (const double term : terms) {
|
||||
right += term;
|
||||
scale += std::abs(term);
|
||||
}
|
||||
const double defect = std::abs(left - right);
|
||||
return std::isfinite(defect) && std::isfinite(scale) &&
|
||||
(scale == 0.0 ? defect == 0.0 : defect / scale <= 1e-12);
|
||||
}
|
||||
|
||||
/// @brief Verifies both absolute and increment forms before accepting history.
|
||||
Status CheckUpdates(const Domain& domain, const DynamicAcceptedFrameState& old,
|
||||
const DynamicAcceptedFrameState& frame,
|
||||
const HhtPredictor& predictor, const HhtParameters& p,
|
||||
const double h) {
|
||||
for (std::size_t i = 0U; i < frame.generalized_coordinates.Size(); ++i) {
|
||||
const double increment =
|
||||
frame.generalized_coordinates[i] - old.generalized_coordinates[i];
|
||||
const double old_term =
|
||||
h * h * (0.5 - p.beta) * old.algorithmic_acceleration[i];
|
||||
const double new_term = h * h * p.beta * frame.algorithmic_acceleration[i];
|
||||
if (!UpdateEqual(frame.generalized_coordinates[i],
|
||||
{old.generalized_coordinates[i], h * old.velocity[i],
|
||||
old_term, new_term}) ||
|
||||
!UpdateEqual(increment, {h * old.velocity[i], old_term, new_term}) ||
|
||||
!UpdateEqual(
|
||||
frame.generalized_coordinates[i] - predictor.coordinates[i],
|
||||
{new_term}) ||
|
||||
!UpdateEqual(frame.velocity[i],
|
||||
{old.velocity[i],
|
||||
h * (1 - p.gamma) * old.algorithmic_acceleration[i],
|
||||
h * p.gamma * frame.algorithmic_acceleration[i]}) ||
|
||||
!UpdateEqual(frame.algorithmic_acceleration[i],
|
||||
{(1 + p.alpha) * frame.acceleration[i],
|
||||
-p.alpha * old.acceleration[i]})) {
|
||||
return Failure(
|
||||
domain, "dynamic-update-inconsistency",
|
||||
"HHT endpoint update or resolved increment exceeds 1e-12.");
|
||||
}
|
||||
}
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
/// @brief Fills the raw channel evidence without hiding a zero denominator.
|
||||
void ResidualRecord(const DofManager& dofs,
|
||||
const DynamicAcceptedFrameState& frame,
|
||||
DynamicNewtonIterationLogRecord* record) {
|
||||
record->residual_numerator = {};
|
||||
record->residual_scale = {};
|
||||
for (const auto full : dofs.FreeDofs()) {
|
||||
const auto c = full % 6U < 3U ? 0U : 1U;
|
||||
record->residual_numerator[c] = (std::max)(record->residual_numerator[c],
|
||||
std::abs(frame.residual[full]));
|
||||
record->residual_scale[c] =
|
||||
(std::max)(record->residual_scale[c], frame.residual_scale[full]);
|
||||
}
|
||||
record->residual_pass = frame.free_residual_metrics[0U] <= 1e-10 &&
|
||||
frame.free_residual_metrics[1U] <= 1e-10;
|
||||
}
|
||||
|
||||
/// @brief Forms the four coordinate/rate correction checks in scaled units.
|
||||
void CorrectionRecord(const Vector& correction, const DofManager& dofs,
|
||||
const double length, const HhtParameters& p,
|
||||
DynamicNewtonIterationLogRecord* record) {
|
||||
record->coordinate_correction = {};
|
||||
for (const auto full : dofs.FreeDofs()) {
|
||||
const auto c = full % 6U < 3U ? 0U : 1U;
|
||||
record->coordinate_correction[c] =
|
||||
(std::max)(record->coordinate_correction[c],
|
||||
std::abs(correction[full]) / (c == 0U ? length : 1.0));
|
||||
}
|
||||
record->correction_pass = true;
|
||||
for (std::size_t c = 0; c < 2U; ++c) {
|
||||
const double dq = record->coordinate_correction[c];
|
||||
record->velocity_correction[c] = p.gamma / p.beta * dq;
|
||||
record->acceleration_correction[c] = dq / ((1 + p.alpha) * p.beta);
|
||||
record->algorithmic_correction[c] = dq / p.beta;
|
||||
for (const auto* channel :
|
||||
{&record->coordinate_correction, &record->velocity_correction,
|
||||
&record->acceleration_correction, &record->algorithmic_correction})
|
||||
record->correction_pass =
|
||||
record->correction_pass && (*channel)[c] <= 1e-10;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
HhtDynamicProcedureExecutor::HhtDynamicProcedureExecutor(
|
||||
const ParallelFor& parallel_for, LinearSolver& positive_solver,
|
||||
TangentSolver& tangent_solver)
|
||||
@@ -122,201 +12,7 @@ HhtDynamicProcedureExecutor::HhtDynamicProcedureExecutor(
|
||||
|
||||
Result<DynamicExecutionResult> HhtDynamicProcedureExecutor::Execute(
|
||||
const Domain& domain, NewtonIterationLogWriter& log) const {
|
||||
if (domain.Dynamic() == nullptr) {
|
||||
return Result<DynamicExecutionResult>::Failure(Status::Failure(
|
||||
FailureCategory::kModel, {{Severity::kError,
|
||||
"invalid-dynamic-step",
|
||||
{},
|
||||
"DYNAMIC",
|
||||
{},
|
||||
"HHT requires exactly one dynamic step."}}));
|
||||
}
|
||||
auto model = AnalysisModel::Create(domain);
|
||||
if (!model.HasValue())
|
||||
return Result<DynamicExecutionResult>::Failure(model.GetStatus());
|
||||
auto dofs_result = DofManager::Create(model.Value());
|
||||
if (!dofs_result.HasValue())
|
||||
return Result<DynamicExecutionResult>::Failure(dofs_result.GetStatus());
|
||||
const auto& dofs = dofs_result.Value();
|
||||
auto state_result = DynamicAnalysisState::Create(domain, dofs);
|
||||
if (!state_result.HasValue())
|
||||
return Result<DynamicExecutionResult>::Failure(state_result.GetStatus());
|
||||
auto state = std::move(state_result.Value());
|
||||
auto shells = BuildRuntimeShells(domain, dofs);
|
||||
if (!shells.HasValue())
|
||||
return Result<DynamicExecutionResult>::Failure(shells.GetStatus());
|
||||
auto length_result = ReferenceLength(domain);
|
||||
if (!length_result.HasValue())
|
||||
return Result<DynamicExecutionResult>::Failure(length_result.GetStatus());
|
||||
const double length = length_result.Value();
|
||||
auto loads = AssembleLoads(domain, dofs);
|
||||
if (!loads.HasValue())
|
||||
return Result<DynamicExecutionResult>::Failure(loads.GetStatus());
|
||||
auto parameters = MakeHhtParameters(domain.Dynamic()->alpha);
|
||||
if (!parameters.HasValue())
|
||||
return Result<DynamicExecutionResult>::Failure(parameters.GetStatus());
|
||||
const auto p = parameters.Value();
|
||||
const double h = domain.Dynamic()->initial_increment;
|
||||
DynamicAcceptedFrameState frame{};
|
||||
frame.is_initial = true;
|
||||
for (auto* vector : {&frame.generalized_coordinates, &frame.velocity,
|
||||
&frame.acceleration, &frame.algorithmic_acceleration})
|
||||
*vector = Vector(dofs.FullDofCount());
|
||||
frame.external_force = loads.Value()[0U];
|
||||
auto initial = EvaluateAssembly(domain, dofs, shells.Value(), frame,
|
||||
loads.Value()[1U], h, p, parallel_for_, true);
|
||||
if (!initial.HasValue())
|
||||
return Result<DynamicExecutionResult>::Failure(initial.GetStatus());
|
||||
const auto null_nodes = NullNodes(dofs, initial.Value().rotations);
|
||||
auto acceleration = InitializeAcceleration(
|
||||
domain, dofs, initial.Value(), length, positive_solver_, tangent_solver_);
|
||||
if (!acceleration.HasValue())
|
||||
return Result<DynamicExecutionResult>::Failure(acceleration.GetStatus());
|
||||
frame.acceleration = acceleration.Value();
|
||||
frame.algorithmic_acceleration = acceleration.Value();
|
||||
initial = EvaluateAssembly(domain, dofs, shells.Value(), frame,
|
||||
loads.Value()[1U], h, p, parallel_for_, true);
|
||||
if (!initial.HasValue())
|
||||
return Result<DynamicExecutionResult>::Failure(initial.GetStatus());
|
||||
frame = initial.Value().frame;
|
||||
Status status = CompleteConstraintEvidence(
|
||||
domain, dofs, initial.Value(), length, null_nodes, positive_solver_,
|
||||
tangent_solver_, &frame);
|
||||
if (!status.IsOk()) return Result<DynamicExecutionResult>::Failure(status);
|
||||
status = CompleteEquilibrium(domain, dofs, initial.Value(), &frame);
|
||||
if (!status.IsOk()) return Result<DynamicExecutionResult>::Failure(status);
|
||||
const double initial_energy = frame.kinetic_energy +
|
||||
frame.physical_strain_energy +
|
||||
frame.drilling_energy;
|
||||
status = state.CommitAcceptedFrame(frame);
|
||||
if (!status.IsOk()) return Result<DynamicExecutionResult>::Failure(status);
|
||||
DynamicNewtonIterationLogRecord record{};
|
||||
record.stage = "accepted";
|
||||
record.outcome = "initial";
|
||||
record.correction_pass = true;
|
||||
ResidualRecord(dofs, frame, &record);
|
||||
status = log.Write(record);
|
||||
if (status.IsOk()) status = log.Flush();
|
||||
if (!status.IsOk()) return Result<DynamicExecutionResult>::Failure(status);
|
||||
for (std::uint32_t increment = 1U;
|
||||
increment <= domain.Dynamic()->increment_count; ++increment) {
|
||||
const auto& old = state.CommittedState();
|
||||
auto predictor = PredictHht(old.generalized_coordinates, old.velocity,
|
||||
old.algorithmic_acceleration, h, p);
|
||||
if (!predictor.HasValue())
|
||||
return Result<DynamicExecutionResult>::Failure(predictor.GetStatus());
|
||||
frame = old;
|
||||
frame.is_initial = false;
|
||||
frame.frame_index = increment;
|
||||
frame.increment_index = increment;
|
||||
frame.time = static_cast<double>(increment) * h;
|
||||
// A large DIRECT step may put a kinematic predictor outside the shell
|
||||
// chart before Newton starts. The last accepted geometry is a valid
|
||||
// initial iterate; q_p still defines the unchanged HHT rate equations.
|
||||
frame.generalized_coordinates = old.generalized_coordinates;
|
||||
record = {};
|
||||
record.increment_index = increment;
|
||||
record.time = frame.time;
|
||||
record.correction_pass = true;
|
||||
bool accepted = false;
|
||||
for (std::uint32_t iteration = 0U; iteration <= 100U; ++iteration) {
|
||||
record.iteration_index = iteration;
|
||||
record.stage = "evaluation";
|
||||
record.outcome = "candidate";
|
||||
auto fail = [&](const Status& failure) -> Result<DynamicExecutionResult> {
|
||||
record.stage = "failed";
|
||||
record.outcome = "failed";
|
||||
record.failure_code = failure.Diagnostics().empty()
|
||||
? "dynamic-solver-failure"
|
||||
: failure.Diagnostics().front().code;
|
||||
const Status written = log.Write(record);
|
||||
return Result<DynamicExecutionResult>::Failure(
|
||||
written.IsOk() ? failure : written);
|
||||
};
|
||||
auto rates = EvaluateHhtRates(frame.generalized_coordinates,
|
||||
predictor.Value(), old.acceleration, h, p);
|
||||
if (!rates.HasValue()) return fail(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 = iteration;
|
||||
auto assembly =
|
||||
EvaluateAssembly(domain, dofs, shells.Value(), frame,
|
||||
loads.Value()[1U], h, p, parallel_for_, false);
|
||||
if (!assembly.HasValue()) return fail(assembly.GetStatus());
|
||||
frame = assembly.Value().frame;
|
||||
ResidualRecord(dofs, frame, &record);
|
||||
if (record.residual_pass && record.correction_pass) {
|
||||
assembly =
|
||||
EvaluateAssembly(domain, dofs, shells.Value(), frame,
|
||||
loads.Value()[1U], h, p, parallel_for_, true);
|
||||
if (!assembly.HasValue()) return fail(assembly.GetStatus());
|
||||
frame = assembly.Value().frame;
|
||||
status = CompleteConstraintEvidence(
|
||||
domain, dofs, assembly.Value(), length, null_nodes,
|
||||
positive_solver_, tangent_solver_, &frame);
|
||||
if (!status.IsOk()) return fail(status);
|
||||
status = CompleteEquilibrium(domain, dofs, assembly.Value(), &frame);
|
||||
if (!status.IsOk()) return fail(status);
|
||||
status = CheckUpdates(domain, old, frame, predictor.Value(), p, h);
|
||||
if (!status.IsOk()) return fail(status);
|
||||
frame.external_work = 0.0;
|
||||
for (std::size_t i = 0; i < dofs.FullDofCount(); ++i)
|
||||
frame.external_work +=
|
||||
frame.external_force[i] * frame.generalized_coordinates[i];
|
||||
frame.energy_defect =
|
||||
frame.kinetic_energy + frame.physical_strain_energy +
|
||||
frame.drilling_energy - initial_energy - frame.external_work;
|
||||
status = state.CommitAcceptedFrame(frame);
|
||||
if (!status.IsOk()) return fail(status);
|
||||
record.stage = "accepted";
|
||||
record.outcome = "accepted";
|
||||
status = log.Write(record);
|
||||
if (status.IsOk()) status = log.Flush();
|
||||
if (!status.IsOk())
|
||||
return Result<DynamicExecutionResult>::Failure(status);
|
||||
accepted = true;
|
||||
break;
|
||||
}
|
||||
if (iteration == 100U)
|
||||
return fail(Failure(domain, "dynamic-nonconvergence",
|
||||
"HHT exhausted 100 corrections without satisfying "
|
||||
"every endpoint check."));
|
||||
status = log.Write(record);
|
||||
if (!status.IsOk())
|
||||
return Result<DynamicExecutionResult>::Failure(status);
|
||||
auto tangent =
|
||||
ScaledFree(assembly.Value().effective_tangent, dofs, length);
|
||||
if (!tangent.HasValue()) return fail(tangent.GetStatus());
|
||||
Vector rhs(dofs.FreeDofCount());
|
||||
Vector solution(dofs.FreeDofCount());
|
||||
Vector correction(dofs.FullDofCount());
|
||||
for (std::size_t i = 0; i < dofs.FreeDofCount(); ++i) {
|
||||
const auto full = dofs.FreeDofs()[i];
|
||||
rhs[i] = -frame.residual[full] * (full % 6U < 3U ? length : 1.0);
|
||||
}
|
||||
if (dofs.FreeDofCount() > 0U) {
|
||||
status = tangent_solver_.FactorizeFresh(tangent.Value());
|
||||
if (!status.IsOk()) return fail(status);
|
||||
status = tangent_solver_.SolveCorrection(rhs, solution);
|
||||
if (!status.IsOk()) return fail(status);
|
||||
status = CheckLinearSolve(tangent.Value(), rhs, solution);
|
||||
if (!status.IsOk()) return fail(status);
|
||||
}
|
||||
for (std::size_t i = 0; i < dofs.FreeDofCount(); ++i) {
|
||||
const auto full = dofs.FreeDofs()[i];
|
||||
correction[full] = solution[i] * (full % 6U < 3U ? length : 1.0);
|
||||
frame.generalized_coordinates[full] += correction[full];
|
||||
}
|
||||
CorrectionRecord(correction, dofs, length, p, &record);
|
||||
}
|
||||
if (!accepted)
|
||||
return Result<DynamicExecutionResult>::Failure(
|
||||
Failure(domain, "dynamic-nonconvergence",
|
||||
"Dynamic increment was not accepted."));
|
||||
}
|
||||
return Result<DynamicExecutionResult>::Success(
|
||||
{std::move(state), domain.Warnings()});
|
||||
return ExecuteReducedHht(domain, log, parallel_for_, positive_solver_,
|
||||
tangent_solver_);
|
||||
}
|
||||
} // namespace fesa
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
#ifndef FESA_ANALYSIS_HHT_REDUCED_INCREMENT_H_
|
||||
#define FESA_ANALYSIS_HHT_REDUCED_INCREMENT_H_
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <initializer_list>
|
||||
#include <limits>
|
||||
|
||||
#include "math/high_low_arithmetic.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace hht_reduced_internal {
|
||||
|
||||
struct PairIncrementEvidence {
|
||||
bool arithmetic_pass = false;
|
||||
bool storage_pass = false;
|
||||
double defect = std::numeric_limits<double>::infinity();
|
||||
double ratio = std::numeric_limits<double>::infinity();
|
||||
};
|
||||
|
||||
// Grow an error-free expansion of the actual binary64 inputs. No requested
|
||||
// trace is copied into the candidate. Eight components cover hi/lo/old and
|
||||
// the three original HHT right-hand terms, without an allocation.
|
||||
inline PairIncrementEvidence CheckPairIncrement(
|
||||
const double high, const double low, const double old_high,
|
||||
const std::initializer_list<double> right_terms, const double scale) {
|
||||
PairIncrementEvidence evidence;
|
||||
if (!math_internal::ArithmeticEnvironmentSupported() || !std::isfinite(high) ||
|
||||
!std::isfinite(low) || !std::isfinite(old_high) ||
|
||||
!std::isfinite(scale) || scale < 0.0 || right_terms.size() > 3U)
|
||||
return evidence;
|
||||
for (const double term : right_terms)
|
||||
if (!std::isfinite(term)) return evidence;
|
||||
// This independent addition checks RN-even storage of the exact pair,
|
||||
// including a midpoint whose parity must choose the existing high.
|
||||
const double stored = high + low;
|
||||
evidence.storage_pass = std::isfinite(stored) && stored == high;
|
||||
std::array<double, 8U> expansion{};
|
||||
std::size_t size = 0U;
|
||||
const auto add = [&](const double value) {
|
||||
std::array<double, 8U> next{};
|
||||
std::size_t count = 0U;
|
||||
double carry = value;
|
||||
for (std::size_t i = 0U; i < size; ++i) {
|
||||
const double sum = carry + expansion[i];
|
||||
const double virtual_right = sum - carry;
|
||||
const double error = (carry - (sum - virtual_right)) +
|
||||
(expansion[i] - virtual_right);
|
||||
if (!std::isfinite(sum) || !std::isfinite(error)) return false;
|
||||
if (error != 0.0) next[count++] = error;
|
||||
carry = sum;
|
||||
}
|
||||
if (carry != 0.0 || count == 0U) next[count++] = carry;
|
||||
expansion = next;
|
||||
size = count;
|
||||
return true;
|
||||
};
|
||||
if (!add(high) || !add(low) || !add(-old_high)) return evidence;
|
||||
for (const double term : right_terms)
|
||||
if (!add(-term)) return evidence;
|
||||
bool exact_zero = true;
|
||||
double difference = 0.0;
|
||||
for (std::size_t i = 0U; i < size; ++i) {
|
||||
exact_zero = exact_zero && expansion[i] == 0.0;
|
||||
difference += expansion[i];
|
||||
}
|
||||
evidence.defect = std::abs(difference);
|
||||
if (!std::isfinite(evidence.defect)) return evidence;
|
||||
if (scale == 0.0) {
|
||||
evidence.ratio = exact_zero ? 0.0 : std::numeric_limits<double>::infinity();
|
||||
evidence.arithmetic_pass = exact_zero;
|
||||
} else {
|
||||
evidence.ratio = evidence.defect / scale;
|
||||
if (exact_zero) {
|
||||
evidence.arithmetic_pass = true;
|
||||
return evidence;
|
||||
}
|
||||
// The rounded ratio is diagnostic only. Compare the exact EFT defect
|
||||
// against the exact dyadic tolerance product; an unsupported underflow
|
||||
// tail fails closed rather than becoming an enlarged rounded threshold.
|
||||
const auto threshold = math_internal::TwoProduct(1.0e-12, scale);
|
||||
if (!math_internal::IsFinite(threshold)) return evidence;
|
||||
if (expansion[size - 1U] < 0.0)
|
||||
for (std::size_t i = 0U; i < size; ++i) expansion[i] = -expansion[i];
|
||||
if (!add(-threshold.high) || !add(-threshold.low)) return evidence;
|
||||
evidence.arithmetic_pass = expansion[size - 1U] <= 0.0;
|
||||
}
|
||||
return evidence;
|
||||
}
|
||||
|
||||
} // namespace hht_reduced_internal
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_ANALYSIS_HHT_REDUCED_INCREMENT_H_
|
||||
@@ -0,0 +1,247 @@
|
||||
#ifndef FESA_ANALYSIS_HHT_REDUCED_INTERNAL_H_
|
||||
#define FESA_ANALYSIS_HHT_REDUCED_INTERNAL_H_
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "analysis/hht_dynamic_internal.h"
|
||||
#include "fesa/core/status.h"
|
||||
#include "fesa/math/matrix.h"
|
||||
#include "fesa/math/vector.h"
|
||||
|
||||
namespace fesa {
|
||||
class TangentSolver;
|
||||
|
||||
namespace hht_reduced_internal {
|
||||
|
||||
/// @brief One canonical nonzero sparse coefficient in full coordinates.
|
||||
struct ConstraintEntry {
|
||||
std::size_t dof = 0U;
|
||||
double value = 0.0;
|
||||
};
|
||||
|
||||
/// @brief One immutable private coordinate and its full-space injection.
|
||||
struct FixedBasisColumn {
|
||||
std::size_t node = 0U;
|
||||
std::size_t channel = 0U; // Translation=0, angular=1.
|
||||
bool fixed_plane = false;
|
||||
std::vector<ConstraintEntry> support;
|
||||
std::vector<double> support_radii;
|
||||
};
|
||||
|
||||
/// @brief Fixed initial-coordinate split q_f=L_p p+L_z z for one HHT run.
|
||||
/// @note A node whose constrained rotation cuts its analytic null is retained
|
||||
/// entirely in p. The split is immutable; chart/rank changes are failures.
|
||||
struct FixedChart {
|
||||
std::size_t full_dof_count = 0U;
|
||||
std::vector<std::size_t> p_dofs;
|
||||
std::vector<std::size_t> z_dofs;
|
||||
std::vector<std::size_t> null_nodes;
|
||||
std::vector<std::size_t> pivot_components;
|
||||
// p_dofs/z_dofs are canonical labels, not injection indices.
|
||||
std::vector<FixedBasisColumn> p_columns;
|
||||
std::vector<FixedBasisColumn> z_columns;
|
||||
std::vector<std::array<double, 3U>> initial_nulls;
|
||||
std::vector<std::size_t> minimum_axes; // 3 for component charts.
|
||||
};
|
||||
|
||||
/// @brief One canonical nonzero Hessian entry for a scalar constraint.
|
||||
struct ConstraintHessianEntry {
|
||||
std::size_t row = 0U;
|
||||
std::size_t column = 0U;
|
||||
double value = 0.0;
|
||||
};
|
||||
|
||||
/// @brief Exact sparse derivatives of one nodal drilling constraint.
|
||||
/// @note Element-local 24-by-24 contributions are reduced in source/scatter
|
||||
/// order. This deliberately avoids a null-count by full-DOF squared tensor.
|
||||
struct ConstraintRowOperators {
|
||||
double value = 0.0;
|
||||
/// @brief Existing N3 uncancelled position-constraint scale.
|
||||
double value_scale = 0.0;
|
||||
double time_first = 0.0;
|
||||
double time_second = 0.0;
|
||||
std::vector<ConstraintEntry> gradient;
|
||||
std::vector<ConstraintEntry> time_gradient;
|
||||
std::vector<ConstraintEntry> third_directional_gradient;
|
||||
std::vector<ConstraintHessianEntry> hessian;
|
||||
};
|
||||
|
||||
/// @brief Exact sparse nodal drilling operators in chart-null order.
|
||||
struct ConstraintOperators {
|
||||
std::vector<ConstraintRowOperators> rows;
|
||||
};
|
||||
|
||||
/// @brief Accepted-branch graph result. `operators` belongs exactly to q.
|
||||
struct EmbeddingRoot {
|
||||
Vector p{0U};
|
||||
Vector z{0U};
|
||||
Vector q{0U};
|
||||
ConstraintOperators operators;
|
||||
double graph_residual_infinity = 0.0;
|
||||
double graph_residual_relative = 0.0;
|
||||
double graph_correction_infinity = 0.0;
|
||||
std::size_t graph_iteration_count = 0U;
|
||||
};
|
||||
|
||||
/// @brief First implicit graph derivatives at one converged embedding root.
|
||||
struct EmbeddingFirstDerivatives {
|
||||
Matrix jacobian{0U, 0U};
|
||||
Vector time_derivative{0U};
|
||||
};
|
||||
|
||||
/// @brief Full physical rates reconstructed from private reduced coordinates.
|
||||
struct ReconstructedRates {
|
||||
Vector velocity{0U};
|
||||
Vector acceleration{0U};
|
||||
Vector z_velocity{0U};
|
||||
Vector z_acceleration{0U};
|
||||
};
|
||||
|
||||
/// @brief Private equation ordering for one exact augmented reduced Newton step.
|
||||
/// The four blocks are `(delta p, delta z, delta vz, delta az)`; no public
|
||||
/// equation numbers or result fields use this ordering.
|
||||
struct AugmentedReducedSystem {
|
||||
SparseMatrix tangent;
|
||||
Vector rhs{0U};
|
||||
std::size_t physical_count = 0U;
|
||||
std::size_t algebraic_count = 0U;
|
||||
Vector row_scale{0U};
|
||||
Vector unknown_scale{0U};
|
||||
};
|
||||
|
||||
/// @brief One augmented solve mapped back to full chart correction channels.
|
||||
/// Only `p` is an integrator correction; z/vz/az are same-trial auxiliary
|
||||
/// variables for diagnostics and exact full q/v/a correction recovery.
|
||||
struct AugmentedCorrection {
|
||||
Vector p{0U};
|
||||
Vector z{0U};
|
||||
Vector z_velocity{0U};
|
||||
Vector z_acceleration{0U};
|
||||
Vector coordinate{0U};
|
||||
Vector velocity{0U};
|
||||
Vector acceleration{0U};
|
||||
};
|
||||
|
||||
/// @brief Actual Newton operator, retaining its indices in the full system.
|
||||
struct AugmentedSolveSelection {
|
||||
SparseMatrix tangent;
|
||||
Vector rhs{0U};
|
||||
std::vector<std::size_t> full_indices;
|
||||
bool angular_only = false;
|
||||
};
|
||||
|
||||
/// @brief Approved translation-pass/rotation-residual-fail solve policy.
|
||||
bool ShouldRefineAngular(
|
||||
const FixedChart& chart, const std::array<double, 2U>& residual_metrics,
|
||||
const std::array<double, 4U>& translation_corrections,
|
||||
bool translation_private_algorithmic_pass);
|
||||
|
||||
/// @brief Selects angular p and all auxiliary blocks, or the full operator.
|
||||
Result<AugmentedSolveSelection> SelectAugmentedSolve(
|
||||
const AugmentedReducedSystem& full, const FixedChart& chart,
|
||||
bool angular_only);
|
||||
|
||||
/// @brief Zero-embeds an actual selected solve into the full augmented order.
|
||||
Result<Vector> EmbedAugmentedSolution(
|
||||
const AugmentedSolveSelection& selected, const Vector& solution,
|
||||
std::size_t full_size);
|
||||
|
||||
/// @brief Evaluates one position-only graph trial without mutating history.
|
||||
using ConstraintEvaluator =
|
||||
std::function<Result<ConstraintOperators>(const Vector&)>;
|
||||
|
||||
/// @brief Builds the fixed p/z graph selectors from frame-zero rotations.
|
||||
Result<FixedChart> BuildFixedChart(
|
||||
const DofManager& dofs,
|
||||
const std::vector<DynamicRotationKinematics>& rotations);
|
||||
|
||||
/// @brief Lifts selector coordinates, retaining exact zero prescribed values.
|
||||
Result<Vector> Lift(const FixedChart& chart, const Vector& p, const Vector& z);
|
||||
|
||||
/// @brief Applies the fixed sparse transpose without reinterpreting labels.
|
||||
Result<Vector> ProjectPhysical(const FixedChart& chart, const Vector& full);
|
||||
Result<Vector> ProjectAlgebraic(const FixedChart& chart, const Vector& full);
|
||||
|
||||
/// @brief Returns the explicit translation/angular channel for a p column.
|
||||
std::size_t PhysicalCoordinateChannel(const FixedChart& chart,
|
||||
std::size_t coordinate);
|
||||
|
||||
/// @brief Solves the coupled fixed graph psi(L_p p+L_z z,t)=0.
|
||||
/// @note `seed` is caller-owned accepted branch data; only the returned root
|
||||
/// may be committed. No nodewise solve, pivot change, or full-coordinate
|
||||
/// fallback is performed here.
|
||||
Result<EmbeddingRoot> SolveEmbeddingRoot(const FixedChart& chart,
|
||||
const Vector& p, const Vector& seed,
|
||||
const ConstraintEvaluator& evaluate,
|
||||
TangentSolver& solver);
|
||||
|
||||
/// @brief Builds Q_p and Q_t from RC-07 using the root's G factorization.
|
||||
Result<EmbeddingFirstDerivatives> EvaluateEmbeddingFirstDerivatives(
|
||||
const FixedChart& chart, const EmbeddingRoot& root, TangentSolver& solver);
|
||||
|
||||
/// @brief Applies RC-07/08 directly without forming a production dense J.
|
||||
Result<ReconstructedRates> ReconstructPhysicalRates(
|
||||
const FixedChart& chart, const EmbeddingRoot& root, const Vector& vp,
|
||||
const Vector& ap, TangentSolver& solver);
|
||||
|
||||
/// @brief Returns H_i[left,right] for all scalar drilling constraints.
|
||||
Vector ContractConstraintHessian(const ConstraintOperators& operators,
|
||||
const Vector& left, const Vector& right);
|
||||
|
||||
/// @brief Forms RC-10/11 without materializing Q_p or a dense reduced tangent.
|
||||
/// @param velocity_rate Coefficient gamma/(beta*h).
|
||||
/// @param acceleration_rate Coefficient 1/((1+alpha)*beta*h*h).
|
||||
/// @note `assembly` must retain separate DqI+Kint, DvI and M operators.
|
||||
Result<AugmentedReducedSystem> AssembleAugmentedReducedSystem(
|
||||
const hht_internal::DynamicAssembly& assembly, const FixedChart& chart,
|
||||
const ConstraintOperators& operators, double velocity_rate,
|
||||
double acceleration_rate, double reference_length,
|
||||
double time_increment);
|
||||
|
||||
/// @brief Decodes all augmented blocks using the RC selector substitutions.
|
||||
Result<AugmentedCorrection> MapAugmentedCorrection(
|
||||
const FixedChart& chart, const AugmentedReducedSystem& system,
|
||||
const Vector& solution, double velocity_rate, double acceleration_rate);
|
||||
|
||||
/// @brief Scatters exact shell drilling scalar operators in canonical order.
|
||||
/// @param assembly Current full assembly at q/v/a; its rotation inventory is
|
||||
/// checked against `chart` before any operator is returned.
|
||||
/// @param force_rate, force_acceleration Private manufactured-load derivatives.
|
||||
/// Public constant CLOAD callers pass zero vectors.
|
||||
Result<ConstraintOperators> EvaluateConstraintOperators(
|
||||
const Domain& domain, const DofManager& dofs,
|
||||
const std::vector<hht_internal::RuntimeShell>& shells,
|
||||
const hht_internal::DynamicAssembly& assembly, const FixedChart& chart,
|
||||
const Vector& force_rate, const Vector& force_acceleration);
|
||||
|
||||
/// @brief Solves G x=rhs with the existing nonsymmetric tangent backend.
|
||||
/// The factorization is checked by the same backward-error rule as HHT.
|
||||
Result<Vector> SolveGraphSystem(const Matrix& matrix, const Vector& rhs,
|
||||
TangentSolver& solver);
|
||||
|
||||
/// @brief Certifies W^-1(psi_q L_z)W^-1 with the existing 2^-32 policy.
|
||||
/// @return Reciprocal infinity condition; empty charts return exact zero.
|
||||
Result<double> CheckGraphRegularity(
|
||||
const hht_internal::DynamicAssembly& assembly, const FixedChart& chart,
|
||||
const ConstraintOperators& operators, TangentSolver& solver);
|
||||
|
||||
/// @brief Checks the normalized full-root spanning operator separately from G.
|
||||
Result<double> CheckSelectorTransversality(
|
||||
const hht_internal::DynamicAssembly& assembly, const FixedChart& chart,
|
||||
TangentSolver& solver);
|
||||
|
||||
/// @brief Forms G=psi_q L_z without building a dense full selector.
|
||||
Matrix GraphJacobian(const ConstraintOperators& operators,
|
||||
const FixedChart& chart);
|
||||
|
||||
/// @brief Forms psi_q L_p, used by RC-07 graph derivative solves.
|
||||
Matrix PhysicalJacobian(const ConstraintOperators& operators,
|
||||
const FixedChart& chart);
|
||||
|
||||
} // namespace hht_reduced_internal
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_ANALYSIS_HHT_REDUCED_INTERNAL_H_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,946 @@
|
||||
#include "analysis/hht_reduced_procedure.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "analysis/hht_dynamic_internal.h"
|
||||
#include "analysis/hht_reduced_increment.h"
|
||||
#include "analysis/hht_reduced_internal.h"
|
||||
#include "fesa/analysis/analysis_model.h"
|
||||
#include "fesa/analysis/newton_iteration_log.h"
|
||||
#include "fesa/solvers/linear/tangent_solver.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
using namespace hht_internal;
|
||||
using namespace hht_reduced_internal;
|
||||
|
||||
Status Failure(const Domain& domain, const std::string& code,
|
||||
const std::string& message) {
|
||||
return Status::Failure(
|
||||
FailureCategory::kSolver,
|
||||
{{Severity::kError, code, domain.Dynamic()->dynamic_location, "DYNAMIC",
|
||||
domain.Dynamic()->source_step_name_raw, message}});
|
||||
}
|
||||
|
||||
Status WithProcedureContext(const Domain& domain, const Status& status,
|
||||
const char* stage, const std::uint32_t increment,
|
||||
const double time) {
|
||||
std::vector<Diagnostic> diagnostics = status.Diagnostics();
|
||||
std::ostringstream message;
|
||||
message << "stage=" << stage << ", step="
|
||||
<< domain.Dynamic()->source_step_name_raw << ", increment="
|
||||
<< increment << ", time=" << time << ".";
|
||||
diagnostics.push_back({Severity::kError, "dynamic-procedure-context",
|
||||
domain.Dynamic()->dynamic_location, "DYNAMIC",
|
||||
domain.Dynamic()->source_step_name_raw,
|
||||
message.str()});
|
||||
if (const auto category = status.Category(); category.has_value())
|
||||
return Status::Failure(*category, std::move(diagnostics));
|
||||
return Status::Failure(std::move(diagnostics));
|
||||
}
|
||||
|
||||
bool UpdateEqual(const double left, const std::initializer_list<double> terms) {
|
||||
double right = 0.0;
|
||||
double scale = 0.0;
|
||||
for (const double term : terms) {
|
||||
right += term;
|
||||
scale += std::abs(term);
|
||||
}
|
||||
const double defect = std::abs(left - right);
|
||||
return std::isfinite(defect) && std::isfinite(scale) &&
|
||||
(scale == 0.0 ? defect == 0.0 : defect / scale <= 1e-12);
|
||||
}
|
||||
|
||||
bool PrivateCorrectionEqual(const FixedChart& chart, const std::size_t coordinate,
|
||||
const Vector& full_algorithmic,
|
||||
const Vector& private_algorithmic,
|
||||
const Vector& private_physical, const double alpha) {
|
||||
if (!UpdateEqual((1.0 + alpha) * private_physical[coordinate],
|
||||
{private_algorithmic[coordinate]})) return false;
|
||||
const auto& column = chart.p_columns[coordinate];
|
||||
if (!column.fixed_plane &&
|
||||
!UpdateEqual(full_algorithmic[column.support[0U].dof],
|
||||
{private_algorithmic[coordinate]})) return false;
|
||||
double sum = 0.0;
|
||||
double compensation = 0.0;
|
||||
double scale = std::abs(private_algorithmic[coordinate]);
|
||||
for (const auto& entry : column.support) {
|
||||
const double term = entry.value * full_algorithmic[entry.dof];
|
||||
const double next = sum + term;
|
||||
compensation += std::abs(sum) >= std::abs(term)
|
||||
? (sum - next) + term : (term - next) + sum;
|
||||
sum = next;
|
||||
scale += std::abs(term);
|
||||
}
|
||||
const double defect = std::abs((sum + compensation) - private_algorithmic[coordinate]);
|
||||
return std::isfinite(scale) && std::isfinite(defect) &&
|
||||
(scale == 0.0 ? defect == 0.0 : defect / scale <= 1e-12);
|
||||
}
|
||||
|
||||
double AdditionLowPart(const double left, const double right, const double sum) {
|
||||
const double right_virtual = sum - left;
|
||||
return (left - (sum - right_virtual)) + (right - right_virtual);
|
||||
}
|
||||
|
||||
void AdvanceCoordinate(const double delta, double* high, double* low) {
|
||||
const double sum = *high + delta;
|
||||
const double error = AdditionLowPart(*high, delta, sum);
|
||||
const double low_sum = *low + error;
|
||||
*high = sum + low_sum;
|
||||
*low = AdditionLowPart(sum, low_sum, *high);
|
||||
}
|
||||
|
||||
void AddIncrementTraceTerm(const double value, double* sum,
|
||||
double* compensation) {
|
||||
const double next = *sum + value;
|
||||
if (std::abs(*sum) >= std::abs(value))
|
||||
*compensation += (*sum - next) + value;
|
||||
else
|
||||
*compensation += (value - next) + *sum;
|
||||
*sum = next;
|
||||
}
|
||||
|
||||
bool GlobalBasisEqual(const FixedChart& chart, const Vector& full,
|
||||
const Vector& p, const Vector& z) {
|
||||
Vector sums{chart.full_dof_count};
|
||||
Vector compensations{chart.full_dof_count};
|
||||
Vector scales{chart.full_dof_count};
|
||||
const auto add = [&](const std::vector<FixedBasisColumn>& columns,
|
||||
const Vector& coordinates) {
|
||||
for (std::size_t i = 0U; i < coordinates.Size(); ++i)
|
||||
for (const auto& entry : columns[i].support) {
|
||||
const double term = entry.value * coordinates[i];
|
||||
AddIncrementTraceTerm(term, &sums[entry.dof], &compensations[entry.dof]);
|
||||
scales[entry.dof] += std::abs(term);
|
||||
}
|
||||
};
|
||||
add(chart.p_columns, p);
|
||||
add(chart.z_columns, z);
|
||||
for (std::size_t i = 0U; i < full.Size(); ++i) {
|
||||
const double defect = std::abs(full[i] - (sums[i] + compensations[i]));
|
||||
if (!std::isfinite(defect) || !std::isfinite(scales[i]) ||
|
||||
(scales[i] == 0.0 ? defect != 0.0 : defect / scales[i] > 1e-12)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ResidualRecord(const DofManager& dofs,
|
||||
const DynamicAcceptedFrameState& frame,
|
||||
DynamicNewtonIterationLogRecord* record) {
|
||||
record->residual_numerator = {};
|
||||
record->residual_scale = {};
|
||||
for (const auto full : dofs.FreeDofs()) {
|
||||
const auto channel = full % 6U < 3U ? 0U : 1U;
|
||||
record->residual_numerator[channel] =
|
||||
(std::max)(record->residual_numerator[channel],
|
||||
std::abs(frame.residual[full]));
|
||||
record->residual_scale[channel] =
|
||||
(std::max)(record->residual_scale[channel], frame.residual_scale[full]);
|
||||
}
|
||||
record->residual_pass = frame.free_residual_metrics[0U] <= 1e-10 &&
|
||||
frame.free_residual_metrics[1U] <= 1e-10;
|
||||
}
|
||||
|
||||
void CorrectionRecord(const Vector& coordinate, const Vector& velocity,
|
||||
const Vector& acceleration,
|
||||
const Vector& full_algorithmic,
|
||||
const Vector& reduced_algorithmic,
|
||||
const Vector& reduced_physical,
|
||||
const FixedChart& chart, const DofManager& dofs,
|
||||
const double length, const double h, const double alpha,
|
||||
DynamicNewtonIterationLogRecord* record) {
|
||||
record->coordinate_correction = {};
|
||||
record->velocity_correction = {};
|
||||
record->acceleration_correction = {};
|
||||
record->algorithmic_correction = {};
|
||||
for (const auto full : dofs.FreeDofs()) {
|
||||
const auto channel = full % 6U < 3U ? 0U : 1U;
|
||||
const double scale = channel == 0U ? length : 1.0;
|
||||
record->coordinate_correction[channel] =
|
||||
(std::max)(record->coordinate_correction[channel],
|
||||
std::abs(coordinate[full]) / scale);
|
||||
record->velocity_correction[channel] =
|
||||
(std::max)(record->velocity_correction[channel],
|
||||
std::abs(h * velocity[full]) / scale);
|
||||
record->acceleration_correction[channel] =
|
||||
(std::max)(record->acceleration_correction[channel],
|
||||
std::abs(h * h * acceleration[full]) / scale);
|
||||
record->algorithmic_correction[channel] =
|
||||
(std::max)(record->algorithmic_correction[channel],
|
||||
std::abs(h * h * full_algorithmic[full]) / scale);
|
||||
}
|
||||
record->correction_pass = true;
|
||||
for (std::size_t channel = 0U; channel < 2U; ++channel) {
|
||||
record->correction_pass = record->correction_pass &&
|
||||
record->coordinate_correction[channel] <= 1e-10 &&
|
||||
record->velocity_correction[channel] <= 1e-10 &&
|
||||
record->acceleration_correction[channel] <= 1e-10 &&
|
||||
record->algorithmic_correction[channel] <= 1e-10;
|
||||
}
|
||||
if (reduced_algorithmic.Size() != chart.p_columns.size() ||
|
||||
reduced_physical.Size() != chart.p_columns.size()) {
|
||||
record->correction_pass = false;
|
||||
return;
|
||||
}
|
||||
for (std::size_t i = 0U; i < chart.p_dofs.size(); ++i)
|
||||
record->correction_pass = record->correction_pass &&
|
||||
PrivateCorrectionEqual(chart, i, full_algorithmic,
|
||||
reduced_algorithmic, reduced_physical, alpha);
|
||||
}
|
||||
|
||||
bool ExactlyEqual(const Vector& left, const Vector& right) {
|
||||
if (left.Size() != right.Size()) return false;
|
||||
for (std::size_t i = 0U; i < left.Size(); ++i)
|
||||
if (left[i] != right[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
Status CheckReducedUpdates(const Domain& domain,
|
||||
const DynamicAcceptedFrameState& old,
|
||||
const DynamicAcceptedFrameState& frame,
|
||||
const Vector& predictor_coordinates,
|
||||
const Vector& predictor_velocity,
|
||||
const Vector& physical_coordinates,
|
||||
const Vector& physical_velocity,
|
||||
const Vector& physical_acceleration,
|
||||
const Vector& algebraic_coordinates,
|
||||
const Vector& algebraic_velocity,
|
||||
const Vector& algebraic_acceleration,
|
||||
const Vector& algorithmic_acceleration,
|
||||
const Vector& old_private_coordinates,
|
||||
const Vector& old_private_velocity,
|
||||
const Vector& old_private_physical_acceleration,
|
||||
const Vector& old_private_algorithmic_acceleration,
|
||||
const Vector& increment_trace,
|
||||
const Vector& increment_trace_compensation,
|
||||
const Vector& increment_trace_absolute_sum,
|
||||
const FixedChart& chart,
|
||||
const Vector& chosen_start_coordinates,
|
||||
const Vector& coordinate_low,
|
||||
const HhtParameters& parameters, double h) {
|
||||
if (!GlobalBasisEqual(chart, frame.generalized_coordinates, physical_coordinates,
|
||||
algebraic_coordinates) ||
|
||||
!GlobalBasisEqual(chart, frame.velocity, physical_velocity, algebraic_velocity) ||
|
||||
!GlobalBasisEqual(chart, frame.acceleration, physical_acceleration, algebraic_acceleration))
|
||||
return Failure(domain, "reduced-dynamic-update-inconsistency",
|
||||
"Global HHT reconstruction differs from its actual sparse basis products.");
|
||||
for (std::size_t i = 0U; i < physical_coordinates.Size(); ++i) {
|
||||
const auto inconsistent = [&](const char* reason) {
|
||||
const auto pair_trace = CheckPairIncrement(physical_coordinates[i], coordinate_low[i],
|
||||
old_private_coordinates[i], {increment_trace[i], increment_trace_compensation[i]},
|
||||
increment_trace_absolute_sum[i]);
|
||||
std::ostringstream message;
|
||||
message << std::setprecision(17) << reason << " i=" << i
|
||||
<< ", coordinate_label=" << chart.p_dofs[i]
|
||||
<< ", channel=" << PhysicalCoordinateChannel(chart, i)
|
||||
<< ", old_p=" << old_private_coordinates[i]
|
||||
<< ", start_p=" << chosen_start_coordinates[i]
|
||||
<< ", p=" << physical_coordinates[i]
|
||||
<< ", p_low=" << coordinate_low[i]
|
||||
<< ", actual_increment=" << physical_coordinates[i] - old_private_coordinates[i]
|
||||
<< ", trace_sum=" << increment_trace[i]
|
||||
<< ", trace_compensation=" << increment_trace_compensation[i]
|
||||
<< ", trace_scale=" << increment_trace_absolute_sum[i]
|
||||
<< ", pair_trace_defect=" << pair_trace.defect
|
||||
<< ", pair_trace_ratio=" << pair_trace.ratio
|
||||
<< ", pair_storage=" << pair_trace.storage_pass
|
||||
<< ", representation_distance=" << std::abs(coordinate_low[i])
|
||||
<< ", pp=" << predictor_coordinates[i]
|
||||
<< ", h_vold=" << h * old_private_velocity[i]
|
||||
<< ", h2_old_Ar=" << h * h * (0.5 - parameters.beta) * old_private_algorithmic_acceleration[i]
|
||||
<< ", beta_h2_Ar=" << parameters.beta * h * h * algorithmic_acceleration[i] << ".";
|
||||
return Failure(domain, "reduced-dynamic-update-inconsistency", message.str());
|
||||
};
|
||||
if (!UpdateEqual(physical_coordinates[i],
|
||||
{predictor_coordinates[i], parameters.beta * h * h *
|
||||
algorithmic_acceleration[i]}) ||
|
||||
!UpdateEqual(physical_velocity[i],
|
||||
{predictor_velocity[i], parameters.gamma * h *
|
||||
algorithmic_acceleration[i]}) ||
|
||||
!UpdateEqual((1.0 + parameters.alpha) * physical_acceleration[i],
|
||||
{algorithmic_acceleration[i],
|
||||
parameters.alpha * old_private_physical_acceleration[i]})) {
|
||||
return inconsistent("Reduced HHT private history or public auxiliary rate is inconsistent.");
|
||||
}
|
||||
const double velocity_term = h * old_private_velocity[i];
|
||||
const double old_rate_term = h * h * (0.5 - parameters.beta) *
|
||||
old_private_algorithmic_acceleration[i];
|
||||
const double new_rate_term = h * h * parameters.beta *
|
||||
algorithmic_acceleration[i];
|
||||
const auto whole = CheckPairIncrement(physical_coordinates[i], coordinate_low[i],
|
||||
old_private_coordinates[i], {velocity_term, old_rate_term, new_rate_term},
|
||||
std::abs(velocity_term) + std::abs(old_rate_term) + std::abs(new_rate_term));
|
||||
if (!whole.storage_pass)
|
||||
return inconsistent("Reduced HHT coordinate high is not the RN-even pair value.");
|
||||
if (!whole.arithmetic_pass) {
|
||||
return inconsistent("Reduced HHT resolved private update is inconsistent.");
|
||||
}
|
||||
const auto requested = CheckPairIncrement(physical_coordinates[i], coordinate_low[i],
|
||||
old_private_coordinates[i], {increment_trace[i], increment_trace_compensation[i]},
|
||||
increment_trace_absolute_sum[i]);
|
||||
if (!requested.arithmetic_pass) {
|
||||
return inconsistent("Reduced HHT increment trace is inconsistent.");
|
||||
}
|
||||
}
|
||||
for (std::size_t i = 0U; i < frame.algorithmic_acceleration.Size(); ++i)
|
||||
if (!UpdateEqual(frame.algorithmic_acceleration[i],
|
||||
{(1.0 + parameters.alpha) * frame.acceleration[i],
|
||||
-parameters.alpha * old.acceleration[i]}))
|
||||
return Failure(domain, "reduced-dynamic-update-inconsistency",
|
||||
"Public HHT auxiliary acceleration is inconsistent.");
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
struct ReducedHistory {
|
||||
Vector p{0U};
|
||||
Vector velocity{0U};
|
||||
Vector acceleration{0U};
|
||||
Vector algorithmic_acceleration{0U};
|
||||
Vector z{0U};
|
||||
};
|
||||
|
||||
struct Trial {
|
||||
DynamicAssembly assembly;
|
||||
EmbeddingRoot root;
|
||||
Vector p{0U};
|
||||
Vector velocity{0U};
|
||||
Vector acceleration{0U};
|
||||
Vector algorithmic_acceleration{0U};
|
||||
Vector algebraic_velocity{0U};
|
||||
Vector algebraic_acceleration{0U};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
Result<DynamicExecutionResult> ExecuteReducedHht(
|
||||
const Domain& domain, NewtonIterationLogWriter& log,
|
||||
const ParallelFor& parallel_for, LinearSolver& positive_solver,
|
||||
TangentSolver& tangent_solver) {
|
||||
if (domain.Dynamic() == nullptr)
|
||||
return Result<DynamicExecutionResult>::Failure(Status::Failure(
|
||||
FailureCategory::kModel, {{Severity::kError, "invalid-dynamic-step", {},
|
||||
"DYNAMIC", {},
|
||||
"HHT requires exactly one dynamic step."}}));
|
||||
auto model = AnalysisModel::Create(domain);
|
||||
if (!model.HasValue()) return Result<DynamicExecutionResult>::Failure(model.GetStatus());
|
||||
auto dofs_result = DofManager::Create(model.Value());
|
||||
if (!dofs_result.HasValue()) return Result<DynamicExecutionResult>::Failure(dofs_result.GetStatus());
|
||||
const auto& dofs = dofs_result.Value();
|
||||
auto state_result = DynamicAnalysisState::Create(domain, dofs);
|
||||
if (!state_result.HasValue()) return Result<DynamicExecutionResult>::Failure(state_result.GetStatus());
|
||||
auto state = std::move(state_result.Value());
|
||||
auto shells = BuildRuntimeShells(domain, dofs);
|
||||
if (!shells.HasValue()) return Result<DynamicExecutionResult>::Failure(shells.GetStatus());
|
||||
auto length_result = ReferenceLength(domain);
|
||||
if (!length_result.HasValue()) return Result<DynamicExecutionResult>::Failure(length_result.GetStatus());
|
||||
const double length = length_result.Value();
|
||||
auto loads = AssembleLoads(domain, dofs);
|
||||
if (!loads.HasValue()) return Result<DynamicExecutionResult>::Failure(loads.GetStatus());
|
||||
auto parameters_result = MakeHhtParameters(domain.Dynamic()->alpha);
|
||||
if (!parameters_result.HasValue()) return Result<DynamicExecutionResult>::Failure(parameters_result.GetStatus());
|
||||
const HhtParameters parameters = parameters_result.Value();
|
||||
const double h = domain.Dynamic()->initial_increment;
|
||||
const Vector zero{dofs.FullDofCount()};
|
||||
|
||||
DynamicAcceptedFrameState frame{};
|
||||
frame.is_initial = true;
|
||||
frame.time_integration_formulation =
|
||||
DynamicTimeIntegrationFormulation::kReducedCoordinateHht;
|
||||
for (auto* values : {&frame.generalized_coordinates, &frame.velocity,
|
||||
&frame.acceleration, &frame.algorithmic_acceleration})
|
||||
*values = Vector(dofs.FullDofCount());
|
||||
frame.external_force = loads.Value()[0U];
|
||||
auto initial = EvaluateAssembly(domain, dofs, shells.Value(), frame,
|
||||
loads.Value()[1U], h, parameters,
|
||||
parallel_for, true);
|
||||
if (!initial.HasValue()) return Result<DynamicExecutionResult>::Failure(
|
||||
WithProcedureContext(domain, initial.GetStatus(), "initial", 0U, 0.0));
|
||||
const auto null_nodes = NullNodes(dofs, initial.Value().rotations);
|
||||
auto initial_acceleration = InitializeAcceleration(domain, dofs, initial.Value(), length,
|
||||
positive_solver, tangent_solver);
|
||||
if (!initial_acceleration.HasValue()) return Result<DynamicExecutionResult>::Failure(
|
||||
WithProcedureContext(domain, initial_acceleration.GetStatus(), "initial", 0U, 0.0));
|
||||
frame.acceleration = initial_acceleration.Value();
|
||||
frame.algorithmic_acceleration = initial_acceleration.Value();
|
||||
initial = EvaluateAssembly(domain, dofs, shells.Value(), frame, loads.Value()[1U], h,
|
||||
parameters, parallel_for, true);
|
||||
if (!initial.HasValue()) return Result<DynamicExecutionResult>::Failure(
|
||||
WithProcedureContext(domain, initial.GetStatus(), "initial", 0U, 0.0));
|
||||
frame = initial.Value().frame;
|
||||
frame.time_integration_formulation = DynamicTimeIntegrationFormulation::kReducedCoordinateHht;
|
||||
auto chart = BuildFixedChart(dofs, initial.Value().rotations);
|
||||
if (!chart.HasValue()) return Result<DynamicExecutionResult>::Failure(
|
||||
WithProcedureContext(domain, chart.GetStatus(), "initial", 0U, 0.0));
|
||||
auto p0 = ProjectPhysical(chart.Value(), frame.generalized_coordinates);
|
||||
auto vp0 = ProjectPhysical(chart.Value(), frame.velocity);
|
||||
auto ap0 = ProjectPhysical(chart.Value(), frame.acceleration);
|
||||
auto z0 = ProjectAlgebraic(chart.Value(), frame.generalized_coordinates);
|
||||
for (const auto* projected : {&p0, &vp0, &ap0, &z0})
|
||||
if (!projected->HasValue()) return Result<DynamicExecutionResult>::Failure(projected->GetStatus());
|
||||
ReducedHistory history{std::move(p0.Value()), std::move(vp0.Value()), ap0.Value(),
|
||||
ap0.Value(), std::move(z0.Value())};
|
||||
auto initial_p_acceleration = Lift(chart.Value(), history.acceleration,
|
||||
Vector{chart.Value().z_columns.size()});
|
||||
if (!initial_p_acceleration.HasValue())
|
||||
return Result<DynamicExecutionResult>::Failure(initial_p_acceleration.GetStatus());
|
||||
bool position_geometry_failure = false;
|
||||
const auto position_operators = [&](const DynamicAcceptedFrameState& template_frame,
|
||||
const Vector& q) -> Result<ConstraintOperators> {
|
||||
auto position = template_frame;
|
||||
position.generalized_coordinates = q;
|
||||
position.velocity = zero;
|
||||
position.acceleration = zero;
|
||||
position.algorithmic_acceleration = zero;
|
||||
auto assembly = EvaluateAssembly(domain, dofs, shells.Value(), position,
|
||||
loads.Value()[1U], h, parameters,
|
||||
parallel_for, false);
|
||||
if (!assembly.HasValue()) {
|
||||
if (!assembly.GetStatus().Diagnostics().empty()) {
|
||||
const auto& code = assembly.GetStatus().Diagnostics().front().code;
|
||||
position_geometry_failure =
|
||||
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") {
|
||||
const double margin = std::sqrt(std::numeric_limits<double>::epsilon());
|
||||
const double pi = std::acos(-1.0);
|
||||
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
|
||||
double squared = 0.0;
|
||||
for (std::size_t component = 0U; component < 3U; ++component) {
|
||||
const double value = q[6U * node + 3U + component];
|
||||
squared += value * value;
|
||||
}
|
||||
const double angle = std::sqrt(squared);
|
||||
if (!std::isfinite(angle) || !(pi - angle > margin)) {
|
||||
position_geometry_failure = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Result<ConstraintOperators>::Failure(assembly.GetStatus());
|
||||
}
|
||||
return EvaluateConstraintOperators(domain, dofs, shells.Value(), assembly.Value(),
|
||||
chart.Value(), zero, zero);
|
||||
};
|
||||
auto root0 = SolveEmbeddingRoot(chart.Value(), history.p, history.z,
|
||||
[&](const Vector& q) { return position_operators(frame, q); },
|
||||
tangent_solver);
|
||||
if (!root0.HasValue()) return Result<DynamicExecutionResult>::Failure(
|
||||
WithProcedureContext(domain, root0.GetStatus(), "inner-position", 0U, 0.0));
|
||||
auto rates0 = ReconstructPhysicalRates(chart.Value(), root0.Value(), history.velocity,
|
||||
history.acceleration, tangent_solver);
|
||||
if (!rates0.HasValue()) return Result<DynamicExecutionResult>::Failure(
|
||||
WithProcedureContext(domain, rates0.GetStatus(), "inner-rates", 0U, 0.0));
|
||||
// Frame zero remains the independently recovered physical state. Its
|
||||
// position and velocity must be represented exactly; acceleration is
|
||||
// certified below by the existing hidden-rate and positive-mass evidence,
|
||||
// rather than requiring two independent solves to round identically.
|
||||
if (!ExactlyEqual(root0.Value().q, frame.generalized_coordinates) ||
|
||||
!ExactlyEqual(rates0.Value().velocity, frame.velocity))
|
||||
return Result<DynamicExecutionResult>::Failure(
|
||||
Failure(domain, "dynamic-initial-consistency",
|
||||
"The initialized full HHT position or velocity is not exactly representable by the fixed reduced chart."));
|
||||
auto reconstructed_zero = frame;
|
||||
reconstructed_zero.generalized_coordinates = root0.Value().q;
|
||||
reconstructed_zero.velocity = rates0.Value().velocity;
|
||||
reconstructed_zero.acceleration = rates0.Value().acceleration;
|
||||
reconstructed_zero.algorithmic_acceleration = rates0.Value().acceleration;
|
||||
auto reconstructed_assembly = EvaluateAssembly(
|
||||
domain, dofs, shells.Value(), reconstructed_zero, loads.Value()[1U], h,
|
||||
parameters, parallel_for, true);
|
||||
if (!reconstructed_assembly.HasValue())
|
||||
return Result<DynamicExecutionResult>::Failure(WithProcedureContext(
|
||||
domain, reconstructed_assembly.GetStatus(), "initial", 0U, 0.0));
|
||||
Status reconstructed_status = CompleteConstraintEvidence(
|
||||
domain, dofs, reconstructed_assembly.Value(), length, null_nodes,
|
||||
positive_solver, tangent_solver, &reconstructed_zero);
|
||||
if (!reconstructed_status.IsOk())
|
||||
return Result<DynamicExecutionResult>::Failure(WithProcedureContext(
|
||||
domain, reconstructed_status, "initial", 0U, 0.0));
|
||||
reconstructed_status = CompleteEquilibrium(domain, dofs,
|
||||
reconstructed_assembly.Value(),
|
||||
&reconstructed_zero);
|
||||
if (!reconstructed_status.IsOk())
|
||||
return Result<DynamicExecutionResult>::Failure(WithProcedureContext(
|
||||
domain, reconstructed_status, "initial", 0U, 0.0));
|
||||
if (!(reconstructed_assembly.Value().frame.free_residual_metrics[0U] <=
|
||||
1.0e-10 &&
|
||||
reconstructed_assembly.Value().frame.free_residual_metrics[1U] <=
|
||||
1.0e-10)) {
|
||||
const auto& reconstructed = reconstructed_assembly.Value().frame;
|
||||
DynamicNewtonIterationLogRecord initial_record{};
|
||||
ResidualRecord(dofs, reconstructed, &initial_record);
|
||||
std::ostringstream message;
|
||||
message << std::setprecision(17)
|
||||
<< "The reconstructed frame-zero physical residual is not acceptable; "
|
||||
<< "initial_u=" << frame.free_residual_metrics[0U]
|
||||
<< ", initial_phi=" << frame.free_residual_metrics[1U];
|
||||
for (std::size_t channel = 0U; channel < 2U; ++channel) {
|
||||
std::size_t worst = 0U;
|
||||
double maximum = -1.0;
|
||||
for (const auto full : dofs.FreeDofs()) {
|
||||
if ((full % 6U < 3U ? 0U : 1U) == channel &&
|
||||
std::abs(reconstructed.residual[full]) > maximum) {
|
||||
worst = full;
|
||||
maximum = std::abs(reconstructed.residual[full]);
|
||||
}
|
||||
}
|
||||
message << "; channel=" << channel
|
||||
<< ", ratio=" << reconstructed.free_residual_metrics[channel]
|
||||
<< ", numerator=" << initial_record.residual_numerator[channel]
|
||||
<< ", scale=" << initial_record.residual_scale[channel];
|
||||
if (maximum >= 0.0)
|
||||
message << ", full=" << worst << ", node=" << worst / 6U
|
||||
<< ", residual=" << reconstructed.residual[worst]
|
||||
<< ", row_scale=" << reconstructed.residual_scale[worst]
|
||||
<< ", original_residual=" << frame.residual[worst]
|
||||
<< ", q=" << reconstructed.generalized_coordinates[worst]
|
||||
<< ", v=" << reconstructed.velocity[worst]
|
||||
<< ", a=" << reconstructed.acceleration[worst]
|
||||
<< ", original_a=" << frame.acceleration[worst]
|
||||
<< ", delta_a=" << reconstructed.acceleration[worst] -
|
||||
frame.acceleration[worst];
|
||||
}
|
||||
Vector acceleration_delta{frame.acceleration.Size()};
|
||||
for (std::size_t full = 0U; full < acceleration_delta.Size(); ++full)
|
||||
acceleration_delta[full] = reconstructed.acceleration[full] - frame.acceleration[full];
|
||||
const auto mass_delta = initial.Value().mass.Multiply(acceleration_delta);
|
||||
const auto graph = GraphJacobian(root0.Value().operators, chart.Value());
|
||||
const auto hessian_velocity = ContractConstraintHessian(
|
||||
root0.Value().operators, frame.velocity, frame.velocity);
|
||||
// Failure-only evidence is bounded to sixteen canonical null rows. C09's
|
||||
// eight-node covariance fixture is fully represented, without state edits.
|
||||
const auto count = (std::min)(root0.Value().operators.rows.size(), std::size_t{16U});
|
||||
for (std::size_t row = 0U; row < count; ++row) {
|
||||
const auto node = chart.Value().null_nodes[row];
|
||||
const auto& null = initial.Value().rotations[node].drill_null;
|
||||
const auto& primitive = root0.Value().operators.rows[row];
|
||||
Vector projected_gradient{frame.acceleration.Size()};
|
||||
for (std::size_t component = 0U; component < 3U; ++component) {
|
||||
const auto full = 6U * node + 3U + component;
|
||||
for (std::size_t index = initial.Value().internal_tangent.RowOffsets()[full];
|
||||
index < initial.Value().internal_tangent.RowOffsets()[full + 1U]; ++index)
|
||||
projected_gradient[initial.Value().internal_tangent.ColumnIndices()[index]] +=
|
||||
null[component] * initial.Value().internal_tangent.Values()[index];
|
||||
}
|
||||
double original_dot = 0.0, reconstructed_dot = 0.0, projected_dot = 0.0;
|
||||
double time_velocity = 0.0;
|
||||
for (const auto& entry : primitive.time_gradient)
|
||||
time_velocity += entry.value * frame.velocity[entry.dof];
|
||||
double partition_rhs = -hessian_velocity[row] - 2.0 * time_velocity - primitive.time_second;
|
||||
std::array<double, 3U> translation_sum{}, projected_translation_sum{};
|
||||
for (std::size_t full = 0U; full < frame.acceleration.Size(); ++full) {
|
||||
projected_dot += projected_gradient[full] * frame.acceleration[full];
|
||||
if (full % 6U < 3U)
|
||||
projected_translation_sum[full % 6U] += projected_gradient[full];
|
||||
}
|
||||
for (const auto& entry : primitive.gradient) {
|
||||
original_dot += entry.value * frame.acceleration[entry.dof];
|
||||
reconstructed_dot += entry.value * reconstructed.acceleration[entry.dof];
|
||||
partition_rhs -= entry.value * initial_p_acceleration.Value()[entry.dof];
|
||||
if (entry.dof % 6U < 3U) translation_sum[entry.dof % 6U] += entry.value;
|
||||
}
|
||||
message << "\ninitial-null row=" << row << ", node=" << node
|
||||
<< ", z_label=" << chart.Value().z_dofs[row]
|
||||
<< ", old_psi_a=" << original_dot
|
||||
<< ", reconstructed_psi_a=" << reconstructed_dot
|
||||
<< ", nH_old_a=" << projected_dot
|
||||
<< ", partition_rhs=" << partition_rhs
|
||||
<< ", old_r2=" << original_dot + hessian_velocity[row] +
|
||||
2.0 * time_velocity + primitive.time_second;
|
||||
for (std::size_t component = 0U; component < 3U; ++component) {
|
||||
const auto full = 6U * node + 3U + component;
|
||||
message << ", n" << component << '=' << null[component]
|
||||
<< ", old_a" << component << '=' << frame.acceleration[full]
|
||||
<< ", reconstructed_a" << component << '=' << reconstructed.acceleration[full]
|
||||
<< ", delta_E" << component << '=' << reconstructed.residual[full] - frame.residual[full]
|
||||
<< ", M_delta_a" << component << '=' << mass_delta[full]
|
||||
<< ", translation_sum" << component << '=' << translation_sum[component]
|
||||
<< ", nH_translation_sum" << component << '=' << projected_translation_sum[component];
|
||||
}
|
||||
message << ", G=";
|
||||
for (std::size_t column = 0U; column < graph.Columns(); ++column)
|
||||
if (graph(row, column) != 0.0)
|
||||
message << '(' << column << ':' << graph(row, column) << ')';
|
||||
message << ", gradient_dof_value_olda_nH=";
|
||||
for (const auto& entry : primitive.gradient)
|
||||
message << '(' << entry.dof << ':' << entry.value << ':'
|
||||
<< frame.acceleration[entry.dof] << ':' << projected_gradient[entry.dof] << ')';
|
||||
}
|
||||
return Result<DynamicExecutionResult>::Failure(
|
||||
Failure(domain, "dynamic-initial-consistency",
|
||||
message.str()));
|
||||
}
|
||||
Status status = CompleteConstraintEvidence(domain, dofs, initial.Value(), length, null_nodes,
|
||||
positive_solver, tangent_solver, &frame);
|
||||
if (!status.IsOk()) return Result<DynamicExecutionResult>::Failure(
|
||||
WithProcedureContext(domain, status, "initial", 0U, 0.0));
|
||||
status = CompleteEquilibrium(domain, dofs, initial.Value(), &frame);
|
||||
if (!status.IsOk()) return Result<DynamicExecutionResult>::Failure(
|
||||
WithProcedureContext(domain, status, "initial", 0U, 0.0));
|
||||
auto graph_regularity = CheckGraphRegularity(initial.Value(), chart.Value(),
|
||||
root0.Value().operators,
|
||||
tangent_solver);
|
||||
if (!graph_regularity.HasValue())
|
||||
return Result<DynamicExecutionResult>::Failure(WithProcedureContext(
|
||||
domain, graph_regularity.GetStatus(), "initial", 0U, 0.0));
|
||||
if (!(frame.free_residual_metrics[0U] <= 1.0e-10 &&
|
||||
frame.free_residual_metrics[1U] <= 1.0e-10))
|
||||
return Result<DynamicExecutionResult>::Failure(
|
||||
Failure(domain, "dynamic-initial-consistency",
|
||||
"The initialized frame-zero physical residual is not acceptable."));
|
||||
const double initial_energy = frame.kinetic_energy + frame.physical_strain_energy + frame.drilling_energy;
|
||||
status = state.CommitAcceptedFrame(frame);
|
||||
if (!status.IsOk()) return Result<DynamicExecutionResult>::Failure(status);
|
||||
DynamicNewtonIterationLogRecord record{};
|
||||
record.stage = "accepted"; record.outcome = "initial"; record.correction_pass = true;
|
||||
ResidualRecord(dofs, frame, &record);
|
||||
status = log.Write(record); if (status.IsOk()) status = log.Flush();
|
||||
if (!status.IsOk()) return Result<DynamicExecutionResult>::Failure(status);
|
||||
|
||||
for (std::uint32_t increment = 1U; increment <= domain.Dynamic()->increment_count; ++increment) {
|
||||
const auto& old = state.CommittedState();
|
||||
Vector pp{history.p.Size()}, vpp{history.p.Size()};
|
||||
for (std::size_t i = 0U; i < history.p.Size(); ++i) {
|
||||
pp[i] = history.p[i] + h * history.velocity[i] + h * h * (0.5 - parameters.beta) * history.algorithmic_acceleration[i];
|
||||
vpp[i] = history.velocity[i] + h * (1.0 - parameters.gamma) * history.algorithmic_acceleration[i];
|
||||
}
|
||||
DynamicAcceptedFrameState trial_frame = old;
|
||||
trial_frame.is_initial = false; trial_frame.frame_index = increment;
|
||||
trial_frame.increment_index = increment; trial_frame.time = static_cast<double>(increment) * h;
|
||||
trial_frame.time_integration_formulation = DynamicTimeIntegrationFormulation::kReducedCoordinateHht;
|
||||
Vector trial_algorithmic{history.p.Size()};
|
||||
Vector trial_p = pp;
|
||||
Vector coordinate_low{history.p.Size()};
|
||||
Vector chosen_start_p = pp;
|
||||
Vector increment_trace{history.p.Size()};
|
||||
Vector increment_trace_compensation{history.p.Size()};
|
||||
Vector increment_trace_absolute_sum{history.p.Size()};
|
||||
for (std::size_t i = 0U; i < history.p.Size(); ++i) {
|
||||
const double velocity_term = h * history.velocity[i];
|
||||
const double old_acceleration_term =
|
||||
h * h * (0.5 - parameters.beta) * history.algorithmic_acceleration[i];
|
||||
const double first_sum = history.p[i] + velocity_term;
|
||||
coordinate_low[i] = AdditionLowPart(history.p[i], velocity_term, first_sum) +
|
||||
AdditionLowPart(first_sum, old_acceleration_term, pp[i]);
|
||||
AddIncrementTraceTerm(velocity_term, &increment_trace[i],
|
||||
&increment_trace_compensation[i]);
|
||||
AddIncrementTraceTerm(old_acceleration_term, &increment_trace[i],
|
||||
&increment_trace_compensation[i]);
|
||||
increment_trace_absolute_sum[i] =
|
||||
std::abs(velocity_term) + std::abs(old_acceleration_term);
|
||||
}
|
||||
Vector seed = history.z;
|
||||
record = {}; record.increment_index = increment; record.time = trial_frame.time; record.correction_pass = true;
|
||||
bool translation_private_algorithmic_pass = true;
|
||||
bool copy_translation_rates = false;
|
||||
Vector retained_velocity{history.p.Size()};
|
||||
Vector retained_acceleration{history.p.Size()};
|
||||
bool accepted = false;
|
||||
for (std::uint32_t iteration = 0U;
|
||||
iteration <= kMaximumOuterCorrections; ++iteration) {
|
||||
record.iteration_index = iteration; record.stage = "evaluation"; record.outcome = "candidate";
|
||||
const auto fail = [&](const Status& failure,
|
||||
const char* stage = "outer") -> Result<DynamicExecutionResult> {
|
||||
const Status contextual = WithProcedureContext(
|
||||
domain, failure, stage, increment, trial_frame.time);
|
||||
record.stage = stage; record.outcome = "failed";
|
||||
record.failure_code = contextual.Diagnostics().empty()
|
||||
? "dynamic-solver-failure"
|
||||
: contextual.Diagnostics().front().code;
|
||||
const Status written = log.Write(record);
|
||||
return Result<DynamicExecutionResult>::Failure(
|
||||
written.IsOk() ? contextual : written);
|
||||
};
|
||||
Vector trial_v{history.p.Size()}, trial_a{history.p.Size()};
|
||||
for (std::size_t i = 0U; i < trial_p.Size(); ++i) {
|
||||
if (copy_translation_rates && PhysicalCoordinateChannel(chart.Value(), i) == 0U) {
|
||||
trial_v[i] = retained_velocity[i];
|
||||
trial_a[i] = retained_acceleration[i];
|
||||
} else {
|
||||
trial_v[i] = vpp[i] + parameters.gamma * h * trial_algorithmic[i];
|
||||
trial_a[i] = (trial_algorithmic[i] + parameters.alpha * history.acceleration[i]) / (1.0 + parameters.alpha);
|
||||
}
|
||||
}
|
||||
const auto evaluate_trial = [&](const Vector& physical_p,
|
||||
const Vector& physical_v,
|
||||
const Vector& physical_a,
|
||||
const Vector& root_seed) -> Result<Trial> {
|
||||
auto root = SolveEmbeddingRoot(chart.Value(), physical_p, root_seed,
|
||||
[&](const Vector& q) { return position_operators(trial_frame, q); },
|
||||
tangent_solver);
|
||||
if (!root.HasValue()) return Result<Trial>::Failure(
|
||||
WithProcedureContext(domain, root.GetStatus(), "inner-position",
|
||||
increment, trial_frame.time));
|
||||
auto rates = ReconstructPhysicalRates(chart.Value(), root.Value(), physical_v,
|
||||
physical_a, tangent_solver);
|
||||
if (!rates.HasValue()) return Result<Trial>::Failure(
|
||||
WithProcedureContext(domain, rates.GetStatus(), "inner-rates",
|
||||
increment, trial_frame.time));
|
||||
auto candidate = trial_frame;
|
||||
candidate.generalized_coordinates = root.Value().q;
|
||||
candidate.velocity = rates.Value().velocity;
|
||||
candidate.acceleration = rates.Value().acceleration;
|
||||
candidate.algorithmic_acceleration = Vector(dofs.FullDofCount());
|
||||
for (std::size_t i = 0U; i < candidate.algorithmic_acceleration.Size(); ++i)
|
||||
candidate.algorithmic_acceleration[i] =
|
||||
(1.0 + parameters.alpha) * candidate.acceleration[i] -
|
||||
parameters.alpha * old.acceleration[i];
|
||||
candidate.iteration_count = iteration;
|
||||
auto assembly = EvaluateAssembly(domain, dofs, shells.Value(), candidate,
|
||||
loads.Value()[1U], h, parameters,
|
||||
parallel_for, true);
|
||||
if (!assembly.HasValue()) return Result<Trial>::Failure(
|
||||
WithProcedureContext(domain, assembly.GetStatus(), "outer",
|
||||
increment, trial_frame.time));
|
||||
assembly.Value().frame.time_integration_formulation =
|
||||
DynamicTimeIntegrationFormulation::kReducedCoordinateHht;
|
||||
return Result<Trial>::Success(
|
||||
{std::move(assembly.Value()), std::move(root.Value()), physical_p,
|
||||
physical_v, physical_a, trial_algorithmic,
|
||||
std::move(rates.Value().z_velocity), std::move(rates.Value().z_acceleration)});
|
||||
};
|
||||
position_geometry_failure = false;
|
||||
auto trial = evaluate_trial(trial_p, trial_v, trial_a, seed);
|
||||
if (!trial.HasValue() && iteration == 0U && position_geometry_failure) {
|
||||
for (std::size_t i = 0U; i < trial_p.Size(); ++i) {
|
||||
trial_algorithmic[i] =
|
||||
(history.p[i] - pp[i]) / (parameters.beta * h * h);
|
||||
trial_p[i] = history.p[i];
|
||||
chosen_start_p[i] = history.p[i];
|
||||
coordinate_low[i] = 0.0;
|
||||
increment_trace[i] = 0.0;
|
||||
increment_trace_compensation[i] = 0.0;
|
||||
increment_trace_absolute_sum[i] = 0.0;
|
||||
trial_v[i] = vpp[i] + parameters.gamma * h * trial_algorithmic[i];
|
||||
trial_a[i] = (trial_algorithmic[i] +
|
||||
parameters.alpha * history.acceleration[i]) /
|
||||
(1.0 + parameters.alpha);
|
||||
}
|
||||
trial = evaluate_trial(trial_p, trial_v, trial_a, seed);
|
||||
}
|
||||
if (!trial.HasValue()) return fail(trial.GetStatus());
|
||||
if (iteration == 0U && !ExactlyEqual(trial_p, history.p)) {
|
||||
Vector old_algorithmic{trial_p.Size()};
|
||||
Vector old_velocity{trial_p.Size()};
|
||||
Vector old_acceleration{trial_p.Size()};
|
||||
for (std::size_t i = 0U; i < trial_p.Size(); ++i) {
|
||||
old_algorithmic[i] = (history.p[i] - pp[i]) / (parameters.beta * h * h);
|
||||
old_velocity[i] = vpp[i] + parameters.gamma * h * old_algorithmic[i];
|
||||
old_acceleration[i] = (old_algorithmic[i] + parameters.alpha * history.acceleration[i]) /
|
||||
(1.0 + parameters.alpha);
|
||||
}
|
||||
auto old_trial = evaluate_trial(history.p, old_velocity, old_acceleration, seed);
|
||||
if (!old_trial.HasValue()) return fail(old_trial.GetStatus());
|
||||
const auto reduced_residual = [&](const Trial& value) {
|
||||
double norm = 0.0;
|
||||
for (const auto full : dofs.FreeDofs()) {
|
||||
const double scale = full % 6U < 3U ? length : 1.0;
|
||||
norm = (std::max)(norm,
|
||||
scale * std::abs(value.assembly.frame.residual[full]));
|
||||
}
|
||||
if (!std::isfinite(norm)) return std::numeric_limits<double>::infinity();
|
||||
return norm;
|
||||
};
|
||||
const double old_residual = reduced_residual(old_trial.Value());
|
||||
const double predictor_residual = reduced_residual(trial.Value());
|
||||
if (!std::isfinite(old_residual) || !std::isfinite(predictor_residual))
|
||||
return fail(Failure(domain, "dynamic-invalid-state",
|
||||
"The scaled initial reduced residual is nonfinite."));
|
||||
if (old_residual < predictor_residual) {
|
||||
trial = std::move(old_trial);
|
||||
trial_p = history.p;
|
||||
chosen_start_p = history.p;
|
||||
coordinate_low = Vector(history.p.Size());
|
||||
increment_trace = Vector(history.p.Size());
|
||||
increment_trace_compensation = Vector(history.p.Size());
|
||||
increment_trace_absolute_sum = Vector(history.p.Size());
|
||||
trial_v = old_velocity;
|
||||
trial_a = old_acceleration;
|
||||
trial_algorithmic = old_algorithmic;
|
||||
}
|
||||
}
|
||||
auto candidate = trial.Value().assembly.frame;
|
||||
auto operators = EvaluateConstraintOperators(domain, dofs, shells.Value(),
|
||||
trial.Value().assembly, chart.Value(), zero, zero);
|
||||
if (!operators.HasValue()) return fail(operators.GetStatus());
|
||||
ResidualRecord(dofs, candidate, &record);
|
||||
if (record.residual_pass && record.correction_pass) {
|
||||
status = CompleteConstraintEvidence(domain, dofs, trial.Value().assembly, length, null_nodes,
|
||||
positive_solver, tangent_solver, &candidate);
|
||||
if (!status.IsOk()) return fail(status, "accepted");
|
||||
status = CompleteEquilibrium(domain, dofs, trial.Value().assembly, &candidate);
|
||||
if (!status.IsOk()) return fail(status, "accepted");
|
||||
graph_regularity = CheckGraphRegularity(trial.Value().assembly,
|
||||
chart.Value(), operators.Value(),
|
||||
tangent_solver);
|
||||
if (!graph_regularity.HasValue())
|
||||
return fail(graph_regularity.GetStatus(), "accepted");
|
||||
status = CheckReducedUpdates(domain, old, candidate, pp, vpp, trial_p, trial_v,
|
||||
trial_a, trial.Value().root.z,
|
||||
trial.Value().algebraic_velocity,
|
||||
trial.Value().algebraic_acceleration,
|
||||
trial_algorithmic, history.p, history.velocity,
|
||||
history.acceleration,
|
||||
history.algorithmic_acceleration,
|
||||
increment_trace,
|
||||
increment_trace_compensation,
|
||||
increment_trace_absolute_sum,
|
||||
chart.Value(), chosen_start_p, coordinate_low,
|
||||
parameters, h);
|
||||
if (!status.IsOk()) return fail(status, "accepted");
|
||||
candidate.external_work = 0.0;
|
||||
for (std::size_t i = 0U; i < dofs.FullDofCount(); ++i)
|
||||
candidate.external_work += candidate.external_force[i] * candidate.generalized_coordinates[i];
|
||||
candidate.energy_defect = candidate.kinetic_energy + candidate.physical_strain_energy +
|
||||
candidate.drilling_energy - initial_energy - candidate.external_work;
|
||||
status = state.CommitAcceptedFrame(candidate);
|
||||
if (!status.IsOk()) return fail(status, "accepted");
|
||||
history = {trial_p, trial_v, trial_a, trial_algorithmic, trial.Value().root.z};
|
||||
record.stage = "accepted"; record.outcome = "accepted";
|
||||
status = log.Write(record); if (status.IsOk()) status = log.Flush();
|
||||
if (!status.IsOk()) return Result<DynamicExecutionResult>::Failure(status);
|
||||
accepted = true; break;
|
||||
}
|
||||
if (!CanApplyOuterCorrection(iteration)) {
|
||||
double full_hidden = 0.0;
|
||||
double analytic_hidden = 0.0;
|
||||
double inertia_hidden = 0.0;
|
||||
double consistency = 0.0;
|
||||
for (std::size_t row = 0U; row < chart.Value().null_nodes.size(); ++row) {
|
||||
const auto node = chart.Value().null_nodes[row];
|
||||
const auto& null = trial.Value().assembly.rotations[node].drill_null;
|
||||
double full = 0.0;
|
||||
double inertia = 0.0;
|
||||
for (std::size_t component = 0U; component < 3U; ++component) {
|
||||
const auto full_dof = 6U * node + 3U + component;
|
||||
full += null[component] * candidate.residual[full_dof];
|
||||
inertia += null[component] * candidate.inertia_force[full_dof];
|
||||
}
|
||||
const double analytic = operators.Value().rows[row].value;
|
||||
full_hidden = (std::max)(full_hidden, std::abs(full));
|
||||
analytic_hidden = (std::max)(analytic_hidden, std::abs(analytic));
|
||||
inertia_hidden = (std::max)(inertia_hidden, std::abs(inertia));
|
||||
consistency = (std::max)(consistency, std::abs(full - analytic - inertia));
|
||||
}
|
||||
std::ostringstream message;
|
||||
const auto maximum = [](const Vector& values) {
|
||||
double result = 0.0;
|
||||
for (std::size_t i = 0U; i < values.Size(); ++i)
|
||||
result = (std::max)(result, std::abs(values[i]));
|
||||
return result;
|
||||
};
|
||||
Vector position_correction{trial_algorithmic.Size()};
|
||||
Vector velocity_correction{trial_algorithmic.Size()};
|
||||
for (std::size_t i = 0U; i < trial_algorithmic.Size(); ++i) {
|
||||
position_correction[i] = parameters.beta * h * h * trial_algorithmic[i];
|
||||
velocity_correction[i] = parameters.gamma * h * trial_algorithmic[i];
|
||||
}
|
||||
message << "HHT exhausted 100 corrections without satisfying every endpoint check; "
|
||||
<< "stage=outer, outer_iteration=" << iteration
|
||||
<< ", graph_iteration=" << trial.Value().root.graph_iteration_count
|
||||
<< ", graph_residual=" << trial.Value().root.graph_residual_infinity
|
||||
<< ", graph_relative=" << trial.Value().root.graph_residual_relative
|
||||
<< ", graph_correction=" << trial.Value().root.graph_correction_infinity
|
||||
<< ", max_NtE=" << full_hidden
|
||||
<< ", max_analytic_psi=" << analytic_hidden
|
||||
<< ", max_NtInertia=" << inertia_hidden
|
||||
<< ", max_NtE_minus_psi_minus_NtInertia=" << consistency
|
||||
<< ", max_pp=" << maximum(pp)
|
||||
<< ", max_beta_h2_trial_Ar=" << maximum(position_correction)
|
||||
<< ", max_trial_p=" << maximum(trial_p)
|
||||
<< ", max_vp_predictor=" << maximum(vpp)
|
||||
<< ", max_gamma_h_trial_Ar=" << maximum(velocity_correction)
|
||||
<< ", max_trial_v=" << maximum(trial_v)
|
||||
<< ", max_physical_v=" << maximum(trial.Value().velocity) << ".";
|
||||
return fail(Failure(domain, "dynamic-nonconvergence", message.str()));
|
||||
}
|
||||
status = log.Write(record);
|
||||
if (!status.IsOk()) return Result<DynamicExecutionResult>::Failure(status);
|
||||
const double velocity_rate = parameters.gamma / (parameters.beta * h);
|
||||
const double acceleration_rate = 1.0 / ((1.0 + parameters.alpha) * parameters.beta * h * h);
|
||||
auto system = AssembleAugmentedReducedSystem(trial.Value().assembly, chart.Value(), operators.Value(), velocity_rate, acceleration_rate, length, h);
|
||||
if (!system.HasValue()) return fail(system.GetStatus());
|
||||
const bool angular_only = ShouldRefineAngular(
|
||||
chart.Value(), candidate.free_residual_metrics,
|
||||
{record.coordinate_correction[0U], record.velocity_correction[0U],
|
||||
record.acceleration_correction[0U], record.algorithmic_correction[0U]},
|
||||
translation_private_algorithmic_pass);
|
||||
auto selected = SelectAugmentedSolve(system.Value(), chart.Value(), angular_only);
|
||||
if (!selected.HasValue()) return fail(selected.GetStatus());
|
||||
const auto linear_failure = [&](const Status& failure) {
|
||||
return fail(failure, angular_only ? "angular-newton-linear-solve"
|
||||
: "full-newton-linear-solve");
|
||||
};
|
||||
Vector selected_solution{selected.Value().rhs.Size()};
|
||||
if (selected_solution.Size() > 0U) {
|
||||
status = tangent_solver.FactorizeFresh(selected.Value().tangent);
|
||||
if (!status.IsOk()) return linear_failure(status);
|
||||
status = tangent_solver.SolveCorrection(selected.Value().rhs, selected_solution);
|
||||
if (!status.IsOk()) return linear_failure(status);
|
||||
status = CheckLinearSolve(selected.Value().tangent, selected.Value().rhs, selected_solution);
|
||||
if (!status.IsOk()) return linear_failure(status);
|
||||
}
|
||||
auto solution = EmbedAugmentedSolution(selected.Value(), selected_solution,
|
||||
system.Value().rhs.Size());
|
||||
if (!solution.HasValue()) return fail(solution.GetStatus());
|
||||
auto correction = MapAugmentedCorrection(chart.Value(), system.Value(), solution.Value(), velocity_rate, acceleration_rate);
|
||||
if (!correction.HasValue()) return fail(correction.GetStatus());
|
||||
Vector full_algorithmic{dofs.FullDofCount()};
|
||||
Vector reduced_algorithmic{correction.Value().p.Size()};
|
||||
Vector reduced_physical{correction.Value().p.Size()};
|
||||
for (std::size_t i = 0U; i < full_algorithmic.Size(); ++i)
|
||||
full_algorithmic[i] =
|
||||
(1.0 + parameters.alpha) * correction.Value().acceleration[i];
|
||||
for (std::size_t i = 0U; i < reduced_algorithmic.Size(); ++i) {
|
||||
reduced_algorithmic[i] = correction.Value().p[i] /
|
||||
(parameters.beta * h * h);
|
||||
reduced_physical[i] = acceleration_rate * correction.Value().p[i];
|
||||
}
|
||||
CorrectionRecord(correction.Value().coordinate, correction.Value().velocity,
|
||||
correction.Value().acceleration, full_algorithmic,
|
||||
reduced_algorithmic, reduced_physical, chart.Value(), dofs, length, h,
|
||||
parameters.alpha,
|
||||
&record);
|
||||
translation_private_algorithmic_pass = true;
|
||||
for (std::size_t i = 0U; i < chart.Value().p_dofs.size(); ++i)
|
||||
if (PhysicalCoordinateChannel(chart.Value(), i) == 0U)
|
||||
translation_private_algorithmic_pass =
|
||||
translation_private_algorithmic_pass &&
|
||||
PrivateCorrectionEqual(chart.Value(), i, full_algorithmic,
|
||||
reduced_algorithmic, reduced_physical, parameters.alpha);
|
||||
copy_translation_rates = angular_only;
|
||||
if (angular_only) {
|
||||
retained_velocity = trial_v;
|
||||
retained_acceleration = trial_a;
|
||||
}
|
||||
for (std::size_t i = 0U; i < trial_algorithmic.Size(); ++i) {
|
||||
if (angular_only && PhysicalCoordinateChannel(chart.Value(), i) == 0U) continue;
|
||||
trial_algorithmic[i] += reduced_algorithmic[i];
|
||||
}
|
||||
for (std::size_t i = 0U; i < trial_p.Size(); ++i) {
|
||||
if (angular_only && PhysicalCoordinateChannel(chart.Value(), i) == 0U) continue;
|
||||
AdvanceCoordinate(correction.Value().p[i], &trial_p[i], &coordinate_low[i]);
|
||||
}
|
||||
for (std::size_t i = 0U; i < increment_trace.Size(); ++i) {
|
||||
if (angular_only && PhysicalCoordinateChannel(chart.Value(), i) == 0U) continue;
|
||||
AddIncrementTraceTerm(correction.Value().p[i], &increment_trace[i],
|
||||
&increment_trace_compensation[i]);
|
||||
increment_trace_absolute_sum[i] += std::abs(correction.Value().p[i]);
|
||||
}
|
||||
seed = trial.Value().root.z;
|
||||
for (std::size_t i = 0U; i < seed.Size(); ++i)
|
||||
seed[i] += correction.Value().z[i];
|
||||
}
|
||||
if (!accepted) return Result<DynamicExecutionResult>::Failure(
|
||||
Failure(domain, "dynamic-nonconvergence", "Dynamic increment was not accepted."));
|
||||
}
|
||||
return Result<DynamicExecutionResult>::Success({std::move(state), domain.Warnings()});
|
||||
}
|
||||
} // namespace fesa
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef FESA_ANALYSIS_HHT_REDUCED_PROCEDURE_H_
|
||||
#define FESA_ANALYSIS_HHT_REDUCED_PROCEDURE_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "fesa/analysis/prepared_analysis.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace hht_reduced_internal {
|
||||
constexpr std::uint32_t kMaximumOuterCorrections = 100U;
|
||||
constexpr bool CanApplyOuterCorrection(const std::uint32_t completed) {
|
||||
return completed < kMaximumOuterCorrections;
|
||||
}
|
||||
} // namespace hht_reduced_internal
|
||||
|
||||
class LinearSolver;
|
||||
class NewtonIterationLogWriter;
|
||||
class ParallelFor;
|
||||
class TangentSolver;
|
||||
|
||||
Result<DynamicExecutionResult> ExecuteReducedHht(
|
||||
const Domain& domain, NewtonIterationLogWriter& log,
|
||||
const ParallelFor& parallel_for, LinearSolver& positive_solver,
|
||||
TangentSolver& tangent_solver);
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_ANALYSIS_HHT_REDUCED_PROCEDURE_H_
|
||||
@@ -119,7 +119,7 @@ void AppendMatrix(const Matrix& local, const RuntimeSolid& solid, std::size_t e,
|
||||
coo->push_back(
|
||||
{solid.scatter[i], solid.scatter[j], local(i, j), e, 24 * i + j});
|
||||
}
|
||||
/// @brief Assembles constant reference consistent mass once without lumping.
|
||||
/// @brief Assembles constant reference row-sum lumped mass once.
|
||||
Result<SparseMatrix> AssembleMass(const std::vector<RuntimeSolid>& solids,
|
||||
const DofManager& dofs) {
|
||||
std::vector<CooContribution> coo;
|
||||
|
||||
@@ -219,7 +219,7 @@ Result<ElementStiffnessContribution> Hex8Element::ComputeMass() const {
|
||||
if (!density_)
|
||||
return Result<ElementStiffnessContribution>::Failure(
|
||||
Failure("Hex8 mass requires reference density."));
|
||||
Matrix mass{24U, 24U};
|
||||
Matrix consistent{24U, 24U};
|
||||
for (const auto& point : points_)
|
||||
for (std::size_t a = 0; a < 8; ++a)
|
||||
for (std::size_t b = 0; b < 8; ++b) {
|
||||
@@ -229,15 +229,18 @@ Result<ElementStiffnessContribution> Hex8Element::ComputeMass() const {
|
||||
return Result<ElementStiffnessContribution>::Failure(
|
||||
Failure("Hex8 reference mass is nonfinite."));
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
mass(3U * a + i, 3U * b + i) += value;
|
||||
consistent(3U * a + i, 3U * b + i) += value;
|
||||
}
|
||||
Matrix mass{24U, 24U};
|
||||
for (std::size_t i = 0; i < 24; ++i)
|
||||
for (std::size_t j = 0; j < 24; ++j)
|
||||
if (!std::isfinite(mass(i, j)))
|
||||
for (std::size_t j = 0; j < 24; ++j) {
|
||||
if (!std::isfinite(consistent(i, j)))
|
||||
return Result<ElementStiffnessContribution>::Failure(
|
||||
Failure("Hex8 accumulated reference mass is nonfinite."));
|
||||
mass(i, i) += consistent(i, j);
|
||||
}
|
||||
for (std::size_t i = 0; i < 24; ++i)
|
||||
if (mass(i, i) <= 0.0)
|
||||
if (!std::isfinite(mass(i, i)) || mass(i, i) <= 0.0)
|
||||
return Result<ElementStiffnessContribution>::Failure(Failure(
|
||||
"Hex8 reference mass has an unrepresentable positive diagonal."));
|
||||
return Result<ElementStiffnessContribution>::Success(
|
||||
|
||||
@@ -19,6 +19,49 @@ const std::array<double, 3> kGauss{-std::sqrt(3.0 / 5.0), 0.0,
|
||||
std::sqrt(3.0 / 5.0)};
|
||||
constexpr std::array<double, 3> kWeights{5.0 / 9.0, 8.0 / 9.0, 5.0 / 9.0};
|
||||
|
||||
class InertiaSum {
|
||||
public:
|
||||
void Add(const double term) {
|
||||
const double next = sum_ + term;
|
||||
correction_ += std::abs(sum_) >= std::abs(term)
|
||||
? (sum_ - next) + term
|
||||
: (term - next) + sum_;
|
||||
sum_ = next;
|
||||
}
|
||||
double Value() const { return sum_ + correction_; }
|
||||
|
||||
private:
|
||||
double sum_ = 0.0;
|
||||
double correction_ = 0.0;
|
||||
};
|
||||
|
||||
Vector3 WeightedSum(const std::array<double, kNodes>& weights,
|
||||
const std::array<Vector3, kNodes>& values) {
|
||||
Vector3 result{};
|
||||
for (std::size_t node = 0U; node < kNodes; ++node)
|
||||
result = result + weights[node] * values[node];
|
||||
return result;
|
||||
}
|
||||
|
||||
// A derivative partition has no absolute-position contribution. Anchoring
|
||||
// before reduction preserves that identity at large global offsets and keeps
|
||||
// the reference/current covariants in the same canonical form.
|
||||
Vector3 DerivativeSum(const std::array<double, kNodes>& derivatives,
|
||||
const std::array<Vector3, kNodes>& values) {
|
||||
std::array<Vector3, kNodes> relative{};
|
||||
for (std::size_t node = 0U; node < kNodes; ++node)
|
||||
relative[node] = values[node] - values[0U];
|
||||
return WeightedSum(derivatives, relative);
|
||||
}
|
||||
|
||||
Vector3 InterpolatedSum(const std::array<double, kNodes>& weights,
|
||||
const std::array<Vector3, kNodes>& values) {
|
||||
std::array<Vector3, kNodes> relative{};
|
||||
for (std::size_t node = 0U; node < kNodes; ++node)
|
||||
relative[node] = values[node] - values[0U];
|
||||
return values[0U] + WeightedSum(weights, relative);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Result<T> Failure(const std::string& code, const SourceLocation& location,
|
||||
const std::string& identity, std::string message) {
|
||||
@@ -723,131 +766,232 @@ Result<NonlinearMitc4InertiaEvaluation> NonlinearMitc4Inertia::Evaluate(
|
||||
{},
|
||||
{},
|
||||
{}};
|
||||
using LocalDerivative = std::array<std::array<std::array<double, 6U>, 6U>, 3U>;
|
||||
std::array<LocalDerivative, kNodes> mass_derivative{};
|
||||
// Integrate acceleration/velocity coefficients before their contraction.
|
||||
// This preserves rotary contributions until odd-in-z translation cancels.
|
||||
std::array<Vector3, kDofs> momentum_coefficients{};
|
||||
std::array<Vector3, kDofs> angular_coefficients{};
|
||||
Vector3 convective_momentum{};
|
||||
Vector3 convective_angular{};
|
||||
for (std::size_t xi = 0U; xi < kGauss.size(); ++xi) {
|
||||
for (std::size_t eta = 0U; eta < kGauss.size(); ++eta) {
|
||||
const Mitc4ShapeFunctions shape =
|
||||
Mitc4Shell::ShapeFunctions(kGauss[xi], kGauss[eta]);
|
||||
std::array<Vector3, kNodes> displacements{};
|
||||
std::array<Vector3, kNodes> current_coordinates{};
|
||||
std::array<Vector3, kNodes> current_directors{};
|
||||
for (std::size_t node = 0U; node < kNodes; ++node) {
|
||||
displacements[node] = trial.displacement[node];
|
||||
current_coordinates[node] = coordinates_[node] + displacements[node];
|
||||
current_directors[node] = rotations[node].director;
|
||||
}
|
||||
const Vector3 reference_xi_anchor =
|
||||
DerivativeSum(shape.xi_derivatives, coordinates_);
|
||||
const Vector3 reference_eta_anchor =
|
||||
DerivativeSum(shape.eta_derivatives, coordinates_);
|
||||
const Vector3 reference_xi_director =
|
||||
DerivativeSum(shape.xi_derivatives, initial_directors_);
|
||||
const Vector3 reference_eta_director =
|
||||
DerivativeSum(shape.eta_derivatives, initial_directors_);
|
||||
const Vector3 reference_zeta =
|
||||
0.5 * thickness_ * InterpolatedSum(shape.values, initial_directors_);
|
||||
const Vector3 current_xi_anchor =
|
||||
reference_xi_anchor + DerivativeSum(shape.xi_derivatives, displacements);
|
||||
const Vector3 current_eta_anchor =
|
||||
reference_eta_anchor + DerivativeSum(shape.eta_derivatives, displacements);
|
||||
const Vector3 current_xi_director =
|
||||
DerivativeSum(shape.xi_derivatives, current_directors);
|
||||
const Vector3 current_eta_director =
|
||||
DerivativeSum(shape.eta_derivatives, current_directors);
|
||||
const Vector3 current_zeta =
|
||||
0.5 * thickness_ * InterpolatedSum(shape.values, current_directors);
|
||||
for (std::size_t zeta = 0U; zeta < kGauss.size(); ++zeta) {
|
||||
const double z = 0.5 * thickness_ * kGauss[zeta];
|
||||
const double weight = kWeights[xi] * kWeights[eta] * kWeights[zeta];
|
||||
Vector3 ref_xi{};
|
||||
Vector3 ref_eta{};
|
||||
Vector3 ref_zeta{};
|
||||
Vector3 current_xi{};
|
||||
Vector3 current_eta{};
|
||||
Vector3 current_zeta{};
|
||||
Vector3 position{};
|
||||
Vector3 velocity_point{};
|
||||
Vector3 acceleration_point{};
|
||||
Vector3 convective_point{};
|
||||
std::array<Vector3, kDofs> h{};
|
||||
std::array<Vector3, kDofs> velocity_derivative{};
|
||||
std::array<Vector3, kDofs> position_derivative{};
|
||||
for (std::size_t node = 0U; node < kNodes; ++node) {
|
||||
const std::size_t base = 6U * node;
|
||||
const auto& rotation = rotations[node];
|
||||
const Vector3 reference =
|
||||
coordinates_[node] + z * initial_directors_[node];
|
||||
const Vector3 current = coordinates_[node] +
|
||||
trial.displacement[node] +
|
||||
z * rotation.director;
|
||||
ref_xi = ref_xi + shape.xi_derivatives[node] * reference;
|
||||
ref_eta = ref_eta + shape.eta_derivatives[node] * reference;
|
||||
ref_zeta = ref_zeta + 0.5 * thickness_ * shape.values[node] *
|
||||
initial_directors_[node];
|
||||
current_xi = current_xi + shape.xi_derivatives[node] * current;
|
||||
current_eta = current_eta + shape.eta_derivatives[node] * current;
|
||||
current_zeta = current_zeta + 0.5 * thickness_ * shape.values[node] *
|
||||
rotation.director;
|
||||
position = position + shape.values[node] * current;
|
||||
for (std::size_t component = 0U; component < 3U; ++component) {
|
||||
std::array<double, 3> translation{};
|
||||
translation[component] = shape.values[node];
|
||||
h[base + component] = Vector3{translation};
|
||||
h[base + 3U + component] =
|
||||
z * shape.values[node] * rotation.director_first[component];
|
||||
for (std::size_t other = 0U; other < 3U; ++other) {
|
||||
const Vector3 second = z * shape.values[node] *
|
||||
rotation.director_second[component][other];
|
||||
convective_point =
|
||||
convective_point + second * velocity[base + 3U + component] *
|
||||
velocity[base + 3U + other];
|
||||
velocity_derivative[base + 3U + component] =
|
||||
velocity_derivative[base + 3U + component] +
|
||||
2.0 * second * velocity[base + 3U + other];
|
||||
position_derivative[base + 3U + component] =
|
||||
position_derivative[base + 3U + component] +
|
||||
second * acceleration[base + 3U + other];
|
||||
for (std::size_t third = 0U; third < 3U; ++third) {
|
||||
position_derivative[base + 3U + component] =
|
||||
position_derivative[base + 3U + component] +
|
||||
z * shape.values[node] *
|
||||
rotation.director_third[component][other][third] *
|
||||
velocity[base + 3U + other] *
|
||||
velocity[base + 3U + third];
|
||||
// Use the nodal kinetic measure rho * N_i * dV0.
|
||||
for (std::size_t mass_node = 0U; mass_node < kNodes; ++mass_node) {
|
||||
std::array<double, kNodes> kinetic_shape{};
|
||||
kinetic_shape[mass_node] = 1.0;
|
||||
const double z = 0.5 * thickness_ * kGauss[zeta];
|
||||
const double weight = kWeights[xi] * kWeights[eta] * kWeights[zeta];
|
||||
const Vector3 ref_xi = reference_xi_anchor + z * reference_xi_director;
|
||||
const Vector3 ref_eta = reference_eta_anchor + z * reference_eta_director;
|
||||
const Vector3 current_xi = current_xi_anchor + z * current_xi_director;
|
||||
const Vector3 current_eta = current_eta_anchor + z * current_eta_director;
|
||||
Vector3 position{};
|
||||
Vector3 velocity_point{};
|
||||
Vector3 acceleration_point{};
|
||||
Vector3 convective_point{};
|
||||
std::array<Vector3, kDofs> h{};
|
||||
std::array<Vector3, kDofs> velocity_derivative{};
|
||||
std::array<Vector3, kDofs> position_derivative{};
|
||||
for (std::size_t node = 0U; node < kNodes; ++node) {
|
||||
const std::size_t base = 6U * node;
|
||||
const auto& rotation = rotations[node];
|
||||
const Vector3 current =
|
||||
current_coordinates[node] + z * current_directors[node];
|
||||
position = position + kinetic_shape[node] * current;
|
||||
for (std::size_t component = 0U; component < 3U; ++component) {
|
||||
std::array<double, 3> translation{};
|
||||
translation[component] = kinetic_shape[node];
|
||||
h[base + component] = Vector3{translation};
|
||||
h[base + 3U + component] =
|
||||
z * kinetic_shape[node] * rotation.director_first[component];
|
||||
for (std::size_t other = 0U; other < 3U; ++other) {
|
||||
const Vector3 second =
|
||||
z * kinetic_shape[node] *
|
||||
rotation.director_second[component][other];
|
||||
convective_point = convective_point +
|
||||
second * velocity[base + 3U + component] *
|
||||
velocity[base + 3U + other];
|
||||
velocity_derivative[base + 3U + component] =
|
||||
velocity_derivative[base + 3U + component] +
|
||||
2.0 * second * velocity[base + 3U + other];
|
||||
for (std::size_t third = 0U; third < 3U; ++third) {
|
||||
position_derivative[base + 3U + component] =
|
||||
position_derivative[base + 3U + component] +
|
||||
z * kinetic_shape[node] *
|
||||
rotation.director_third[component][other][third] *
|
||||
velocity[base + 3U + other] *
|
||||
velocity[base + 3U + third];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const double jacobian = Determinant(ref_xi, ref_eta, ref_zeta);
|
||||
const double current_jacobian =
|
||||
Determinant(current_xi, current_eta, current_zeta);
|
||||
if (!std::isfinite(jacobian) || !(jacobian > 0.0) ||
|
||||
!std::isfinite(current_jacobian) || !(current_jacobian > 0.0)) {
|
||||
return Failure<NonlinearMitc4InertiaEvaluation>(
|
||||
"invalid-nonlinear-inertia-geometry", source_location_, identity_,
|
||||
"Inertia requires positive reference and current mass-point "
|
||||
"Jacobians.");
|
||||
}
|
||||
for (std::size_t row = 0U; row < kDofs; ++row) {
|
||||
velocity_point = velocity_point + h[row] * velocity[row];
|
||||
acceleration_point = acceleration_point + h[row] * acceleration[row];
|
||||
}
|
||||
acceleration_point = acceleration_point + convective_point;
|
||||
const double scale = density_ * weight * jacobian;
|
||||
if (!std::isfinite(scale) || !(scale > 0.0)) {
|
||||
return Failure<NonlinearMitc4InertiaEvaluation>(
|
||||
"invalid-nonlinear-inertia-positive-mass", source_location_,
|
||||
identity_,
|
||||
"Mass-point integration weight must be finite and positive.");
|
||||
}
|
||||
result.kinetic_energy +=
|
||||
0.5 * scale * velocity_point.Dot(velocity_point);
|
||||
result.momentum = result.momentum + scale * velocity_point;
|
||||
result.momentum_rate =
|
||||
result.momentum_rate + scale * acceleration_point;
|
||||
result.angular_momentum =
|
||||
result.angular_momentum + scale * position.Cross(velocity_point);
|
||||
result.angular_momentum_rate =
|
||||
result.angular_momentum_rate +
|
||||
scale * position.Cross(acceleration_point);
|
||||
result.momentum_rate_scale += scale * acceleration_point.Norm();
|
||||
result.angular_momentum_rate_scale +=
|
||||
scale * position.Cross(acceleration_point).Norm();
|
||||
for (std::size_t row = 0U; row < kDofs; ++row) {
|
||||
result.force[row] += scale * h[row].Dot(acceleration_point);
|
||||
// Keep c independent of a; subtracting M*a destroys small c values.
|
||||
result.convective_force[row] += scale * h[row].Dot(convective_point);
|
||||
for (std::size_t column = 0U; column < kDofs; ++column) {
|
||||
result.mass(row, column) += scale * h[row].Dot(h[column]);
|
||||
result.velocity_tangent(row, column) +=
|
||||
scale * h[row].Dot(velocity_derivative[column]);
|
||||
double position_entry = h[row].Dot(position_derivative[column]);
|
||||
if (row / 6U == column / 6U && row % 6U >= 3U &&
|
||||
column % 6U >= 3U) {
|
||||
const std::size_t node = row / 6U;
|
||||
position_entry +=
|
||||
z * shape.values[node] *
|
||||
rotations[node]
|
||||
.director_second[row % 6U - 3U][column % 6U - 3U]
|
||||
.Dot(acceleration_point);
|
||||
const double jacobian =
|
||||
Determinant(ref_xi, ref_eta, reference_zeta);
|
||||
const double current_jacobian =
|
||||
Determinant(current_xi, current_eta, current_zeta);
|
||||
if (!std::isfinite(jacobian) || !(jacobian > 0.0) ||
|
||||
!std::isfinite(current_jacobian) || !(current_jacobian > 0.0)) {
|
||||
return Failure<NonlinearMitc4InertiaEvaluation>(
|
||||
"invalid-nonlinear-inertia-geometry", source_location_,
|
||||
identity_,
|
||||
"Inertia requires positive reference and current mass-point "
|
||||
"Jacobians.");
|
||||
}
|
||||
for (std::size_t row = 6U * mass_node; row < 6U * (mass_node + 1U);
|
||||
++row) {
|
||||
velocity_point = velocity_point + h[row] * velocity[row];
|
||||
acceleration_point =
|
||||
acceleration_point + h[row] * acceleration[row];
|
||||
}
|
||||
acceleration_point = acceleration_point + convective_point;
|
||||
const double scale =
|
||||
density_ * weight * jacobian * shape.values[mass_node];
|
||||
if (!std::isfinite(scale) || !(scale > 0.0)) {
|
||||
return Failure<NonlinearMitc4InertiaEvaluation>(
|
||||
"invalid-nonlinear-inertia-positive-mass", source_location_,
|
||||
identity_,
|
||||
"Mass-point integration weight must be finite and positive.");
|
||||
}
|
||||
result.kinetic_energy +=
|
||||
0.5 * scale * velocity_point.Dot(velocity_point);
|
||||
for (std::size_t field = 6U * mass_node;
|
||||
field < 6U * (mass_node + 1U); ++field) {
|
||||
momentum_coefficients[field] = momentum_coefficients[field] + scale * h[field];
|
||||
angular_coefficients[field] =
|
||||
angular_coefficients[field] + scale * position.Cross(h[field]);
|
||||
}
|
||||
convective_momentum =
|
||||
convective_momentum + scale * convective_point;
|
||||
convective_angular =
|
||||
convective_angular + scale * position.Cross(convective_point);
|
||||
result.momentum_rate_scale += scale * acceleration_point.Norm();
|
||||
result.angular_momentum_rate_scale +=
|
||||
scale * position.Cross(acceleration_point).Norm();
|
||||
for (std::size_t row = 6U * mass_node; row < 6U * (mass_node + 1U);
|
||||
++row) {
|
||||
// Keep c independent of a; subtracting M*a destroys small c values.
|
||||
result.convective_force[row] +=
|
||||
scale * h[row].Dot(convective_point);
|
||||
for (std::size_t column = 6U * mass_node;
|
||||
column < 6U * (mass_node + 1U); ++column) {
|
||||
result.mass(row, column) += scale * h[row].Dot(h[column]);
|
||||
result.velocity_tangent(row, column) +=
|
||||
scale * h[row].Dot(velocity_derivative[column]);
|
||||
double position_entry = h[row].Dot(position_derivative[column]);
|
||||
if (row / 6U == column / 6U && row % 6U >= 3U &&
|
||||
column % 6U >= 3U) {
|
||||
const std::size_t node = row / 6U;
|
||||
position_entry +=
|
||||
z * kinetic_shape[node] *
|
||||
rotations[node]
|
||||
.director_second[row % 6U - 3U][column % 6U - 3U]
|
||||
.Dot(convective_point);
|
||||
}
|
||||
result.position_tangent(row, column) += scale * position_entry;
|
||||
if (column % 6U >= 3U) {
|
||||
const auto direction = column % 6U - 3U;
|
||||
const Vector3 dh_row =
|
||||
row % 6U < 3U
|
||||
? Vector3{}
|
||||
: z * rotations[mass_node]
|
||||
.director_second[row % 6U - 3U][direction];
|
||||
for (std::size_t local = 0U; local < 6U; ++local) {
|
||||
const auto field = 6U * mass_node + local;
|
||||
const Vector3 dh_field =
|
||||
local < 3U
|
||||
? Vector3{}
|
||||
: z * rotations[mass_node]
|
||||
.director_second[local - 3U][direction];
|
||||
mass_derivative[mass_node][direction][row % 6U][local] +=
|
||||
scale * (dh_row.Dot(h[field]) + h[row].Dot(dh_field));
|
||||
}
|
||||
}
|
||||
}
|
||||
result.position_tangent(row, column) += scale * position_entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Integrate M and c before contracting acceleration. Mixing a large
|
||||
// translation and tiny rotary acceleration at a mass point can lose the
|
||||
// latter before the odd-in-z translational moment cancels in integration.
|
||||
for (std::size_t row = 0U; row < kDofs; ++row) {
|
||||
InertiaSum sum;
|
||||
for (std::size_t column = 0U; column < kDofs; ++column)
|
||||
sum.Add(result.mass(row, column) * acceleration[column]);
|
||||
sum.Add(result.convective_force[row]);
|
||||
result.force[row] = sum.Value();
|
||||
}
|
||||
// DqI = (DqM)*a + Dqc. The integrated tangent above contains only Dqc;
|
||||
// its acceleration contribution uses the independently integrated DqM.
|
||||
for (std::size_t node = 0U; node < kNodes; ++node)
|
||||
for (std::size_t direction = 0U; direction < 3U; ++direction)
|
||||
for (std::size_t row = 0U; row < 6U; ++row) {
|
||||
InertiaSum sum;
|
||||
for (std::size_t field = 0U; field < 6U; ++field)
|
||||
sum.Add(mass_derivative[node][direction][row][field] *
|
||||
acceleration[6U * node + field]);
|
||||
sum.Add(result.position_tangent(6U * node + row,
|
||||
6U * node + 3U + direction));
|
||||
result.position_tangent(6U * node + row,
|
||||
6U * node + 3U + direction) = sum.Value();
|
||||
}
|
||||
std::array<double, 3U> momentum{};
|
||||
std::array<double, 3U> angular{};
|
||||
std::array<double, 3U> momentum_rate{};
|
||||
std::array<double, 3U> angular_rate{};
|
||||
for (std::size_t component = 0U; component < 3U; ++component) {
|
||||
const auto contract = [&](const std::array<Vector3, kDofs>& coefficients,
|
||||
const Vector& channel, const double extra) {
|
||||
InertiaSum sum;
|
||||
for (std::size_t field = 0U; field < kDofs; ++field)
|
||||
sum.Add(coefficients[field][component] * channel[field]);
|
||||
sum.Add(extra);
|
||||
return sum.Value();
|
||||
};
|
||||
momentum[component] = contract(momentum_coefficients, velocity, 0.0);
|
||||
angular[component] = contract(angular_coefficients, velocity, 0.0);
|
||||
momentum_rate[component] = contract(momentum_coefficients, acceleration,
|
||||
convective_momentum[component]);
|
||||
angular_rate[component] = contract(angular_coefficients, acceleration,
|
||||
convective_angular[component]);
|
||||
}
|
||||
result.momentum = Vector3{momentum};
|
||||
result.angular_momentum = Vector3{angular};
|
||||
result.momentum_rate = Vector3{momentum_rate};
|
||||
result.angular_momentum_rate = Vector3{angular_rate};
|
||||
if (!IsFinite(result.mass) || !IsFinite(result.position_tangent) ||
|
||||
!IsFinite(result.velocity_tangent) || !IsFinite(result.force) ||
|
||||
!IsFinite(result.convective_force) ||
|
||||
|
||||
@@ -26,6 +26,7 @@ using EngineeringStress = std::array<double, 5>;
|
||||
struct Geometry {
|
||||
std::array<Vector3, 3> reference_covariant;
|
||||
std::array<Vector3, 3> current_covariant;
|
||||
std::array<Vector3, 3> covariant_increment;
|
||||
std::array<Vector3, 3> reciprocal;
|
||||
Mitc4LocalFrame local_frame;
|
||||
double reference_jacobian;
|
||||
@@ -145,6 +146,31 @@ bool MakeLocalFrame(const Vector3& midsurface_xi, const Vector3& midsurface_eta,
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief Forms (R(phi)-I)D0 without adding and subtracting the baseline.
|
||||
Vector3 DirectorIncrement(const Vector3& phi, const Vector3& initial) {
|
||||
const double squared_norm = phi.Dot(phi);
|
||||
double first = 1.0;
|
||||
double second = 0.5;
|
||||
if (squared_norm < 0.25) {
|
||||
double power = 1.0;
|
||||
double first_coefficient = 1.0;
|
||||
double second_coefficient = 0.5;
|
||||
for (unsigned int k = 1U; k <= 9U; ++k) {
|
||||
power *= squared_norm;
|
||||
first_coefficient /= -static_cast<double>((2U * k) * (2U * k + 1U));
|
||||
second_coefficient /= -static_cast<double>((2U * k + 1U) * (2U * k + 2U));
|
||||
first += first_coefficient * power;
|
||||
second += second_coefficient * power;
|
||||
}
|
||||
} else {
|
||||
const double norm = std::sqrt(squared_norm);
|
||||
first = std::sin(norm) / norm;
|
||||
second = (1.0 - std::cos(norm)) / squared_norm;
|
||||
}
|
||||
const Vector3 crossed = phi.Cross(initial);
|
||||
return first * crossed + second * phi.Cross(crossed);
|
||||
}
|
||||
|
||||
/// @brief Evaluates fixed-reference and trial covariant geometry together.
|
||||
bool EvaluateGeometry(const std::array<Vector3, kNodeCount>& coordinates,
|
||||
const std::array<Vector3, kNodeCount>& initial_directors,
|
||||
@@ -169,6 +195,18 @@ bool EvaluateGeometry(const std::array<Vector3, kNodeCount>& coordinates,
|
||||
const Vector3 director_eta = DerivativeSum(shape.eta_derivatives, directors);
|
||||
const Vector3 director = WeightedSum(shape.values, directors);
|
||||
const double half_thickness = 0.5 * thickness;
|
||||
std::array<Vector3, kNodeCount> director_increment{};
|
||||
for (std::size_t node = 0U; node < kNodeCount; ++node)
|
||||
director_increment[node] =
|
||||
DirectorIncrement(trial.rotation_vector[node], initial_directors[node]);
|
||||
geometry.covariant_increment[0U] =
|
||||
displacement_xi + half_thickness * zeta *
|
||||
DerivativeSum(shape.xi_derivatives, director_increment);
|
||||
geometry.covariant_increment[1U] =
|
||||
displacement_eta + half_thickness * zeta *
|
||||
DerivativeSum(shape.eta_derivatives, director_increment);
|
||||
geometry.covariant_increment[2U] =
|
||||
half_thickness * WeightedSum(shape.values, director_increment);
|
||||
|
||||
geometry.reference_covariant[0U] =
|
||||
reference_xi + half_thickness * zeta * initial_director_xi;
|
||||
@@ -215,10 +253,13 @@ Tensor DirectStrain(const Geometry& geometry) {
|
||||
Tensor strain{};
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
strain[row][column] = 0.5 * (geometry.current_covariant[row].Dot(
|
||||
geometry.current_covariant[column]) -
|
||||
geometry.reference_covariant[row].Dot(
|
||||
geometry.reference_covariant[column]));
|
||||
strain[row][column] =
|
||||
0.5 * (geometry.reference_covariant[row].Dot(
|
||||
geometry.covariant_increment[column]) +
|
||||
geometry.covariant_increment[row].Dot(
|
||||
geometry.reference_covariant[column]) +
|
||||
geometry.covariant_increment[row].Dot(
|
||||
geometry.covariant_increment[column]));
|
||||
}
|
||||
}
|
||||
strain[2U][2U] = 0.0;
|
||||
@@ -278,8 +319,11 @@ EngineeringStress Stress(const EngineeringStrain& strain, const double youngs,
|
||||
shear * strain[4U]};
|
||||
}
|
||||
|
||||
/// @brief Distinguishes invalid geometry from arithmetic recovery failure.
|
||||
enum class PointStatus { kValid, kInvalidGeometry, kNonfiniteRecovery };
|
||||
|
||||
/// @brief Evaluates direct strain at one mandatory edge-midpoint tying point.
|
||||
bool EvaluateTyingSample(
|
||||
PointStatus EvaluateTyingSample(
|
||||
const std::array<Vector3, kNodeCount>& coordinates,
|
||||
const std::array<Vector3, kNodeCount>& initial_directors,
|
||||
const std::array<Vector3, kNodeCount>& directors,
|
||||
@@ -288,14 +332,14 @@ bool EvaluateTyingSample(
|
||||
Geometry geometry{};
|
||||
if (!EvaluateGeometry(coordinates, initial_directors, directors, trial,
|
||||
thickness, xi, eta, 0.0, geometry)) {
|
||||
return false;
|
||||
return PointStatus::kInvalidGeometry;
|
||||
}
|
||||
strain = DirectStrain(geometry);
|
||||
return IsFinite(strain);
|
||||
return IsFinite(strain) ? PointStatus::kValid : PointStatus::kNonfiniteRecovery;
|
||||
}
|
||||
|
||||
/// @brief Computes one MITC-projected local strain and material stress point.
|
||||
bool EvaluatePoint(const std::array<Vector3, kNodeCount>& coordinates,
|
||||
PointStatus EvaluatePoint(const std::array<Vector3, kNodeCount>& coordinates,
|
||||
const std::array<Vector3, kNodeCount>& initial_directors,
|
||||
const std::array<Vector3, kNodeCount>& directors,
|
||||
const NonlinearMitc4TrialDofs& trial,
|
||||
@@ -305,7 +349,7 @@ bool EvaluatePoint(const std::array<Vector3, kNodeCount>& coordinates,
|
||||
const double zeta, PointEvaluation& point) {
|
||||
if (!EvaluateGeometry(coordinates, initial_directors, directors, trial,
|
||||
thickness, xi, eta, zeta, point.geometry)) {
|
||||
return false;
|
||||
return PointStatus::kInvalidGeometry;
|
||||
}
|
||||
Tensor covariant = DirectStrain(point.geometry);
|
||||
const Mitc4TyingWeights weights = Mitc4Shell::TyingWeights(xi, eta);
|
||||
@@ -320,7 +364,9 @@ bool EvaluatePoint(const std::array<Vector3, kNodeCount>& coordinates,
|
||||
point.strain = LocalEngineering(cartesian, point.geometry.local_frame);
|
||||
point.stress = Stress(point.strain, youngs, poisson);
|
||||
return IsFinite(covariant) && IsFinite(cartesian) && IsFinite(point.strain) &&
|
||||
IsFinite(point.stress);
|
||||
IsFinite(point.stress)
|
||||
? PointStatus::kValid
|
||||
: PointStatus::kNonfiniteRecovery;
|
||||
}
|
||||
|
||||
/// @brief Tests the fixed-order physical recovery inventory for finite values.
|
||||
@@ -787,7 +833,8 @@ JetMatrix3 Inverse(const JetMatrix3& matrix) {
|
||||
}
|
||||
|
||||
/// @brief Evaluates Rodrigues' map with an analytic near-zero series branch.
|
||||
JetMatrix3 ExpSkew(const JetVector3& rotation) {
|
||||
JetMatrix3 ExpSkew(const JetVector3& rotation, const bool increment_only = false) {
|
||||
const JetMatrix3 identity = increment_only ? JetMatrix3{} : Identity();
|
||||
const Jet squared_norm = Dot(rotation, rotation);
|
||||
Jet sine_over_norm{};
|
||||
Jet one_minus_cosine_over_norm_squared{};
|
||||
@@ -807,12 +854,12 @@ JetMatrix3 ExpSkew(const JetVector3& rotation) {
|
||||
}
|
||||
const JetMatrix3 skew = Skew(rotation);
|
||||
JetMatrix3 result =
|
||||
Add(Add(Identity(), Scale(sine_over_norm, skew)),
|
||||
Add(Add(identity, Scale(sine_over_norm, skew)),
|
||||
Scale(one_minus_cosine_over_norm_squared, Multiply(skew, skew)));
|
||||
if (squared_norm.value < 0.25) {
|
||||
// Third derivatives amplify cancellation in 1-cos(theta). Preserve the
|
||||
// established static coefficients and use a stable series for the new
|
||||
// directional derivatives of the complete rotation tensor.
|
||||
// Near the original small-angle boundary, cancellation in 1-cos(theta)
|
||||
// also corrupts the physical-force gradient and Hessian. Use the existing
|
||||
// stable series consistently for the complete Jet and rotation tensor.
|
||||
Jet sine_series = Constant(1.0);
|
||||
Jet cosine_series = Constant(0.5);
|
||||
Jet power = Constant(1.0);
|
||||
@@ -825,18 +872,9 @@ JetMatrix3 ExpSkew(const JetVector3& rotation) {
|
||||
sine_series = sine_series + sine_coefficient * power;
|
||||
cosine_series = cosine_series + cosine_coefficient * power;
|
||||
}
|
||||
const JetMatrix3 stable = Add(Add(Identity(), Scale(sine_series, skew)),
|
||||
const JetMatrix3 stable = Add(Add(identity, Scale(sine_series, skew)),
|
||||
Scale(cosine_series, Multiply(skew, skew)));
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
result[row][column].directional_first =
|
||||
stable[row][column].directional_first;
|
||||
result[row][column].directional_second =
|
||||
stable[row][column].directional_second;
|
||||
result[row][column].mixed_first = stable[row][column].mixed_first;
|
||||
result[row][column].mixed_second = stable[row][column].mixed_second;
|
||||
}
|
||||
}
|
||||
result = stable;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -845,7 +883,7 @@ JetMatrix3 ExpSkew(const JetVector3& rotation) {
|
||||
struct JetTrial {
|
||||
std::array<JetVector3, kNodeCount> displacement;
|
||||
std::array<JetVector3, kNodeCount> rotation;
|
||||
std::array<JetVector3, kNodeCount> director;
|
||||
std::array<JetVector3, kNodeCount> director_increment;
|
||||
};
|
||||
|
||||
/// @brief Lifts one validated nonlinear trial into independent Jet variables.
|
||||
@@ -871,8 +909,8 @@ JetTrial MakeJetTrial(const NonlinearMitc4TrialDofs& trial,
|
||||
}
|
||||
const JetVector3 initial_director{frame[0U][2U], frame[1U][2U],
|
||||
frame[2U][2U]};
|
||||
result.director[node] =
|
||||
Multiply(ExpSkew(result.rotation[node]), initial_director);
|
||||
result.director_increment[node] =
|
||||
Multiply(ExpSkew(result.rotation[node], true), initial_director);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -882,13 +920,13 @@ std::array<JetVector3, 3> CovariantIncrement(
|
||||
const std::array<Vector3, kNodeCount>& initial_directors,
|
||||
const JetTrial& trial, const double thickness, const double xi,
|
||||
const double eta, const double zeta) {
|
||||
static_cast<void>(initial_directors);
|
||||
const auto shape = Mitc4Shell::ShapeFunctions(xi, eta);
|
||||
std::array<JetVector3, 3> increment{};
|
||||
for (std::size_t node = 0U; node < kNodeCount; ++node) {
|
||||
const auto displacement =
|
||||
Subtract(trial.displacement[node], trial.displacement[0U]);
|
||||
const auto director =
|
||||
Subtract(trial.director[node], ConstantVector(initial_directors[node]));
|
||||
const auto director = trial.director_increment[node];
|
||||
const auto layer =
|
||||
Add(displacement, Scale(0.5 * thickness * zeta, director));
|
||||
increment[0U] =
|
||||
@@ -1043,8 +1081,8 @@ JetMatrix3 PolarRotation(const JetMatrix3& deformation) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief Returns the speed of physical spin for a unit chart null vector.
|
||||
Jet DrillSpinRate(const JetVector3& phi, const Vector3& director) {
|
||||
/// @brief Returns the unnormalized physical drill-null chart vector.
|
||||
JetVector3 DrillNullPreimage(const JetVector3& phi, const Vector3& director) {
|
||||
const Jet square = Dot(phi, phi);
|
||||
Jet coefficient{};
|
||||
if (square.value < 0.25) {
|
||||
@@ -1069,7 +1107,19 @@ Jet DrillSpinRate(const JetVector3& phi, const Vector3& director) {
|
||||
const auto cross = Cross(phi, d0);
|
||||
const auto inverse_action =
|
||||
Add(Add(d0, Scale(0.5, cross)), Scale(coefficient, Cross(phi, cross)));
|
||||
return Reciprocal(Sqrt(Dot(inverse_action, inverse_action)));
|
||||
return inverse_action;
|
||||
}
|
||||
|
||||
/// @brief Returns the normalized physical drill-null chart vector.
|
||||
JetVector3 DrillNull(const JetVector3& phi, const Vector3& director) {
|
||||
const JetVector3 preimage = DrillNullPreimage(phi, director);
|
||||
return Scale(Reciprocal(Sqrt(Dot(preimage, preimage))), preimage);
|
||||
}
|
||||
|
||||
/// @brief Returns the speed of physical spin for a unit chart null vector.
|
||||
Jet DrillSpinRate(const JetVector3& phi, const Vector3& director) {
|
||||
const JetVector3 preimage = DrillNullPreimage(phi, director);
|
||||
return Reciprocal(Sqrt(Dot(preimage, preimage)));
|
||||
}
|
||||
|
||||
/// @brief Evaluates the objective numerical drilling potential and variations.
|
||||
@@ -1332,14 +1382,17 @@ Result<NonlinearMitc4Recovery> NonlinearMitc4Shell::EvaluateKinematics(
|
||||
std::array<double, 2>{-1.0, 0.0}, std::array<double, 2>{1.0, 0.0}};
|
||||
std::array<Tensor, 4> tying_samples{};
|
||||
for (std::size_t point = 0U; point < tying_locations.size(); ++point) {
|
||||
if (!EvaluateTyingSample(coordinates_, initial_directors_, directors, trial,
|
||||
thickness_, tying_locations[point][0U],
|
||||
tying_locations[point][1U],
|
||||
tying_samples[point])) {
|
||||
const auto point_status = EvaluateTyingSample(
|
||||
coordinates_, initial_directors_, directors, trial, thickness_,
|
||||
tying_locations[point][0U], tying_locations[point][1U],
|
||||
tying_samples[point]);
|
||||
if (point_status != PointStatus::kValid) {
|
||||
return Failure<NonlinearMitc4Recovery>(
|
||||
"invalid-nonlinear-shell-geometry", source_location_, identity_,
|
||||
"Nonlinear MITC4 tying-point reference or trial geometry is "
|
||||
"invalid.");
|
||||
point_status == PointStatus::kInvalidGeometry
|
||||
? "invalid-nonlinear-shell-geometry"
|
||||
: "invalid-nonlinear-shell-recovery",
|
||||
source_location_, identity_,
|
||||
"Nonlinear MITC4 tying-point geometry or strain is invalid.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1374,14 +1427,18 @@ Result<NonlinearMitc4Recovery> NonlinearMitc4Shell::EvaluateKinematics(
|
||||
|
||||
for (const double thickness_sign : thickness_gauss) {
|
||||
PointEvaluation point{};
|
||||
if (!EvaluatePoint(coordinates_, initial_directors_, directors, trial,
|
||||
tying_samples, thickness_, youngs_modulus_,
|
||||
poisson_ratio_, row.natural_coordinates[0U],
|
||||
row.natural_coordinates[1U], thickness_sign * gauss,
|
||||
point)) {
|
||||
const auto point_status = EvaluatePoint(
|
||||
coordinates_, initial_directors_, directors, trial, tying_samples,
|
||||
thickness_, youngs_modulus_, poisson_ratio_,
|
||||
row.natural_coordinates[0U], row.natural_coordinates[1U],
|
||||
thickness_sign * gauss, point);
|
||||
if (point_status != PointStatus::kValid) {
|
||||
return Failure<NonlinearMitc4Recovery>(
|
||||
"invalid-nonlinear-shell-geometry", source_location_, identity_,
|
||||
"Nonlinear MITC4 volume recovery geometry is invalid.");
|
||||
point_status == PointStatus::kInvalidGeometry
|
||||
? "invalid-nonlinear-shell-geometry"
|
||||
: "invalid-nonlinear-shell-recovery",
|
||||
source_location_, identity_,
|
||||
"Nonlinear MITC4 volume recovery geometry or values are invalid.");
|
||||
}
|
||||
for (std::size_t component = 0U; component < 3U; ++component) {
|
||||
row.generalized_strain[component] += 0.5 * point.strain[component];
|
||||
@@ -1410,14 +1467,18 @@ Result<NonlinearMitc4Recovery> NonlinearMitc4Shell::EvaluateKinematics(
|
||||
|
||||
for (std::size_t position = 0U; position < stress_zeta.size(); ++position) {
|
||||
PointEvaluation point{};
|
||||
if (!EvaluatePoint(coordinates_, initial_directors_, directors, trial,
|
||||
tying_samples, thickness_, youngs_modulus_,
|
||||
poisson_ratio_, row.natural_coordinates[0U],
|
||||
row.natural_coordinates[1U], stress_zeta[position],
|
||||
point)) {
|
||||
const auto point_status = EvaluatePoint(
|
||||
coordinates_, initial_directors_, directors, trial, tying_samples,
|
||||
thickness_, youngs_modulus_, poisson_ratio_,
|
||||
row.natural_coordinates[0U], row.natural_coordinates[1U],
|
||||
stress_zeta[position], point);
|
||||
if (point_status != PointStatus::kValid) {
|
||||
return Failure<NonlinearMitc4Recovery>(
|
||||
"invalid-nonlinear-shell-geometry", source_location_, identity_,
|
||||
"Nonlinear MITC4 section-position recovery geometry is invalid.");
|
||||
point_status == PointStatus::kInvalidGeometry
|
||||
? "invalid-nonlinear-shell-geometry"
|
||||
: "invalid-nonlinear-shell-recovery",
|
||||
source_location_, identity_,
|
||||
"Nonlinear MITC4 section recovery geometry or values are invalid.");
|
||||
}
|
||||
row.stress[position] = {
|
||||
stress_position[position],
|
||||
@@ -1439,6 +1500,97 @@ Result<NonlinearMitc4Recovery> NonlinearMitc4Shell::RecoverState(
|
||||
return EvaluateKinematics(trial);
|
||||
}
|
||||
|
||||
Result<NonlinearMitc4ExternalDrillingConstraint>
|
||||
NonlinearMitc4Shell::EvaluateExternalDrillingConstraint(
|
||||
const std::size_t node, const Vector3& phi, const Vector3& phi_dot,
|
||||
const Vector3& force, const Vector3& force_rate,
|
||||
const Vector3& force_acceleration) const {
|
||||
if (node >= kNodeCount || !phi_dot.IsFinite() || !force.IsFinite() ||
|
||||
!force_rate.IsFinite() || !force_acceleration.IsFinite()) {
|
||||
return Failure<NonlinearMitc4ExternalDrillingConstraint>(
|
||||
"invalid-nonlinear-shell-external-constraint", source_location_,
|
||||
identity_, "External drilling constraint inputs must be finite and "
|
||||
"identify one source-local node.");
|
||||
}
|
||||
const auto rotation = MakeFiniteRotationState(phi, initial_frames_[node]);
|
||||
if (!rotation.HasValue()) {
|
||||
return Failure<NonlinearMitc4ExternalDrillingConstraint>(
|
||||
"invalid-nonlinear-shell-rotation", source_location_, identity_,
|
||||
"External drilling constraint rotations must remain in the principal "
|
||||
"chart.");
|
||||
}
|
||||
|
||||
JetVector3 jet_phi{};
|
||||
for (std::size_t component = 0U; component < 3U; ++component) {
|
||||
jet_phi[component] = Variable(phi[component], component,
|
||||
phi_dot[component]);
|
||||
}
|
||||
const JetVector3 null_direction = DrillNull(jet_phi, initial_directors_[node]);
|
||||
const Jet scalar = -1.0 * Dot(null_direction, ConstantVector(force));
|
||||
const Jet scalar_time =
|
||||
-1.0 * Dot(null_direction, ConstantVector(force_rate));
|
||||
const Jet scalar_acceleration =
|
||||
-1.0 * Dot(null_direction, ConstantVector(force_acceleration));
|
||||
if (!IsFinite(scalar) || !IsFinite(scalar_time) ||
|
||||
!IsFinite(scalar_acceleration)) {
|
||||
return Failure<NonlinearMitc4ExternalDrillingConstraint>(
|
||||
"invalid-nonlinear-shell-external-constraint", source_location_,
|
||||
identity_, "External drilling constraint derivatives must remain "
|
||||
"finite.");
|
||||
}
|
||||
|
||||
NonlinearMitc4ExternalDrillingConstraint result{};
|
||||
result.value = scalar.value;
|
||||
result.gradient = Vector{3U};
|
||||
result.hessian = Matrix{3U, 3U};
|
||||
result.gradient_directional_first = Vector{3U};
|
||||
result.gradient_directional_second = Vector{3U};
|
||||
result.directional_first =
|
||||
scalar.directional_first + scalar_time.value;
|
||||
result.directional_second = scalar.directional_second +
|
||||
2.0 * scalar_time.directional_first +
|
||||
scalar_acceleration.value;
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
result.gradient[row] = scalar.first[row];
|
||||
result.gradient_directional_first[row] =
|
||||
scalar.mixed_first[row] + scalar_time.first[row];
|
||||
result.gradient_directional_second[row] =
|
||||
scalar.mixed_second[row] +
|
||||
2.0 * scalar_time.mixed_first[row] + scalar_acceleration.first[row];
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
result.hessian(row, column) =
|
||||
scalar.second[row * kGlobalDofCount + column];
|
||||
}
|
||||
}
|
||||
if (!std::isfinite(result.value) || !std::isfinite(result.directional_first) ||
|
||||
!std::isfinite(result.directional_second)) {
|
||||
return Failure<NonlinearMitc4ExternalDrillingConstraint>(
|
||||
"invalid-nonlinear-shell-external-constraint", source_location_,
|
||||
identity_, "External drilling constraint derivatives must remain "
|
||||
"finite.");
|
||||
}
|
||||
for (std::size_t row = 0U; row < 3U; ++row) {
|
||||
if (!std::isfinite(result.gradient[row]) ||
|
||||
!std::isfinite(result.gradient_directional_first[row]) ||
|
||||
!std::isfinite(result.gradient_directional_second[row])) {
|
||||
return Failure<NonlinearMitc4ExternalDrillingConstraint>(
|
||||
"invalid-nonlinear-shell-external-constraint", source_location_,
|
||||
identity_, "External drilling constraint derivatives must remain "
|
||||
"finite.");
|
||||
}
|
||||
for (std::size_t column = 0U; column < 3U; ++column) {
|
||||
if (!std::isfinite(result.hessian(row, column))) {
|
||||
return Failure<NonlinearMitc4ExternalDrillingConstraint>(
|
||||
"invalid-nonlinear-shell-external-constraint", source_location_,
|
||||
identity_, "External drilling constraint derivatives must remain "
|
||||
"finite.");
|
||||
}
|
||||
}
|
||||
}
|
||||
return Result<NonlinearMitc4ExternalDrillingConstraint>::Success(
|
||||
std::move(result));
|
||||
}
|
||||
|
||||
Result<NonlinearMitc4InternalEquilibrium> EvaluateInternalResidualAndTangent(
|
||||
const NonlinearMitc4Shell& element, const NonlinearMitc4TrialDofs& trial,
|
||||
const Vector* coordinate_direction) {
|
||||
@@ -1526,9 +1678,13 @@ Result<NonlinearMitc4InternalEquilibrium> EvaluateInternalResidualAndTangent(
|
||||
equilibrium.drilling_constraint[node] = constraints[node].value;
|
||||
equilibrium.drilling_constraint_hessian[node] =
|
||||
Matrix{kGlobalDofCount, kGlobalDofCount};
|
||||
equilibrium.drilling_constraint_third_directional_gradient[node] =
|
||||
Vector{kGlobalDofCount};
|
||||
for (std::size_t i = 0U; i < kGlobalDofCount; ++i) {
|
||||
equilibrium.drilling_constraint_gradient(node, i) =
|
||||
constraints[node].first[i];
|
||||
equilibrium.drilling_constraint_third_directional_gradient[node][i] =
|
||||
constraints[node].mixed_second[i];
|
||||
for (std::size_t j = 0U; j < kGlobalDofCount; ++j)
|
||||
equilibrium.drilling_constraint_hessian[node](i, j) =
|
||||
constraints[node].second[i * kGlobalDofCount + j];
|
||||
|
||||
@@ -241,8 +241,24 @@ void DeleteAttribute(const hid_t object, const char* name) {
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Maps a validated frame's integration semantics to its schema.
|
||||
std::uint64_t SchemaVersion(const DynamicAcceptedFrameState& frame) {
|
||||
return frame.time_integration_formulation ==
|
||||
DynamicTimeIntegrationFormulation::kReducedCoordinateHht
|
||||
? 2U
|
||||
: 1U;
|
||||
}
|
||||
|
||||
/// @brief Returns the explicit integration discriminator without aliases.
|
||||
const char* IntegrationFormulation(const DynamicAcceptedFrameState& frame) {
|
||||
return SchemaVersion(frame) == 2U
|
||||
? "reduced-coordinate-HHT-with-physical-rate-reconstruction"
|
||||
: "full-coordinate-HHT";
|
||||
}
|
||||
|
||||
/// @brief Writes the dynamic procedure identity and fixed time schedule.
|
||||
void WriteMetadata(const hid_t file, const Domain& domain) {
|
||||
void WriteMetadata(const hid_t file, const Domain& domain,
|
||||
const DynamicAcceptedFrameState& frame) {
|
||||
Hdf5Handle metadata{
|
||||
hdf5_internal::RequireHdf5Id(H5Gopen2(file, "/metadata", H5P_DEFAULT),
|
||||
"The model metadata group is missing."),
|
||||
@@ -252,7 +268,7 @@ void WriteMetadata(const hid_t file, const Domain& domain) {
|
||||
"mass_formulation", "internal_formulation", "initial_state",
|
||||
"acceleration_convention", "step_name", "source_step_name",
|
||||
"unit_system_label", "coordinate_convention", "integration_rule",
|
||||
"frame_index"}) {
|
||||
"frame_index", "time_integration_formulation"}) {
|
||||
DeleteAttribute(metadata.Get(), name);
|
||||
}
|
||||
const DynamicStepDefinition* const step = domain.Dynamic();
|
||||
@@ -260,9 +276,13 @@ void WriteMetadata(const hid_t file, const Domain& domain) {
|
||||
"hht-implicit-dynamics");
|
||||
hdf5_internal::WriteStringAttribute(metadata.Get(), "analysis_procedure",
|
||||
"hht-implicit-dynamics");
|
||||
hdf5_internal::WriteUint64Attribute(metadata.Get(), "schema_version", 1U);
|
||||
hdf5_internal::WriteUint64Attribute(metadata.Get(), "schema_version",
|
||||
SchemaVersion(frame));
|
||||
hdf5_internal::WriteStringAttribute(metadata.Get(),
|
||||
"time_integration_formulation",
|
||||
IntegrationFormulation(frame));
|
||||
hdf5_internal::WriteStringAttribute(metadata.Get(), "mass_formulation",
|
||||
"consistent-kinetic");
|
||||
"nodal-lumped-kinetic");
|
||||
hdf5_internal::WriteStringAttribute(metadata.Get(), "internal_formulation",
|
||||
"FESA-MITC4-total-lagrangian");
|
||||
hdf5_internal::WriteStringAttribute(metadata.Get(), "initial_state",
|
||||
@@ -292,7 +312,8 @@ void WriteMetadata(const hid_t file, const Domain& domain) {
|
||||
/// @brief Records exact accepted endpoint identity on a result dataset.
|
||||
void WriteFrameAttributes(const hid_t dataset,
|
||||
const DynamicAcceptedFrameState& frame) {
|
||||
hdf5_internal::WriteUint64Attribute(dataset, "schema_version", 1U);
|
||||
hdf5_internal::WriteUint64Attribute(dataset, "schema_version",
|
||||
SchemaVersion(frame));
|
||||
hdf5_internal::WriteStringAttribute(dataset, "step_name", "Step-1");
|
||||
hdf5_internal::WriteUint64Attribute(dataset, "frame_index",
|
||||
frame.frame_index);
|
||||
@@ -329,7 +350,7 @@ void ProcessDataset(const hid_t file, const std::string& path,
|
||||
string_attribute("source_step_name", source_step);
|
||||
if (readback) {
|
||||
CheckAttribute(dataset.Get(), "schema_version", H5T_STD_U64LE,
|
||||
H5T_NATIVE_UINT64, std::uint64_t{1U});
|
||||
H5T_NATIVE_UINT64, SchemaVersion(frame));
|
||||
CheckString(dataset.Get(), "step_name", "Step-1");
|
||||
CheckAttribute(dataset.Get(), "frame_index", H5T_STD_U64LE,
|
||||
H5T_NATIVE_UINT64, std::uint64_t{frame.frame_index});
|
||||
@@ -354,7 +375,9 @@ void ProcessDataset(const hid_t file, const std::string& path,
|
||||
leaf == "displacement" ? "global-translation-and-total-rotation-vector"
|
||||
: leaf == "velocity" ? "global-translation-rate-and-chart-rate"
|
||||
: leaf == "acceleration" ? "physical-endpoint-chart-acceleration"
|
||||
: "auxiliary-HHT-chart-acceleration");
|
||||
: SchemaVersion(frame) == 2U
|
||||
? "full-chart-HHT-filter-combination-not-predictor-driver"
|
||||
: "auxiliary-HHT-chart-acceleration");
|
||||
}
|
||||
if (leaf == "triad") string_attribute("axis_names", "A,B,D");
|
||||
if (path.find("/element/shell/") != std::string::npos) {
|
||||
@@ -886,7 +909,7 @@ Status SelfCheck(const std::filesystem::path& path, const Domain& domain,
|
||||
const auto& step = *domain.Dynamic();
|
||||
CheckString(metadata.Get(), "feature_id", "hht-implicit-dynamics");
|
||||
CheckString(metadata.Get(), "analysis_procedure", "hht-implicit-dynamics");
|
||||
CheckString(metadata.Get(), "mass_formulation", "consistent-kinetic");
|
||||
CheckString(metadata.Get(), "mass_formulation", "nodal-lumped-kinetic");
|
||||
CheckString(metadata.Get(), "internal_formulation",
|
||||
"FESA-MITC4-total-lagrangian");
|
||||
CheckString(metadata.Get(), "initial_state", "zero-q-v-step-load-0+");
|
||||
@@ -899,7 +922,10 @@ Status SelfCheck(const std::filesystem::path& path, const Domain& domain,
|
||||
CheckString(metadata.Get(), "source_input_identity",
|
||||
hdf5_internal::SourceInputIdentity(domain));
|
||||
CheckAttribute(metadata.Get(), "schema_version", H5T_STD_U64LE,
|
||||
H5T_NATIVE_UINT64, std::uint64_t{1U});
|
||||
H5T_NATIVE_UINT64,
|
||||
SchemaVersion(state.AcceptedFrames().front()));
|
||||
CheckString(metadata.Get(), "time_integration_formulation",
|
||||
IntegrationFormulation(state.AcceptedFrames().front()));
|
||||
CheckAttribute(metadata.Get(), "increment_count", H5T_STD_U64LE,
|
||||
H5T_NATIVE_UINT64, std::uint64_t{step.increment_count});
|
||||
CheckAttribute(metadata.Get(), "maximum_increments", H5T_STD_U64LE,
|
||||
@@ -988,7 +1014,7 @@ Status DynamicHdf5ResultsWriter::Write(
|
||||
H5Fclose};
|
||||
(void)hdf5_internal::CreateGroup(file.Get(), "/model/shell");
|
||||
hdf5_internal::WriteModel(file.Get(), domain, model);
|
||||
WriteMetadata(file.Get(), domain);
|
||||
WriteMetadata(file.Get(), domain, state.AcceptedFrames().front());
|
||||
WriteDensity(file.Get(), domain);
|
||||
(void)hdf5_internal::CreateGroup(file.Get(), "/steps");
|
||||
(void)hdf5_internal::CreateGroup(file.Get(), "/steps/Step-1");
|
||||
|
||||
@@ -349,7 +349,7 @@ Result<SolidPlan> BuildSolidPlan(const Domain& domain, SolidStateView view,
|
||||
: "linear-static"},
|
||||
{"element_formulation", "FESA-Hex8"},
|
||||
{"integration_rule", "gauss-2x2x2"},
|
||||
{"mass_formulation", "consistent-reference"},
|
||||
{"mass_formulation", "lumped-reference"},
|
||||
{"source_input_identity", SourceInputIdentity(domain)},
|
||||
{"source_step_name", domain.Steps()[0].Name()},
|
||||
{"unit_system_label", "user-consistent-unspecified"},
|
||||
|
||||
Reference in New Issue
Block a user