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:
@@ -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
|
||||
Reference in New Issue
Block a user