Integrate Refactor solver sources and dual MSVC CMake builds
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
#ifndef FESA_ANALYSIS_ANALYSIS_PROCEDURES_H_
|
||||
#define FESA_ANALYSIS_ANALYSIS_PROCEDURES_H_
|
||||
|
||||
#include "fesa/analysis/analysis_procedure.h"
|
||||
#include "fesa/analysis/hht_dynamic_procedure.h"
|
||||
#include "fesa/analysis/linear_frequency_procedure.h"
|
||||
#include "fesa/analysis/linear_static_procedure.h"
|
||||
#include "fesa/analysis/nonlinear_static_procedure.h"
|
||||
#include "fesa/analysis/prepared_analysis_outcome.h"
|
||||
#include "fesa/analysis/solid_frequency_procedure.h"
|
||||
#include "fesa/analysis/solid_hht_dynamic_procedure.h"
|
||||
#include "fesa/analysis/solid_static_procedure.h"
|
||||
#include "fesa/analysis/transient_heat_procedure.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
class EigenSolver;
|
||||
|
||||
/// @brief Adapts the LinearStatic lifecycle to prepared execution.
|
||||
/// @note Referenced backend and log adapters must outlive this procedure.
|
||||
class LinearStaticProcedure final : public AnalysisProcedure {
|
||||
public:
|
||||
/// @brief Injects only the adapters required by this family.
|
||||
LinearStaticProcedure(const ParallelFor& parallel_for,
|
||||
LinearSolver& linear_solver)
|
||||
: executor_(parallel_for, linear_solver) {}
|
||||
/// @brief Retains compatibility with an already configured typed executor.
|
||||
explicit LinearStaticProcedure(LinearStaticProcedureExecutor executor)
|
||||
: executor_(executor) {}
|
||||
Result<PreparedAnalysisOutcome> Execute(const Domain& domain) override;
|
||||
|
||||
private:
|
||||
LinearStaticProcedureExecutor executor_;
|
||||
};
|
||||
|
||||
/// @brief Adapts the NonlinearStatic lifecycle to prepared execution.
|
||||
/// @note Referenced backend and log adapters must outlive this procedure.
|
||||
class NonlinearStaticProcedure final : public AnalysisProcedure {
|
||||
public:
|
||||
/// @brief Injects only the adapters required by this family.
|
||||
NonlinearStaticProcedure(const ParallelFor& parallel_for,
|
||||
TangentSolver& tangent_solver,
|
||||
NewtonIterationLogWriter& log)
|
||||
: executor_(parallel_for, tangent_solver), log_(log) {}
|
||||
/// @brief Retains compatibility with an already configured typed executor.
|
||||
explicit NonlinearStaticProcedure(NonlinearStaticProcedureExecutor executor,
|
||||
NewtonIterationLogWriter& log)
|
||||
: executor_(executor), log_(log) {}
|
||||
Result<PreparedAnalysisOutcome> Execute(const Domain& domain) override;
|
||||
|
||||
private:
|
||||
NonlinearStaticProcedureExecutor executor_;
|
||||
NewtonIterationLogWriter& log_;
|
||||
};
|
||||
|
||||
/// @brief Adapts the LinearFrequency lifecycle to prepared execution.
|
||||
/// @note Referenced backend and log adapters must outlive this procedure.
|
||||
class LinearFrequencyProcedure final : public AnalysisProcedure {
|
||||
public:
|
||||
/// @brief Injects only the adapters required by this family.
|
||||
LinearFrequencyProcedure(const EigenSolver& eigen_solver)
|
||||
: executor_(eigen_solver) {}
|
||||
/// @brief Retains compatibility with an already configured typed executor.
|
||||
explicit LinearFrequencyProcedure(LinearFrequencyProcedureExecutor executor)
|
||||
: executor_(executor) {}
|
||||
Result<PreparedAnalysisOutcome> Execute(const Domain& domain) override;
|
||||
|
||||
private:
|
||||
LinearFrequencyProcedureExecutor executor_;
|
||||
};
|
||||
|
||||
/// @brief Adapts the HhtDynamic lifecycle to prepared execution.
|
||||
/// @note Referenced backend and log adapters must outlive this procedure.
|
||||
class HhtDynamicProcedure final : public AnalysisProcedure {
|
||||
public:
|
||||
/// @brief Injects only the adapters required by this family.
|
||||
HhtDynamicProcedure(const ParallelFor& parallel_for,
|
||||
LinearSolver& positive_solver,
|
||||
TangentSolver& tangent_solver,
|
||||
NewtonIterationLogWriter& log)
|
||||
: executor_(parallel_for, positive_solver, tangent_solver), log_(log) {}
|
||||
/// @brief Retains compatibility with an already configured typed executor.
|
||||
explicit HhtDynamicProcedure(HhtDynamicProcedureExecutor executor,
|
||||
NewtonIterationLogWriter& log)
|
||||
: executor_(executor), log_(log) {}
|
||||
Result<PreparedAnalysisOutcome> Execute(const Domain& domain) override;
|
||||
|
||||
private:
|
||||
HhtDynamicProcedureExecutor executor_;
|
||||
NewtonIterationLogWriter& log_;
|
||||
};
|
||||
|
||||
/// @brief Adapts the SolidStatic lifecycle to prepared execution.
|
||||
/// @note Referenced backend and log adapters must outlive this procedure.
|
||||
class SolidStaticProcedure final : public AnalysisProcedure {
|
||||
public:
|
||||
/// @brief Injects only the adapters required by this family.
|
||||
SolidStaticProcedure(const ParallelFor& parallel_for,
|
||||
LinearSolver& linear_solver,
|
||||
TangentSolver& tangent_solver,
|
||||
NewtonIterationLogWriter& log)
|
||||
: executor_(parallel_for, linear_solver, tangent_solver), log_(log) {}
|
||||
/// @brief Retains compatibility with an already configured typed executor.
|
||||
explicit SolidStaticProcedure(SolidStaticProcedureExecutor executor,
|
||||
NewtonIterationLogWriter& log)
|
||||
: executor_(executor), log_(log) {}
|
||||
Result<PreparedAnalysisOutcome> Execute(const Domain& domain) override;
|
||||
|
||||
private:
|
||||
SolidStaticProcedureExecutor executor_;
|
||||
NewtonIterationLogWriter& log_;
|
||||
};
|
||||
|
||||
/// @brief Adapts the SolidFrequency lifecycle to prepared execution.
|
||||
/// @note Referenced backend and log adapters must outlive this procedure.
|
||||
class SolidFrequencyProcedure final : public AnalysisProcedure {
|
||||
public:
|
||||
/// @brief Injects only the adapters required by this family.
|
||||
SolidFrequencyProcedure(const EigenSolver& eigen_solver)
|
||||
: executor_(eigen_solver) {}
|
||||
/// @brief Retains compatibility with an already configured typed executor.
|
||||
explicit SolidFrequencyProcedure(SolidFrequencyProcedureExecutor executor)
|
||||
: executor_(executor) {}
|
||||
Result<PreparedAnalysisOutcome> Execute(const Domain& domain) override;
|
||||
|
||||
private:
|
||||
SolidFrequencyProcedureExecutor executor_;
|
||||
};
|
||||
|
||||
/// @brief Adapts the SolidHhtDynamic lifecycle to prepared execution.
|
||||
/// @note Referenced backend and log adapters must outlive this procedure.
|
||||
class SolidHhtDynamicProcedure final : public AnalysisProcedure {
|
||||
public:
|
||||
/// @brief Injects only the adapters required by this family.
|
||||
SolidHhtDynamicProcedure(const ParallelFor& parallel_for,
|
||||
LinearSolver& positive_solver,
|
||||
TangentSolver& tangent_solver,
|
||||
NewtonIterationLogWriter& log)
|
||||
: executor_(parallel_for, positive_solver, tangent_solver), log_(log) {}
|
||||
/// @brief Retains compatibility with an already configured typed executor.
|
||||
explicit SolidHhtDynamicProcedure(SolidHhtDynamicProcedureExecutor executor,
|
||||
NewtonIterationLogWriter& log)
|
||||
: executor_(executor), log_(log) {}
|
||||
Result<PreparedAnalysisOutcome> Execute(const Domain& domain) override;
|
||||
|
||||
private:
|
||||
SolidHhtDynamicProcedureExecutor executor_;
|
||||
NewtonIterationLogWriter& log_;
|
||||
};
|
||||
|
||||
/// @brief Adapts the TransientHeat lifecycle to prepared execution.
|
||||
/// @note Referenced backend and log adapters must outlive this procedure.
|
||||
class TransientHeatProcedure final : public AnalysisProcedure {
|
||||
public:
|
||||
/// @brief Injects only the adapters required by this family.
|
||||
TransientHeatProcedure(const ParallelFor& parallel_for,
|
||||
LinearSolver& linear_solver)
|
||||
: executor_(parallel_for, linear_solver) {}
|
||||
/// @brief Retains compatibility with an already configured typed executor.
|
||||
explicit TransientHeatProcedure(TransientHeatProcedureExecutor executor)
|
||||
: executor_(executor) {}
|
||||
Result<PreparedAnalysisOutcome> Execute(const Domain& domain) override;
|
||||
|
||||
private:
|
||||
TransientHeatProcedureExecutor executor_;
|
||||
};
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_ANALYSIS_ANALYSIS_PROCEDURES_H_
|
||||
@@ -7,9 +7,15 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "fesa/fem/dof_manager.h"
|
||||
#include "math/finite_values.h"
|
||||
#include "results/shell_result_recovery.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
using math_internal::IsFinite;
|
||||
|
||||
constexpr std::size_t kShellLocationsPerElement = 4U;
|
||||
|
||||
/// @brief Creates a structured failure without mutating existing state.
|
||||
@@ -21,31 +27,6 @@ Status ShellCandidateFailure(const std::string& code,
|
||||
{{Severity::kError, code, {}, "ANALYSIS_STATE", identity, message}});
|
||||
}
|
||||
|
||||
template <std::size_t Size>
|
||||
/// @brief Tests one fixed-size candidate component array for finite values.
|
||||
bool IsFinite(const std::array<double, Size>& values) {
|
||||
return std::all_of(values.begin(), values.end(),
|
||||
[](const double value) { return std::isfinite(value); });
|
||||
}
|
||||
|
||||
/// @brief Tests every physical shell row component for finite values.
|
||||
bool IsFinite(const ShellResultRow& row) {
|
||||
if (!IsFinite(row.natural_coordinates) || !IsFinite(row.generalized_strain) ||
|
||||
!IsFinite(row.section_resultant)) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& axis : row.local_frame) {
|
||||
if (!IsFinite(axis)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return std::all_of(row.stress.begin(), row.stress.end(),
|
||||
[](const ShellSectionStressRow& stress) {
|
||||
return std::isfinite(stress.zeta) &&
|
||||
IsFinite(stress.components);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AnalysisState AnalysisState::Create(const DofManager& dofs,
|
||||
@@ -170,7 +151,7 @@ Status AnalysisState::CommitShellResults(
|
||||
"Shell stress rows require BOTTOM, MIDDLE, TOP identity.");
|
||||
}
|
||||
}
|
||||
if (!IsFinite(row)) {
|
||||
if (!results_internal::IsFiniteShellRow(row)) {
|
||||
return ShellCandidateFailure(
|
||||
"nonfinite-shell-state-value", std::to_string(row.element),
|
||||
"Shell result rows must contain only finite values.");
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
#include "fesa/analysis/dynamic_analysis_state.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "fesa/core/source_identity.h"
|
||||
#include "fesa/elements/element_definition.h"
|
||||
#include "fesa/elements/finite_rotation.h"
|
||||
#include "math/finite_values.h"
|
||||
#include "results/shell_result_recovery.h"
|
||||
#include "analysis/shell_state_inventory.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
using analysis_internal::ShellElementOrder;
|
||||
using math_internal::IsFinite;
|
||||
|
||||
constexpr std::size_t kDofsPerNode = 6U;
|
||||
constexpr std::size_t kAngularDofsPerNode = 3U;
|
||||
constexpr std::size_t kShellLocationsPerElement = 4U;
|
||||
@@ -28,59 +34,6 @@ Status Failure(const FailureCategory category, const std::string& message) {
|
||||
message}});
|
||||
}
|
||||
|
||||
/// @brief Compares canonical and raw source identity exactly.
|
||||
bool SameId(const SourceEntityId& left, const SourceEntityId& right) {
|
||||
return left.instance_name == right.instance_name &&
|
||||
left.source_label == right.source_label &&
|
||||
left.source_label_text == right.source_label_text;
|
||||
}
|
||||
|
||||
/// @brief Checks all components of one fixed-size physical record.
|
||||
template <std::size_t Size>
|
||||
bool IsFinite(const std::array<double, Size>& values) {
|
||||
return std::all_of(values.begin(), values.end(),
|
||||
[](double value) { return std::isfinite(value); });
|
||||
}
|
||||
|
||||
/// @brief Checks every vector component before state acceptance.
|
||||
bool IsFinite(const Vector& values) {
|
||||
for (std::size_t index = 0U; index < values.Size(); ++index) {
|
||||
if (!std::isfinite(values[index])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief Checks all physical recovery quantities for finite values.
|
||||
bool IsFinite(const ShellResultRow& row) {
|
||||
if (!IsFinite(row.natural_coordinates) || !IsFinite(row.generalized_strain) ||
|
||||
!IsFinite(row.section_resultant)) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& axis : row.local_frame) {
|
||||
if (!IsFinite(axis)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return std::all_of(row.stress.begin(), row.stress.end(),
|
||||
[](const ShellSectionStressRow& stress) {
|
||||
return std::isfinite(stress.zeta) &&
|
||||
IsFinite(stress.components);
|
||||
});
|
||||
}
|
||||
|
||||
/// @brief Collects stable Domain indices for recovery identity.
|
||||
std::vector<EntityIndex> ShellElementOrder(const Domain& domain) {
|
||||
std::vector<EntityIndex> order;
|
||||
for (std::size_t index = 0U; index < domain.Elements().Size(); ++index) {
|
||||
if (domain.Elements()[index].Kind() == ElementDefinitionKind::kMitc4Shell) {
|
||||
order.push_back(static_cast<EntityIndex>(index));
|
||||
}
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
/// @brief Requires complete ordered Gauss and section-position rows.
|
||||
Status ValidateShellRows(const DynamicAcceptedFrameState& frame,
|
||||
const std::vector<EntityIndex>& shell_order) {
|
||||
@@ -110,7 +63,7 @@ Status ValidateShellRows(const DynamicAcceptedFrameState& frame,
|
||||
frame.shell_results[element * kShellLocationsPerElement + point];
|
||||
if (row.element != shell_order[element] ||
|
||||
row.location != kLocations[point] ||
|
||||
row.natural_coordinates != natural[point] || !IsFinite(row)) {
|
||||
row.natural_coordinates != natural[point] || !results_internal::IsFiniteShellRow(row)) {
|
||||
return Failure(FailureCategory::kSolver,
|
||||
"Shell recovery rows have invalid identity or values.");
|
||||
}
|
||||
@@ -228,7 +181,7 @@ Status DynamicAnalysisState::CommitAcceptedFrame(
|
||||
}
|
||||
for (std::size_t node = 0U; node < node_count; ++node) {
|
||||
if (frame.nodal_internal_order[node] != node ||
|
||||
!SameId(frame.nodal_source_ids[node], nodal_source_ids_[node]) ||
|
||||
!SourceIdentityEquals(frame.nodal_source_ids[node], nodal_source_ids_[node]) ||
|
||||
!IsFinite(frame.nodal_triad[node][0U]) ||
|
||||
!IsFinite(frame.nodal_triad[node][1U]) ||
|
||||
!IsFinite(frame.nodal_triad[node][2U]) ||
|
||||
|
||||
@@ -162,54 +162,49 @@ Result<NodalLift> MakeLift(const Matrix& mass,
|
||||
Failure("eigen-mass-conditioning", node,
|
||||
"A positive-space mass pivot is not resolvable."));
|
||||
}
|
||||
Matrix lower(positive, positive);
|
||||
Matrix inverse_lower(positive, positive);
|
||||
if (!dense_blas_internal::CholeskyLowerAndInverse(normalized_mp, lower,
|
||||
inverse_lower)) {
|
||||
const auto factor = dense_blas_internal::DenseFactor::Cholesky(normalized_mp);
|
||||
if (!factor.HasValue()) {
|
||||
return Result<NodalLift>::Failure(
|
||||
Failure("eigen-mass-conditioning", node,
|
||||
"A positive-space mass pivot is not resolvable."));
|
||||
}
|
||||
const Matrix lower = std::move(factor.Value().Lower().Value());
|
||||
Matrix transposed_lower(positive, positive);
|
||||
Matrix scaled_inverse_transpose(positive, positive);
|
||||
const double root_scale = std::sqrt(scale);
|
||||
for (std::size_t i = 0; i < positive; ++i)
|
||||
for (std::size_t j = 0; j < positive; ++j) {
|
||||
for (std::size_t j = 0; j < positive; ++j)
|
||||
transposed_lower(i, j) = lower(j, i);
|
||||
scaled_inverse_transpose(i, j) = inverse_lower(j, i) / root_scale;
|
||||
}
|
||||
const auto inverse_mass = dense_blas_internal::MultiplyTransposeLeft(
|
||||
inverse_lower, inverse_lower);
|
||||
const auto condition = factor.Value().ReciprocalConditionInfinity();
|
||||
const auto rebuilt_mass = lower.Multiply(transposed_lower);
|
||||
double norm_mp = 0.0;
|
||||
double norm_inverse = 0.0;
|
||||
double reconstruction = 0.0;
|
||||
double scaled_mass_norm = 0.0;
|
||||
double rebuilt_mass_norm = 0.0;
|
||||
for (std::size_t i = 0; i < positive; ++i) {
|
||||
double row_mp = 0.0;
|
||||
double row_inverse = 0.0;
|
||||
for (std::size_t j = 0; j < positive; ++j) {
|
||||
const double inverse = inverse_mass(i, j);
|
||||
const double rebuilt = rebuilt_mass(i, j);
|
||||
row_inverse += std::abs(inverse);
|
||||
row_mp += std::abs(mp(i, j) / scale);
|
||||
reconstruction = std::hypot(reconstruction, rebuilt - mp(i, j) / scale);
|
||||
scaled_mass_norm = std::hypot(scaled_mass_norm, mp(i, j) / scale);
|
||||
rebuilt_mass_norm = std::hypot(rebuilt_mass_norm, rebuilt);
|
||||
}
|
||||
norm_mp = (std::max)(norm_mp, row_mp);
|
||||
norm_inverse = (std::max)(norm_inverse, row_inverse);
|
||||
}
|
||||
result.record.rcond_inf = (1.0 / norm_mp) / norm_inverse;
|
||||
if (!(result.record.rcond_inf >= kConditionLimit) ||
|
||||
if (!condition.HasValue() || !(condition.Value() >= kConditionLimit) ||
|
||||
!(Ratio(reconstruction, scaled_mass_norm + rebuilt_mass_norm) <=
|
||||
kAlgebraLimit)) {
|
||||
return Result<NodalLift>::Failure(Failure(
|
||||
"eigen-mass-conditioning", node,
|
||||
"Nodal mass fails rcond_inf >= 2^-32 or Cholesky reconstruction."));
|
||||
}
|
||||
result.scaled_lift = basis.Multiply(scaled_inverse_transpose);
|
||||
result.record.rcond_inf = condition.Value();
|
||||
// X L^T = basis/sqrt(scale), so X^T M_scaled X = I.
|
||||
Matrix scaled_basis = basis;
|
||||
const double root_scale = std::sqrt(scale);
|
||||
for (std::size_t c = 0; c < 6U; ++c)
|
||||
for (std::size_t j = 0; j < positive; ++j)
|
||||
scaled_basis(c, j) /= root_scale;
|
||||
auto whitened = factor.Value().SolveLowerRight(scaled_basis, true);
|
||||
if (!whitened.HasValue())
|
||||
return Result<NodalLift>::Failure(Failure(
|
||||
"eigen-mass-conditioning", node, "Nodal whitening is nonfinite."));
|
||||
result.scaled_lift = std::move(whitened.Value());
|
||||
for (std::size_t c = 0; c < 6U; ++c) {
|
||||
for (std::size_t j = 0; j < positive; ++j) {
|
||||
if (!std::isfinite(result.scaled_lift(c, j))) {
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
#ifndef FESA_ANALYSIS_EQUILIBRIUM_ALGORITHM_H_
|
||||
#define FESA_ANALYSIS_EQUILIBRIUM_ALGORITHM_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
#include "fesa/core/status.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
/// @brief Controls evaluated iteration order without owning a family candidate.
|
||||
/// @note Evaluation and its referenced trial context remain alive and unmoved
|
||||
/// throughout this call. A successful evaluation value means candidate ready.
|
||||
class EquilibriumAlgorithm {
|
||||
public:
|
||||
using Evaluation = std::function<Result<bool>(std::uint32_t)>;
|
||||
virtual ~EquilibriumAlgorithm() = default;
|
||||
virtual Status SolveCurrentStep(std::uint32_t first, std::uint32_t last,
|
||||
const Evaluation& evaluate) = 0;
|
||||
};
|
||||
|
||||
/// @brief Preserves the original inclusive full-Newton evaluation sequence.
|
||||
class NewtonStepAlgorithm final : public EquilibriumAlgorithm {
|
||||
public:
|
||||
Status SolveCurrentStep(std::uint32_t first, std::uint32_t last,
|
||||
const Evaluation& evaluate) override {
|
||||
for (std::uint32_t iteration = first; iteration <= last; ++iteration) {
|
||||
auto result = evaluate(iteration);
|
||||
if (!result.HasValue()) return result.GetStatus();
|
||||
if (result.Value()) return Status::Ok();
|
||||
}
|
||||
// Family evaluators retain their original final-evaluation failure.
|
||||
return Status::Ok();
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Owns one procedure-local typed evaluator and unpublished candidate.
|
||||
/// @note This value is not an algorithm interface. The injected algorithm never
|
||||
/// owns or type-erases Candidate, and neither callback nor context may escape.
|
||||
template <typename Candidate, std::uint32_t kFirstIteration>
|
||||
class EquilibriumStep {
|
||||
public:
|
||||
using Evaluation =
|
||||
std::function<Result<std::optional<Candidate>>(std::uint32_t)>;
|
||||
EquilibriumStep(Evaluation evaluate, std::uint32_t maximum)
|
||||
: evaluate_(std::move(evaluate)), maximum_(maximum) {}
|
||||
EquilibriumStep(const EquilibriumStep&) = delete;
|
||||
EquilibriumStep& operator=(const EquilibriumStep&) = delete;
|
||||
Status Solve(EquilibriumAlgorithm& algorithm) {
|
||||
const EquilibriumAlgorithm::Evaluation evaluate =
|
||||
[this](std::uint32_t iteration) -> Result<bool> {
|
||||
auto result = evaluate_(iteration);
|
||||
if (!result.HasValue()) return Result<bool>::Failure(result.GetStatus());
|
||||
if (result.Value().has_value()) {
|
||||
pending_ = std::move(result.Value());
|
||||
return Result<bool>::Success(true);
|
||||
}
|
||||
return Result<bool>::Success(false);
|
||||
};
|
||||
return algorithm.SolveCurrentStep(kFirstIteration, maximum_, evaluate);
|
||||
}
|
||||
bool HasCandidate() const { return pending_.has_value(); }
|
||||
/// @brief Transfers the validated candidate to the procedure commit boundary.
|
||||
Candidate TakeCandidate() { return std::move(pending_.value()); }
|
||||
|
||||
private:
|
||||
Evaluation evaluate_;
|
||||
std::uint32_t maximum_;
|
||||
std::optional<Candidate> pending_;
|
||||
};
|
||||
|
||||
// Static evaluations start at one; mechanical HHT corrections start at zero.
|
||||
// Evaluation at the maximum still performs the existing endpoint validation.
|
||||
template <typename Candidate>
|
||||
using ShellFullNewtonStep = EquilibriumStep<Candidate, 1U>;
|
||||
template <typename Candidate>
|
||||
using SolidFullNewtonStep = EquilibriumStep<Candidate, 1U>;
|
||||
template <typename Candidate>
|
||||
using ReducedShellHhtStep = EquilibriumStep<Candidate, 0U>;
|
||||
template <typename Candidate>
|
||||
using SolidHhtStep = EquilibriumStep<Candidate, 0U>;
|
||||
|
||||
} // namespace fesa
|
||||
#endif // FESA_ANALYSIS_EQUILIBRIUM_ALGORITHM_H_
|
||||
@@ -16,11 +16,14 @@
|
||||
#include "fesa/solvers/linear/linear_solver.h"
|
||||
#include "fesa/solvers/linear/tangent_solver.h"
|
||||
#include "math/dense_blas_internal.h"
|
||||
#include "math/compensated_sum.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace hht_internal {
|
||||
namespace {
|
||||
|
||||
using math_internal::AddCompensated;
|
||||
|
||||
/// @brief Attaches a fail-closed dynamic diagnostic to the source model.
|
||||
Status Failure(const Domain& domain, const std::string& code,
|
||||
const std::string& message) {
|
||||
@@ -148,48 +151,23 @@ Status CheckLocalMass(const Domain& domain, const SparseMatrix& matrix,
|
||||
return Failure(domain, "dynamic-positive-mass",
|
||||
"A nonempty physical mass block has no positive scale.");
|
||||
}
|
||||
double norm = 0.0;
|
||||
for (std::size_t i = 0; i < size; ++i) {
|
||||
double sum = 0.0;
|
||||
for (std::size_t j = 0; j < size; ++j) {
|
||||
block(i, j) /= scale;
|
||||
sum += std::abs(block(i, j));
|
||||
}
|
||||
norm = std::max(norm, sum);
|
||||
}
|
||||
Matrix lower{size, size};
|
||||
Matrix inverse_lower{size, size};
|
||||
auto factor_failure = dense_blas_internal::CholeskyFailure::kNone;
|
||||
for (std::size_t i = 0; i < size; ++i)
|
||||
for (std::size_t j = 0; j < size; ++j) block(i, j) /= scale;
|
||||
try {
|
||||
if (!dense_blas_internal::CholeskyLowerAndInverse(
|
||||
block, lower, inverse_lower, &factor_failure)) {
|
||||
if (factor_failure == dense_blas_internal::CholeskyFailure::kInverse)
|
||||
return Failure(
|
||||
domain, "dynamic-positive-mass-conditioning",
|
||||
"A nodal free physical block reciprocal condition is below 2^-32.");
|
||||
const auto factor = dense_blas_internal::DenseFactor::Cholesky(block);
|
||||
if (!factor.HasValue())
|
||||
return Failure(
|
||||
domain, "dynamic-positive-mass",
|
||||
"Physical mass requires strictly positive Cholesky pivots.");
|
||||
}
|
||||
const auto rcond = factor.Value().ReciprocalConditionInfinity();
|
||||
if (!rcond.HasValue() || !std::isfinite(rcond.Value()) ||
|
||||
!(rcond.Value() >= std::ldexp(1.0, -32)))
|
||||
return Failure(
|
||||
domain, "dynamic-positive-mass-conditioning",
|
||||
"A nodal free physical block reciprocal condition is below 2^-32.");
|
||||
} catch (const std::exception& error) {
|
||||
return Failure(domain, "dynamic-positive-mass", error.what());
|
||||
}
|
||||
const Matrix inverse =
|
||||
dense_blas_internal::MultiplyTransposeLeft(inverse_lower, inverse_lower);
|
||||
std::array<double, 6> sums{};
|
||||
for (std::size_t column = 0; column < size; ++column) {
|
||||
for (std::size_t i = 0; i < size; ++i) {
|
||||
sums[i] += std::abs(inverse(i, column));
|
||||
}
|
||||
}
|
||||
const double inverse_norm = *std::max_element(sums.begin(), sums.end());
|
||||
const double rcond = 1.0 / norm / inverse_norm;
|
||||
if (!std::isfinite(inverse_norm) || !std::isfinite(rcond) ||
|
||||
!(rcond >= std::ldexp(1.0, -32))) {
|
||||
return Failure(
|
||||
domain, "dynamic-positive-mass-conditioning",
|
||||
"A nodal free physical block reciprocal condition is below 2^-32.");
|
||||
}
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
@@ -468,15 +446,6 @@ Result<SparseMatrix> ConstraintGradient(const DynamicAssembly& assembly,
|
||||
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.
|
||||
struct Regularity {
|
||||
SparseMatrix matrix;
|
||||
@@ -553,23 +522,10 @@ Result<Regularity> CheckRegularity(const Domain& domain,
|
||||
sum += std::abs(matrix.Value().Values()[index]);
|
||||
norm = std::max(norm, sum);
|
||||
}
|
||||
Vector inverse_sums{nodes.size()};
|
||||
// Columns are discarded after their absolute row sums are accumulated;
|
||||
// a global dense inverse is never retained.
|
||||
for (std::size_t column = 0; column < nodes.size(); ++column) {
|
||||
Vector rhs{nodes.size()};
|
||||
rhs[column] = 1.0;
|
||||
Vector solution{nodes.size()};
|
||||
const Status solved = solver.SolveCorrection(rhs, solution);
|
||||
if (!solved.IsOk()) return Result<Regularity>::Failure(solved);
|
||||
const Status checked = CheckLinearSolve(matrix.Value(), rhs, solution);
|
||||
if (!checked.IsOk()) return Result<Regularity>::Failure(checked);
|
||||
for (std::size_t row = 0; row < nodes.size(); ++row)
|
||||
inverse_sums[row] += std::abs(solution[row]);
|
||||
}
|
||||
double inverse_norm = 0.0;
|
||||
for (std::size_t row = 0; row < nodes.size(); ++row)
|
||||
inverse_norm = std::max(inverse_norm, inverse_sums[row]);
|
||||
const Status accumulated =
|
||||
AccumulateCheckedCanonicalNorm(matrix.Value(), solver, inverse_norm);
|
||||
if (!accumulated.IsOk()) return Result<Regularity>::Failure(accumulated);
|
||||
const double condition = 1.0 / norm / inverse_norm;
|
||||
if (!std::isfinite(norm) || !std::isfinite(inverse_norm) ||
|
||||
!std::isfinite(condition) || !(condition >= std::ldexp(1.0, -32))) {
|
||||
@@ -662,6 +618,26 @@ Status HiddenEvidence(const Domain& domain, const DynamicAssembly& assembly,
|
||||
|
||||
} // namespace
|
||||
|
||||
Status AccumulateCheckedCanonicalNorm(const SparseMatrix& matrix,
|
||||
TangentSolver& solver, double& norm) {
|
||||
const auto size = matrix.Rows();
|
||||
Vector row_sums{size};
|
||||
for (std::size_t column = 0U; column < size; ++column) {
|
||||
Vector rhs{size};
|
||||
rhs[column] = 1.0;
|
||||
Vector solution{size};
|
||||
const Status solved = solver.SolveCorrection(rhs, solution);
|
||||
if (!solved.IsOk()) return solved;
|
||||
const Status checked = CheckLinearSolve(matrix, rhs, solution);
|
||||
if (!checked.IsOk()) return checked;
|
||||
for (std::size_t row = 0U; row < size; ++row)
|
||||
row_sums[row] += std::abs(solution[row]);
|
||||
}
|
||||
for (std::size_t row = 0U; row < size; ++row)
|
||||
norm = (std::max)(norm, row_sums[row]);
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
double OrderedNullMetric(const SparseMatrix& metric, const std::size_t node,
|
||||
const Vector3& direction) {
|
||||
Vector left{9U};
|
||||
|
||||
@@ -151,9 +151,24 @@ Result<std::vector<RuntimeShell>> BuildRuntimeShells(const Domain& domain,
|
||||
*material, density);
|
||||
if (!inertia.HasValue())
|
||||
return Result<std::vector<RuntimeShell>>::Failure(inertia.GetStatus());
|
||||
shells.push_back({static_cast<EntityIndex>(index),
|
||||
dofs.ShellElementScatter(static_cast<EntityIndex>(index)),
|
||||
std::move(internal.Value()), std::move(inertia.Value())});
|
||||
auto internal_kernel =
|
||||
std::make_unique<NonlinearMitc4Shell>(std::move(internal.Value()));
|
||||
auto inertia_kernel =
|
||||
std::make_unique<NonlinearMitc4Inertia>(std::move(inertia.Value()));
|
||||
ElementDofLayout layout{
|
||||
definition->SourceId(), definition->NodeIndices(),
|
||||
{DofComponent::kUx, DofComponent::kUy, DofComponent::kUz,
|
||||
DofComponent::kUrx, DofComponent::kUry, DofComponent::kUrz}};
|
||||
auto trial = std::make_unique<Mitc4TrialAdapter>(layout, *internal_kernel);
|
||||
auto kinetic = std::make_unique<Mitc4InertiaAdapter>(layout, *inertia_kernel);
|
||||
auto mapped = dofs.ElementScatter(kinetic->DofLayout());
|
||||
if (!mapped.HasValue())
|
||||
return Result<std::vector<RuntimeShell>>::Failure(mapped.GetStatus());
|
||||
std::array<std::size_t, 24> scatter{};
|
||||
std::copy(mapped.Value().begin(), mapped.Value().end(), scatter.begin());
|
||||
shells.push_back({static_cast<EntityIndex>(index), scatter,
|
||||
std::move(internal_kernel), std::move(inertia_kernel),
|
||||
std::move(trial), std::move(kinetic)});
|
||||
}
|
||||
return Result<std::vector<RuntimeShell>>::Success(std::move(shells));
|
||||
}
|
||||
@@ -431,8 +446,8 @@ Result<DynamicAssembly> EvaluateAssembly(
|
||||
}
|
||||
const auto before = retain_constraints ? ReadTrialEvaluationContext()
|
||||
: TrialEvaluationContext{};
|
||||
auto f = EvaluateInternalResidualAndTangent(
|
||||
shells[e].internal, trial, directional_third ? &v : nullptr);
|
||||
auto f = shells[e].internal->EvaluateTrial(
|
||||
trial, directional_third ? &v : nullptr);
|
||||
if (!f.HasValue()) {
|
||||
failures[e] = f.GetStatus();
|
||||
return;
|
||||
@@ -441,7 +456,7 @@ Result<DynamicAssembly> EvaluateAssembly(
|
||||
const auto after = ReadTrialEvaluationContext();
|
||||
if (ReusableTrialContext(before, after)) evaluation_contexts[e] = after;
|
||||
}
|
||||
auto m = shells[e].inertia.Evaluate(trial, v, a);
|
||||
auto m = shells[e].inertia->Evaluate(trial, v, a);
|
||||
if (!m.HasValue()) {
|
||||
failures[e] = m.GetStatus();
|
||||
return;
|
||||
@@ -499,7 +514,7 @@ Result<DynamicAssembly> EvaluateAssembly(
|
||||
external_done[node] = true;
|
||||
const auto first = 6U * node + 3U;
|
||||
const auto external =
|
||||
shells[e].internal.EvaluateExternalDrillingConstraint(
|
||||
shells[e].internal->EvaluateExternalDrillingConstraint(
|
||||
local, Triple(frame.generalized_coordinates, first),
|
||||
Triple(frame.velocity, first),
|
||||
Triple(frame.external_force, first), {}, {});
|
||||
@@ -660,7 +675,7 @@ Result<DynamicAssembly> EvaluateAssembly(
|
||||
record.scatter = shells[e].scatter;
|
||||
record.configuration =
|
||||
nonlinear_mitc4_internal::ConfigurationSnapshot::Read(
|
||||
shells[e].internal);
|
||||
*shells[e].internal_kernel);
|
||||
record.evaluation_context = evaluation_contexts[e];
|
||||
for (std::size_t i = 0U; i < 24U; ++i) {
|
||||
record.q[i] = frame.generalized_coordinates[shells[e].scatter[i]];
|
||||
@@ -703,11 +718,13 @@ Status CompleteEquilibrium(const Domain& domain, const DofManager& dofs,
|
||||
const auto first = 6U * node;
|
||||
const Vector3 arm = Vector3(domain.Nodes()[node].coordinates) +
|
||||
Triple(frame->generalized_coordinates, first);
|
||||
const Matrix transform = InverseTransposeLeftJacobianSO3(
|
||||
Triple(frame->generalized_coordinates, first + 3U));
|
||||
const Vector3 phi = Triple(frame->generalized_coordinates, first + 3U);
|
||||
for (const Vector* values : {&frame->external_force, &frame->reaction}) {
|
||||
const Vector3 force = Triple(*values, first);
|
||||
const Vector3 moment = Transform(transform, Triple(*values, first + 3U));
|
||||
const auto spatial = SpatialMomentSO3(phi, Triple(*values, first + 3U));
|
||||
if (!spatial.HasValue())
|
||||
return Failure("Dynamic physical force or moment balance exceeds 1e-8.");
|
||||
const Vector3 moment = spatial.Value();
|
||||
total_force = total_force + force;
|
||||
total_moment = total_moment + arm.Cross(force) + moment;
|
||||
force_scale += force.Norm();
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/analysis/dynamic_analysis_state.h"
|
||||
#include "fesa/analysis/hht_time_integration.h"
|
||||
#include "fesa/elements/element_capabilities.h"
|
||||
#include "fesa/elements/nonlinear_mitc4_inertia.h"
|
||||
#include "fesa/elements/nonlinear_mitc4_shell.h"
|
||||
#include "fesa/math/sparse_matrix.h"
|
||||
@@ -25,8 +27,10 @@ namespace hht_internal {
|
||||
struct RuntimeShell {
|
||||
EntityIndex element_index;
|
||||
std::array<std::size_t, 24> scatter;
|
||||
NonlinearMitc4Shell internal;
|
||||
NonlinearMitc4Inertia inertia;
|
||||
std::unique_ptr<NonlinearMitc4Shell> internal_kernel;
|
||||
std::unique_ptr<NonlinearMitc4Inertia> inertia_kernel;
|
||||
std::unique_ptr<ShellTrialElement> internal;
|
||||
std::unique_ptr<ShellInertiaElement> inertia;
|
||||
};
|
||||
|
||||
/// @brief Captures only controls used by the current MSVC x64 local evaluator.
|
||||
@@ -138,6 +142,12 @@ Result<SparseMatrix> ScaledFree(const SparseMatrix& matrix,
|
||||
Status CheckLinearSolve(const SparseMatrix& matrix, const Vector& rhs,
|
||||
const Vector& solution);
|
||||
|
||||
/// @brief Streams checked canonical solves into the caller's infinity norm.
|
||||
/// @note The caller owns factorization and the initial norm. Each column is
|
||||
/// discarded after ascending absolute row-sum accumulation; failures preserve norm.
|
||||
Status AccumulateCheckedCanonicalNorm(const SparseMatrix& matrix,
|
||||
TangentSolver& solver, double& norm);
|
||||
|
||||
/// @brief Finds analytic admissible node null directions without rank cuts.
|
||||
std::vector<std::size_t> NullNodes(
|
||||
const DofManager& dofs,
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
#ifndef FESA_ANALYSIS_HHT_REDUCED_INTEGRATOR_H_
|
||||
#define FESA_ANALYSIS_HHT_REDUCED_INTEGRATOR_H_
|
||||
#include <utility>
|
||||
#include "analysis/hht_reduced_internal.h"
|
||||
namespace fesa::hht_reduced_internal {
|
||||
struct ReducedHistory {
|
||||
Vector p{0U};
|
||||
Vector velocity{0U};
|
||||
Vector acceleration{0U};
|
||||
Vector algorithmic_acceleration{0U};
|
||||
Vector z{0U};
|
||||
};
|
||||
|
||||
/// @brief Keeps every private predictor compensation channel before a trial.
|
||||
struct ReducedShellPredictor {
|
||||
Vector pp{0U};
|
||||
Vector vpp{0U};
|
||||
Vector coordinate_low{0U};
|
||||
Vector increment_trace{0U};
|
||||
Vector increment_trace_compensation{0U};
|
||||
Vector increment_trace_absolute_sum{0U};
|
||||
};
|
||||
|
||||
struct ReducedShellRateRequest {
|
||||
const FixedChart& chart;
|
||||
const ReducedHistory& history;
|
||||
const Vector& trial_algorithmic;
|
||||
const Vector& vpp;
|
||||
bool copy_translation_rates;
|
||||
const Vector& retained_velocity;
|
||||
const Vector& retained_acceleration;
|
||||
double h;
|
||||
const HhtParameters& parameters;
|
||||
};
|
||||
|
||||
struct ReducedShellTrialRates {
|
||||
Vector velocity{0U};
|
||||
Vector acceleration{0U};
|
||||
};
|
||||
|
||||
/// @brief Handles private shell predictors and exact sparse rate
|
||||
/// reconstruction.
|
||||
/// @note Chart/root/solver are non-owning call views; no history is committed.
|
||||
class ShellHhtIntegrator {
|
||||
public:
|
||||
virtual ~ShellHhtIntegrator() = default;
|
||||
virtual ReducedShellPredictor
|
||||
Predict(const ReducedHistory& history, double h,
|
||||
const HhtParameters& parameters) const = 0;
|
||||
/// @note Output buffers have history.p.Size() entries and do not alias any
|
||||
/// request input or each other. The caller owns their allocation/lifetime.
|
||||
virtual void EvaluateRates(const ReducedShellRateRequest& request,
|
||||
Vector& velocity, Vector& acceleration) const = 0;
|
||||
virtual Result<ReconstructedRates>
|
||||
ReconstructPhysicalRates(const FixedChart& chart, const EmbeddingRoot& root,
|
||||
const Vector& velocity, const Vector& acceleration,
|
||||
TangentSolver& solver) const = 0;
|
||||
};
|
||||
|
||||
/// @brief Sequences one private predictor, pending endpoint and commit.
|
||||
/// @note Callables are synchronous borrowed views. They retain all original
|
||||
/// evaluation/diagnostic/commit policy; no accepted history is owned here.
|
||||
template <typename Evaluate, typename Commit>
|
||||
Status RunReducedHhtStep(const ShellHhtIntegrator& integrator,
|
||||
const ReducedHistory& history, double h,
|
||||
const HhtParameters& parameters,
|
||||
Evaluate&& evaluate, Commit&& commit) {
|
||||
auto predictor = integrator.Predict(history, h, parameters);
|
||||
auto pending = evaluate(integrator, std::move(predictor));
|
||||
if (!pending.HasValue()) return pending.GetStatus();
|
||||
return commit(std::move(pending.Value()));
|
||||
}
|
||||
} // namespace fesa::hht_reduced_internal
|
||||
#endif // FESA_ANALYSIS_HHT_REDUCED_INTEGRATOR_H_
|
||||
@@ -10,13 +10,18 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "fesa/core/source_identity.h"
|
||||
#include "fesa/solvers/linear/tangent_solver.h"
|
||||
#include "math/dense_blas_internal.h"
|
||||
#include "math/high_low_arithmetic.h"
|
||||
#include "math/finite_values.h"
|
||||
#include "math/compensated_sum.h"
|
||||
|
||||
namespace fesa::hht_reduced_internal {
|
||||
namespace {
|
||||
|
||||
using RateSum = math_internal::CompensatedSum;
|
||||
|
||||
Status Failure(const std::string& message,
|
||||
const std::string& code = "dynamic-invalid-state") {
|
||||
return Status::Failure(FailureCategory::kSolver,
|
||||
@@ -25,27 +30,9 @@ Status Failure(const std::string& message,
|
||||
}
|
||||
|
||||
bool Finite(const Vector& values) {
|
||||
for (std::size_t i = 0U; i < values.Size(); ++i)
|
||||
if (!std::isfinite(values[i])) return false;
|
||||
return true;
|
||||
return math_internal::IsFinite(values);
|
||||
}
|
||||
|
||||
class RateSum {
|
||||
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;
|
||||
};
|
||||
|
||||
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)
|
||||
@@ -65,13 +52,6 @@ bool SameBits(const double left, const double right) {
|
||||
return std::memcmp(&left, &right, sizeof(double)) == 0;
|
||||
}
|
||||
|
||||
/// @brief Compares semantic and original source identity without object keys.
|
||||
bool SameSource(const SourceEntityId& left, const SourceEntityId& right) {
|
||||
return left.instance_name == right.instance_name &&
|
||||
left.source_label == right.source_label &&
|
||||
left.source_label_text == right.source_label_text;
|
||||
}
|
||||
|
||||
/// @brief Finds only the current complete directional response, otherwise miss.
|
||||
const hht_internal::DirectionalShellConstraintData* MatchingConstraints(
|
||||
const Domain& domain, const hht_internal::RuntimeShell& shell,
|
||||
@@ -98,7 +78,7 @@ const hht_internal::DirectionalShellConstraintData* MatchingConstraints(
|
||||
hht_internal::ReadTrialEvaluationContext()))
|
||||
return nullptr;
|
||||
const auto configuration =
|
||||
nonlinear_mitc4_internal::ConfigurationSnapshot::Read(shell.internal);
|
||||
nonlinear_mitc4_internal::ConfigurationSnapshot::Read(*shell.internal_kernel);
|
||||
if (record.configuration.source.file != configuration.source.file ||
|
||||
record.configuration.source.line != configuration.source.line ||
|
||||
record.configuration.identity != configuration.identity)
|
||||
@@ -116,7 +96,7 @@ const hht_internal::DirectionalShellConstraintData* MatchingConstraints(
|
||||
for (std::size_t node = 0U; node < 4U; ++node) {
|
||||
const auto global_node = shell.scatter[6U * node] / 6U;
|
||||
if (global_node >= domain.Nodes().size() ||
|
||||
!SameSource(record.node_source_ids[node],
|
||||
!SourceIdentityEquals(record.node_source_ids[node],
|
||||
domain.Nodes()[global_node].source_id))
|
||||
return nullptr;
|
||||
}
|
||||
@@ -534,7 +514,7 @@ Result<ConstraintOperators> EvaluateConstraintOperators(
|
||||
std::optional<NonlinearMitc4InternalEquilibrium> fresh;
|
||||
if (saved == nullptr) {
|
||||
auto internal =
|
||||
EvaluateInternalResidualAndTangent(shell.internal, Trial(q), &v);
|
||||
shell.internal->EvaluateTrial(Trial(q), &v);
|
||||
if (!internal.HasValue())
|
||||
return Result<ConstraintOperators>::Failure(internal.GetStatus());
|
||||
fresh = std::move(internal.Value());
|
||||
@@ -572,7 +552,7 @@ Result<ConstraintOperators> EvaluateConstraintOperators(
|
||||
if (external_done[global_node]) continue;
|
||||
external_done[global_node] = true;
|
||||
const auto first = 6U * global_node + 3U;
|
||||
const auto external = shell.internal.EvaluateExternalDrillingConstraint(
|
||||
const auto external = shell.internal->EvaluateExternalDrillingConstraint(
|
||||
local_node, Vector3{q[6U * local_node + 3U],
|
||||
q[6U * local_node + 4U],
|
||||
q[6U * local_node + 5U]},
|
||||
@@ -587,7 +567,7 @@ Result<ConstraintOperators> EvaluateConstraintOperators(
|
||||
force_acceleration[first + 2U]});
|
||||
if (!external.HasValue())
|
||||
return Result<ConstraintOperators>::Failure(external.GetStatus());
|
||||
const auto rate_term = shell.internal.EvaluateExternalDrillingConstraint(
|
||||
const auto rate_term = shell.internal->EvaluateExternalDrillingConstraint(
|
||||
local_node, Vector3{q[6U * local_node + 3U],
|
||||
q[6U * local_node + 4U],
|
||||
q[6U * local_node + 5U]},
|
||||
@@ -596,7 +576,7 @@ Result<ConstraintOperators> EvaluateConstraintOperators(
|
||||
if (!rate_term.HasValue())
|
||||
return Result<ConstraintOperators>::Failure(rate_term.GetStatus());
|
||||
const auto acceleration_term =
|
||||
shell.internal.EvaluateExternalDrillingConstraint(
|
||||
shell.internal->EvaluateExternalDrillingConstraint(
|
||||
local_node, Vector3{q[6U * local_node + 3U],
|
||||
q[6U * local_node + 4U],
|
||||
q[6U * local_node + 5U]},
|
||||
@@ -1321,21 +1301,10 @@ Result<double> CheckGraphRegularity(
|
||||
if (!sparse.HasValue()) return Result<double>::Failure(sparse.GetStatus());
|
||||
Status status = solver.FactorizeFresh(sparse.Value());
|
||||
if (!status.IsOk()) return Result<double>::Failure(status);
|
||||
Vector inverse_sums{size};
|
||||
for (std::size_t column = 0U; column < size; ++column) {
|
||||
Vector rhs{size};
|
||||
rhs[column] = 1.0;
|
||||
Vector solution{size};
|
||||
status = solver.SolveCorrection(rhs, solution);
|
||||
if (!status.IsOk()) return Result<double>::Failure(status);
|
||||
status = hht_internal::CheckLinearSolve(sparse.Value(), rhs, solution);
|
||||
if (!status.IsOk()) return Result<double>::Failure(status);
|
||||
for (std::size_t row = 0U; row < size; ++row)
|
||||
inverse_sums[row] += std::abs(solution[row]);
|
||||
}
|
||||
double inverse_norm = 0.0;
|
||||
for (std::size_t row = 0U; row < size; ++row)
|
||||
inverse_norm = std::max(inverse_norm, inverse_sums[row]);
|
||||
status = hht_internal::AccumulateCheckedCanonicalNorm(
|
||||
sparse.Value(), solver, inverse_norm);
|
||||
if (!status.IsOk()) return Result<double>::Failure(status);
|
||||
const double condition = 1.0 / norm / inverse_norm;
|
||||
if (!std::isfinite(norm) || !std::isfinite(inverse_norm) ||
|
||||
!std::isfinite(condition) || !(condition >= std::ldexp(1.0, -32))) {
|
||||
@@ -1409,20 +1378,9 @@ Result<double> CheckSelectorTransversality(
|
||||
if (!sparse.HasValue()) return Result<double>::Failure(sparse.GetStatus());
|
||||
auto status = solver.FactorizeFresh(sparse.Value());
|
||||
if (!status.IsOk()) return Result<double>::Failure(status);
|
||||
Vector inverse_rows{rows.size()};
|
||||
for (std::size_t column = 0U; column < rows.size(); ++column) {
|
||||
Vector rhs{rows.size()};
|
||||
rhs[column] = 1.0;
|
||||
Vector solution{rows.size()};
|
||||
status = solver.SolveCorrection(rhs, solution);
|
||||
if (!status.IsOk()) return Result<double>::Failure(status);
|
||||
status = hht_internal::CheckLinearSolve(sparse.Value(), rhs, solution);
|
||||
if (!status.IsOk()) return Result<double>::Failure(status);
|
||||
for (std::size_t row = 0U; row < rows.size(); ++row)
|
||||
inverse_rows[row] += std::abs(solution[row]);
|
||||
}
|
||||
for (std::size_t row = 0U; row < rows.size(); ++row)
|
||||
inverse_norm = (std::max)(inverse_norm, inverse_rows[row]);
|
||||
status = hht_internal::AccumulateCheckedCanonicalNorm(
|
||||
sparse.Value(), solver, inverse_norm);
|
||||
if (!status.IsOk()) return Result<double>::Failure(status);
|
||||
}
|
||||
const double condition = 1.0 / norm / inverse_norm;
|
||||
if (!std::isfinite(condition) || condition < std::ldexp(1.0, -32))
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#include "analysis/hht_reduced_procedure.h"
|
||||
#include "analysis/hht_reduced_integrator.h"
|
||||
|
||||
#include "equilibrium_algorithm.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
@@ -296,12 +299,83 @@ Status CheckReducedUpdates(const Domain& domain,
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
struct ReducedHistory {
|
||||
Vector p{0U};
|
||||
Vector velocity{0U};
|
||||
Vector acceleration{0U};
|
||||
Vector algorithmic_acceleration{0U};
|
||||
Vector z{0U};
|
||||
// Private nonvirtual primitive: keep velocity assignment before acceleration
|
||||
// evaluation, including per-index position retries. No accepted state changes.
|
||||
void EvaluateReducedShellRate(double predictor_velocity,
|
||||
double algorithmic_acceleration,
|
||||
double old_acceleration, double h,
|
||||
const HhtParameters& parameters,
|
||||
double* velocity, double* acceleration) {
|
||||
*velocity = predictor_velocity + parameters.gamma * h * algorithmic_acceleration;
|
||||
*acceleration = (algorithmic_acceleration + parameters.alpha * old_acceleration) /
|
||||
(1.0 + parameters.alpha);
|
||||
}
|
||||
|
||||
class ReducedCoordinateShellHhtIntegrator final : public ShellHhtIntegrator {
|
||||
public:
|
||||
ReducedShellPredictor
|
||||
Predict(const ReducedHistory& history, double h,
|
||||
const HhtParameters& parameters) const override {
|
||||
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];
|
||||
}
|
||||
Vector coordinate_low{history.p.Size()};
|
||||
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);
|
||||
}
|
||||
return {std::move(pp),
|
||||
std::move(vpp),
|
||||
std::move(coordinate_low),
|
||||
std::move(increment_trace),
|
||||
std::move(increment_trace_compensation),
|
||||
std::move(increment_trace_absolute_sum)};
|
||||
}
|
||||
void EvaluateRates(const ReducedShellRateRequest& request,
|
||||
Vector& trial_v, Vector& trial_a) const override {
|
||||
const auto& history = request.history;
|
||||
const auto& trial_algorithmic = request.trial_algorithmic;
|
||||
const auto& vpp = request.vpp;
|
||||
const auto& parameters = request.parameters;
|
||||
const double h = request.h;
|
||||
for (std::size_t i = 0U; i < history.p.Size(); ++i) {
|
||||
if (request.copy_translation_rates &&
|
||||
PhysicalCoordinateChannel(request.chart, i) == 0U) {
|
||||
trial_v[i] = request.retained_velocity[i];
|
||||
trial_a[i] = request.retained_acceleration[i];
|
||||
} else {
|
||||
EvaluateReducedShellRate(
|
||||
vpp[i], trial_algorithmic[i], history.acceleration[i], h, parameters,
|
||||
&trial_v[i], &trial_a[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Result<ReconstructedRates>
|
||||
ReconstructPhysicalRates(const FixedChart& chart, const EmbeddingRoot& root,
|
||||
const Vector& velocity, const Vector& acceleration,
|
||||
TangentSolver& solver) const override {
|
||||
return hht_reduced_internal::ReconstructPhysicalRates(chart, root, velocity,
|
||||
acceleration, solver);
|
||||
}
|
||||
};
|
||||
|
||||
struct Trial {
|
||||
@@ -320,6 +394,17 @@ Result<DynamicExecutionResult> ExecuteReducedHht(
|
||||
const Domain& domain, NewtonIterationLogWriter& log,
|
||||
const ParallelFor& parallel_for, LinearSolver& positive_solver,
|
||||
TangentSolver& tangent_solver) {
|
||||
const ReducedCoordinateShellHhtIntegrator integrator;
|
||||
NewtonStepAlgorithm algorithm;
|
||||
return ExecuteReducedHht(domain, log, parallel_for, positive_solver,
|
||||
tangent_solver, integrator, algorithm);
|
||||
}
|
||||
|
||||
Result<DynamicExecutionResult> ExecuteReducedHht(
|
||||
const Domain& domain, NewtonIterationLogWriter& log,
|
||||
const ParallelFor& parallel_for, LinearSolver& positive_solver,
|
||||
TangentSolver& tangent_solver, const ShellHhtIntegrator& integrator,
|
||||
EquilibriumAlgorithm& algorithm) {
|
||||
if (domain.Dynamic() == nullptr)
|
||||
return Result<DynamicExecutionResult>::Failure(Status::Failure(
|
||||
FailureCategory::kModel, {{Severity::kError, "invalid-dynamic-step", {},
|
||||
@@ -433,7 +518,7 @@ Result<DynamicExecutionResult> ExecuteReducedHht(
|
||||
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,
|
||||
auto rates0 = integrator.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));
|
||||
@@ -604,343 +689,434 @@ Result<DynamicExecutionResult> ExecuteReducedHht(
|
||||
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) {
|
||||
struct ReducedStepCandidate {
|
||||
DynamicAcceptedFrameState frame;
|
||||
ReducedHistory history;
|
||||
};
|
||||
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."));
|
||||
const auto report_failure = [&](const Status& failure, const char* stage) {
|
||||
const Status contextual = WithProcedureContext(
|
||||
domain, failure, stage, increment, record.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 written.IsOk() ? contextual : written;
|
||||
};
|
||||
status = RunReducedHhtStep(
|
||||
integrator, history, h, parameters,
|
||||
[&](const ShellHhtIntegrator& step_integrator,
|
||||
ReducedShellPredictor predictor) -> Result<ReducedStepCandidate> {
|
||||
Vector pp = std::move(predictor.pp);
|
||||
Vector vpp = std::move(predictor.vpp);
|
||||
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 = std::move(predictor.coordinate_low);
|
||||
Vector chosen_start_p = pp;
|
||||
Vector increment_trace = std::move(predictor.increment_trace);
|
||||
Vector increment_trace_compensation =
|
||||
std::move(predictor.increment_trace_compensation);
|
||||
Vector increment_trace_absolute_sum =
|
||||
std::move(predictor.increment_trace_absolute_sum);
|
||||
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()};
|
||||
using StepResult = Result<std::optional<ReducedStepCandidate>>;
|
||||
const auto write_iteration =
|
||||
[&](const DynamicNewtonIterationLogRecord& value) {
|
||||
return log.Write(value);
|
||||
};
|
||||
ReducedShellHhtStep<ReducedStepCandidate> iteration_step(
|
||||
[&](std::uint32_t iteration) -> StepResult {
|
||||
record.iteration_index = iteration;
|
||||
record.stage = "evaluation";
|
||||
record.outcome = "candidate";
|
||||
const auto fail = [&](const Status& failure,
|
||||
const char* stage = "outer") -> StepResult {
|
||||
return StepResult::Failure(report_failure(failure, stage));
|
||||
};
|
||||
ReducedShellTrialRates reduced_rates{
|
||||
Vector{history.p.Size()}, Vector{history.p.Size()}};
|
||||
step_integrator.EvaluateRates(
|
||||
{chart.Value(), history, trial_algorithmic, vpp,
|
||||
copy_translation_rates, retained_velocity, retained_acceleration,
|
||||
h, parameters}, reduced_rates.velocity,
|
||||
reduced_rates.acceleration);
|
||||
Vector trial_v = std::move(reduced_rates.velocity);
|
||||
Vector trial_a = std::move(reduced_rates.acceleration);
|
||||
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 = step_integrator.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;
|
||||
}
|
||||
step_integrator.EvaluateRates(
|
||||
{chart.Value(), history, trial_algorithmic, vpp, false,
|
||||
retained_velocity, retained_acceleration, h, parameters},
|
||||
trial_v, trial_a);
|
||||
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);
|
||||
}
|
||||
step_integrator.EvaluateRates(
|
||||
{chart.Value(), history, old_algorithmic, vpp, false,
|
||||
retained_velocity, retained_acceleration, h, parameters},
|
||||
old_velocity, old_acceleration);
|
||||
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;
|
||||
return StepResult::Success(ReducedStepCandidate{
|
||||
std::move(candidate),
|
||||
{trial_p, trial_v, trial_a, trial_algorithmic,
|
||||
trial.Value().root.z}});
|
||||
}
|
||||
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 = write_iteration(record);
|
||||
if (!status.IsOk())
|
||||
return StepResult::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];
|
||||
return StepResult::Success(std::nullopt);
|
||||
},
|
||||
kMaximumOuterCorrections);
|
||||
const Status solved = iteration_step.Solve(algorithm);
|
||||
if (!solved.IsOk())
|
||||
return Result<ReducedStepCandidate>::Failure(solved);
|
||||
if (!iteration_step.HasCandidate()) return Result<ReducedStepCandidate>::Failure(
|
||||
Failure(domain, "dynamic-nonconvergence", "Dynamic increment was not accepted."));
|
||||
return Result<ReducedStepCandidate>::Success(
|
||||
iteration_step.TakeCandidate());
|
||||
},
|
||||
[&](ReducedStepCandidate&& accepted_candidate) -> Status {
|
||||
status = state.CommitAcceptedFrame(accepted_candidate.frame);
|
||||
if (!status.IsOk())
|
||||
return report_failure(status, "accepted");
|
||||
history = std::move(accepted_candidate.history);
|
||||
record.stage = "accepted";
|
||||
record.outcome = "accepted";
|
||||
status = log.Write(record);
|
||||
if (status.IsOk())
|
||||
status = log.Flush();
|
||||
return status;
|
||||
});
|
||||
if (!status.IsOk()) return Result<DynamicExecutionResult>::Failure(status);
|
||||
}
|
||||
return Result<DynamicExecutionResult>::Success({std::move(state), domain.Warnings()});
|
||||
return Result<DynamicExecutionResult>::Success(
|
||||
{std::move(state), domain.Warnings()});
|
||||
}
|
||||
} // namespace fesa
|
||||
} // namespace fesa
|
||||
|
||||
@@ -3,16 +3,20 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "fesa/analysis/prepared_analysis.h"
|
||||
#include "fesa/analysis/dynamic_execution_result.h"
|
||||
#include "fesa/core/status.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace hht_reduced_internal {
|
||||
class ShellHhtIntegrator;
|
||||
constexpr std::uint32_t kMaximumOuterCorrections = 100U;
|
||||
constexpr bool CanApplyOuterCorrection(const std::uint32_t completed) {
|
||||
return completed < kMaximumOuterCorrections;
|
||||
}
|
||||
} // namespace hht_reduced_internal
|
||||
|
||||
class Domain;
|
||||
class EquilibriumAlgorithm;
|
||||
class LinearSolver;
|
||||
class NewtonIterationLogWriter;
|
||||
class ParallelFor;
|
||||
@@ -22,6 +26,14 @@ Result<DynamicExecutionResult> ExecuteReducedHht(
|
||||
const Domain& domain, NewtonIterationLogWriter& log,
|
||||
const ParallelFor& parallel_for, LinearSolver& positive_solver,
|
||||
TangentSolver& tangent_solver);
|
||||
|
||||
/// @brief Borrows caller iteration policy for the complete production core.
|
||||
Result<DynamicExecutionResult> ExecuteReducedHht(
|
||||
const Domain& domain, NewtonIterationLogWriter& log,
|
||||
const ParallelFor& parallel_for, LinearSolver& positive_solver,
|
||||
TangentSolver& tangent_solver,
|
||||
const hht_reduced_internal::ShellHhtIntegrator& integrator,
|
||||
EquilibriumAlgorithm& algorithm);
|
||||
} // namespace fesa
|
||||
|
||||
#endif // FESA_ANALYSIS_HHT_REDUCED_PROCEDURE_H_
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "fesa/elements/mitc4_shell.h"
|
||||
#include "fesa/fem/dof_manager.h"
|
||||
#include "fesa/math/sparse_matrix.h"
|
||||
#include "fesa/solvers/eigen/mkl_feast_eigen_solver.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
@@ -84,7 +85,8 @@ double MaximumEdgeLength(const Domain& domain) {
|
||||
return maximum;
|
||||
}
|
||||
|
||||
Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain) {
|
||||
Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain,
|
||||
const EigenSolver& solver) {
|
||||
const FrequencyStepDefinition* frequency = domain.Frequency();
|
||||
if (frequency == nullptr || domain.Steps().Size() != 1U) {
|
||||
return Result<EigenAnalysisState>::Failure(
|
||||
@@ -101,6 +103,7 @@ Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain) {
|
||||
return Result<EigenAnalysisState>::Failure(model.GetStatus());
|
||||
const ElementFactory factory;
|
||||
std::vector<std::unique_ptr<Element>> owned;
|
||||
std::vector<std::unique_ptr<ShellReferenceElement>> references;
|
||||
ElementView view;
|
||||
owned.reserve(model.Value().ActiveElements().size());
|
||||
view.reserve(model.Value().ActiveElements().size());
|
||||
@@ -108,13 +111,15 @@ Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain) {
|
||||
auto element = factory.Create(domain.Elements()[index], domain);
|
||||
if (!element.HasValue())
|
||||
return Result<EigenAnalysisState>::Failure(element.GetStatus());
|
||||
if (dynamic_cast<const Mitc4Shell*>(element.Value().get()) == nullptr) {
|
||||
auto reference = factory.BindShellReference(*element.Value());
|
||||
if (reference == nullptr) {
|
||||
return Result<EigenAnalysisState>::Failure(Failure(
|
||||
"unsupported-frequency-element", std::to_string(index),
|
||||
"The frequency procedure supports MITC4 shell elements only."));
|
||||
}
|
||||
view.emplace_back(*element.Value());
|
||||
owned.push_back(std::move(element.Value()));
|
||||
references.push_back(std::move(reference));
|
||||
}
|
||||
DofManager dofs;
|
||||
const Status dof_status = dofs.Build(model.Value(), view);
|
||||
@@ -158,7 +163,7 @@ Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain) {
|
||||
std::to_string(definition.MaterialIndex()),
|
||||
"Every active shell material requires a positive density."));
|
||||
}
|
||||
const auto* element = dynamic_cast<const Mitc4Shell*>(owned[shell].get());
|
||||
const auto& element = references[shell];
|
||||
auto mass = element->Mass(density[definition.MaterialIndex()]);
|
||||
auto shell_stiffness = element->Stiffness();
|
||||
if (!mass.HasValue())
|
||||
@@ -207,7 +212,7 @@ Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain) {
|
||||
(material->YoungsModulus() / density[material_id]) /
|
||||
length / length);
|
||||
}
|
||||
auto backend = MklFeastEigenSolver{}.Solve(
|
||||
auto backend = solver.Solve(
|
||||
reduction.Value().whitened_operator, request, zero_scale);
|
||||
if (!backend.HasValue())
|
||||
return Result<EigenAnalysisState>::Failure(backend.GetStatus());
|
||||
@@ -403,7 +408,8 @@ Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain) {
|
||||
Result<EigenAnalysisState> LinearFrequencyProcedureExecutor::Execute(
|
||||
const Domain& domain) const {
|
||||
try {
|
||||
auto result = ExecuteCandidate(domain);
|
||||
const MklFeastEigenSolver default_solver;
|
||||
auto result = ExecuteCandidate(domain, solver_ ? *solver_ : default_solver);
|
||||
if (!result.HasValue()) {
|
||||
auto diagnostics = result.GetStatus().Diagnostics();
|
||||
for (auto& diagnostic : diagnostics) {
|
||||
|
||||
@@ -142,7 +142,7 @@ Result<LinearStaticExecutionResult> LinearStaticProcedureExecutor::Execute(
|
||||
}
|
||||
state.ExternalForce() = std::move(nodal_external.Value());
|
||||
|
||||
Result<Vector> rhs = LoadAssembler::EffectiveFreeRhs(
|
||||
Result<Vector> rhs = EssentialConstraintPolicy{}.EffectiveFreeRhs(
|
||||
state.ExternalForce(), partitions.Value().k_fc, dofs.PrescribedValues(),
|
||||
dofs);
|
||||
if (!rhs.HasValue()) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "fesa/analysis/nonlinear_analysis_state.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
@@ -9,11 +8,18 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/core/source_identity.h"
|
||||
#include "fesa/elements/element_definition.h"
|
||||
#include "math/finite_values.h"
|
||||
#include "results/shell_result_recovery.h"
|
||||
#include "analysis/shell_state_inventory.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
using analysis_internal::ShellElementOrder;
|
||||
using math_internal::IsFinite;
|
||||
|
||||
constexpr std::size_t kDofsPerNode = 6U;
|
||||
constexpr std::size_t kShellLocationsPerElement = 4U;
|
||||
constexpr std::uint32_t kMaximumNewtonIterations = 100U;
|
||||
@@ -27,59 +33,6 @@ Status StateFailure(const FailureCategory category, const std::string& code,
|
||||
message}});
|
||||
}
|
||||
|
||||
/// @brief Compares every preserved source-identity component.
|
||||
bool SourceIdsMatch(const SourceEntityId& left, const SourceEntityId& right) {
|
||||
return left.instance_name == right.instance_name &&
|
||||
left.source_label == right.source_label &&
|
||||
left.source_label_text == right.source_label_text;
|
||||
}
|
||||
|
||||
template <std::size_t Size>
|
||||
/// @brief Tests one fixed-size numerical array for finite values.
|
||||
bool IsFinite(const std::array<double, Size>& values) {
|
||||
return std::all_of(values.begin(), values.end(),
|
||||
[](const double value) { return std::isfinite(value); });
|
||||
}
|
||||
|
||||
/// @brief Tests each component of a stored trial vector for finiteness.
|
||||
bool IsFinite(const Vector& values) {
|
||||
for (std::size_t index = 0U; index < values.Size(); ++index) {
|
||||
if (!std::isfinite(values[index])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @brief Tests all physical quantities attached to one shell recovery row.
|
||||
bool IsFinite(const ShellResultRow& row) {
|
||||
if (!IsFinite(row.natural_coordinates) || !IsFinite(row.generalized_strain) ||
|
||||
!IsFinite(row.section_resultant)) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& axis : row.local_frame) {
|
||||
if (!IsFinite(axis)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return std::all_of(row.stress.begin(), row.stress.end(),
|
||||
[](const ShellSectionStressRow& stress) {
|
||||
return std::isfinite(stress.zeta) &&
|
||||
IsFinite(stress.components);
|
||||
});
|
||||
}
|
||||
|
||||
/// @brief Returns shell Domain indices in deterministic unified element order.
|
||||
std::vector<EntityIndex> ShellElementOrder(const Domain& domain) {
|
||||
std::vector<EntityIndex> order;
|
||||
for (std::size_t index = 0U; index < domain.Elements().Size(); ++index) {
|
||||
if (domain.Elements()[index].Kind() == ElementDefinitionKind::kMitc4Shell) {
|
||||
order.push_back(static_cast<EntityIndex>(index));
|
||||
}
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
/// @brief Validates one accepted frame before any committed-state mutation.
|
||||
Status ValidateAcceptedFrame(const AcceptedFrameState& frame,
|
||||
const NonlinearTrialState& committed,
|
||||
@@ -117,7 +70,7 @@ Status ValidateAcceptedFrame(const AcceptedFrameState& frame,
|
||||
for (std::size_t node = 0U; node < node_count; ++node) {
|
||||
if (frame.nodal_internal_order[node] !=
|
||||
committed.nodal_internal_order[node] ||
|
||||
!SourceIdsMatch(frame.nodal_source_ids[node],
|
||||
!SourceIdentityEquals(frame.nodal_source_ids[node],
|
||||
committed.nodal_source_ids[node]) ||
|
||||
!IsFinite(frame.nodal_triad[node][0U]) ||
|
||||
!IsFinite(frame.nodal_triad[node][1U]) ||
|
||||
@@ -179,7 +132,7 @@ Status ValidateAcceptedFrame(const AcceptedFrameState& frame,
|
||||
if (row.element != shell_order[element] ||
|
||||
row.location != expected_locations[point] ||
|
||||
row.natural_coordinates != expected_coordinates[point] ||
|
||||
!IsFinite(row)) {
|
||||
!results_internal::IsFiniteShellRow(row)) {
|
||||
return StateFailure(FailureCategory::kSolver, "invalid-accepted-state", {},
|
||||
"Shell result rows do not preserve stable identity.");
|
||||
}
|
||||
@@ -241,7 +194,7 @@ Result<NonlinearAnalysisState> NonlinearAnalysisState::Create(
|
||||
for (std::size_t node = 0U; node < node_count; ++node) {
|
||||
const auto& entry = baseline.node_baselines[node];
|
||||
if (entry.node_index != static_cast<EntityIndex>(node) ||
|
||||
!SourceIdsMatch(entry.source_id, domain.Nodes()[node].source_id) ||
|
||||
!SourceIdentityEquals(entry.source_id, domain.Nodes()[node].source_id) ||
|
||||
!IsFinite(entry.triad[0U]) || !IsFinite(entry.triad[1U]) ||
|
||||
!IsFinite(entry.triad[2U]) || !IsFinite(entry.director)) {
|
||||
return Result<NonlinearAnalysisState>::Failure(StateFailure(
|
||||
|
||||
@@ -12,10 +12,17 @@
|
||||
#include "fesa/analysis/analysis_model.h"
|
||||
#include "fesa/assembly/load_assembler.h"
|
||||
#include "fesa/elements/finite_rotation.h"
|
||||
#include "math/vector3_operations.h"
|
||||
#include "elements/shell_kernel_values.h"
|
||||
#include "math/dense_blas_internal.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
using shell_kernel_internal::FrameMatrix;
|
||||
using math_internal::Multiply;
|
||||
using math_internal::Skew;
|
||||
|
||||
constexpr std::size_t kDofsPerNode = 6U;
|
||||
constexpr std::size_t kTranslationDofCount = 3U;
|
||||
constexpr double kMomentAdmissibilityThreshold = 1.0e-12;
|
||||
@@ -67,27 +74,6 @@ Result<Vector3> RotationVectorForNode(const Vector& trial,
|
||||
return Result<Vector3>::Success(rotation);
|
||||
}
|
||||
|
||||
/// @brief Builds a proper initial frame from its semantic nodal record.
|
||||
Matrix InitialFrame(const ShellNodeInitialFrame& frame) {
|
||||
Matrix result{3U, 3U};
|
||||
for (std::size_t row = 0U; row < kTranslationDofCount; ++row) {
|
||||
result(row, 0U) = frame.tangent_a[row];
|
||||
result(row, 1U) = frame.tangent_b[row];
|
||||
result(row, 2U) = frame.director[row];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief Multiplies a 3-by-3 matrix by a Vector3.
|
||||
Vector3 Multiply(const Matrix& matrix, const Vector3& vector) {
|
||||
return {matrix(0U, 0U) * vector.X() + matrix(0U, 1U) * vector.Y() +
|
||||
matrix(0U, 2U) * vector.Z(),
|
||||
matrix(1U, 0U) * vector.X() + matrix(1U, 1U) * vector.Y() +
|
||||
matrix(1U, 2U) * vector.Z(),
|
||||
matrix(2U, 0U) * vector.X() + matrix(2U, 1U) * vector.Y() +
|
||||
matrix(2U, 2U) * vector.Z()};
|
||||
}
|
||||
|
||||
/// @brief Multiplies a 3-by-3 matrix transpose by a Vector3.
|
||||
Vector3 MultiplyTranspose(const Matrix& matrix, const Vector3& vector) {
|
||||
return {matrix(0U, 0U) * vector.X() + matrix(1U, 0U) * vector.Y() +
|
||||
@@ -98,37 +84,18 @@ Vector3 MultiplyTranspose(const Matrix& matrix, const Vector3& vector) {
|
||||
matrix(2U, 2U) * vector.Z()};
|
||||
}
|
||||
|
||||
/// @brief Forms the three-dimensional skew matrix of a global vector.
|
||||
Matrix Skew(const Vector3& vector) {
|
||||
Matrix result{3U, 3U};
|
||||
result(0U, 1U) = -vector.Z();
|
||||
result(0U, 2U) = vector.Y();
|
||||
result(1U, 0U) = vector.Z();
|
||||
result(1U, 2U) = -vector.X();
|
||||
result(2U, 0U) = -vector.Y();
|
||||
result(2U, 1U) = vector.X();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief Scales a small dense matrix without changing its shape.
|
||||
Matrix Scale(const Matrix& matrix, const double scale) {
|
||||
Matrix result{matrix.Rows(), matrix.Columns()};
|
||||
for (std::size_t row = 0U; row < matrix.Rows(); ++row) {
|
||||
for (std::size_t column = 0U; column < matrix.Columns(); ++column) {
|
||||
result(row, column) = scale * matrix(row, column);
|
||||
}
|
||||
}
|
||||
Matrix result{matrix};
|
||||
result.Scale(scale);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief Adds matching small dense matrices.
|
||||
Matrix Add(const Matrix& left, const Matrix& right) {
|
||||
Matrix result{left.Rows(), left.Columns()};
|
||||
for (std::size_t row = 0U; row < left.Rows(); ++row) {
|
||||
for (std::size_t column = 0U; column < left.Columns(); ++column) {
|
||||
result(row, column) = left(row, column) + right(row, column);
|
||||
}
|
||||
}
|
||||
Matrix result{left};
|
||||
dense_blas_internal::AxpyValues(1.0, right.Data(),
|
||||
left.Rows() * left.Columns(), result.Data());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -472,7 +439,7 @@ Result<GlobalFixedLoadContribution> AssembleGlobalFixedLoadContribution(
|
||||
return Result<GlobalFixedLoadContribution>::Failure(phi.GetStatus());
|
||||
}
|
||||
auto rotation =
|
||||
MakeFiniteRotationState(phi.Value(), InitialFrame(*frames[node]));
|
||||
MakeFiniteRotationState(phi.Value(), FrameMatrix(*frames[node]));
|
||||
if (!rotation.HasValue()) {
|
||||
return Result<GlobalFixedLoadContribution>::Failure(rotation.GetStatus());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef FESA_ANALYSIS_NONLINEAR_STATIC_CORE_H_
|
||||
#define FESA_ANALYSIS_NONLINEAR_STATIC_CORE_H_
|
||||
#include "fesa/analysis/nonlinear_static_execution_result.h"
|
||||
namespace fesa {
|
||||
class EquilibriumAlgorithm;
|
||||
class NewtonIterationLogWriter;
|
||||
class ParallelFor;
|
||||
class TangentSolver;
|
||||
/// @brief Runs the production core with caller-owned iteration policy.
|
||||
/// @note All adapters outlive this synchronous call; candidate/history stay local.
|
||||
Result<NonlinearStaticExecutionResult> ExecuteNonlinearStaticCore(
|
||||
const Domain& domain, NewtonIterationLogWriter& log,
|
||||
const ParallelFor& parallel_for, TangentSolver& tangent_solver,
|
||||
EquilibriumAlgorithm& algorithm);
|
||||
} // namespace fesa
|
||||
#endif // FESA_ANALYSIS_NONLINEAR_STATIC_CORE_H_
|
||||
@@ -1,11 +1,15 @@
|
||||
#include "fesa/analysis/nonlinear_static_procedure.h"
|
||||
|
||||
#include "equilibrium_algorithm.h"
|
||||
#include "analysis/nonlinear_static_core.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -17,6 +21,7 @@
|
||||
#include "fesa/assembly/parallel_for.h"
|
||||
#include "fesa/assembly/surface_pressure_assembler.h"
|
||||
#include "fesa/constraints/essential_constraint_policy.h"
|
||||
#include "fesa/elements/element_capabilities.h"
|
||||
#include "fesa/elements/finite_rotation.h"
|
||||
#include "fesa/elements/mitc4_shell.h"
|
||||
#include "fesa/elements/nonlinear_mitc4_shell.h"
|
||||
@@ -25,9 +30,16 @@
|
||||
#include "fesa/properties/shell_section.h"
|
||||
#include "fesa/solvers/linear/tangent_solver.h"
|
||||
|
||||
#include "assembly/ordered_contribution_internal.h"
|
||||
#include "math/finite_values.h"
|
||||
#include "elements/shell_kernel_values.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
using shell_kernel_internal::FrameMatrix;
|
||||
using math_internal::IsFinite;
|
||||
|
||||
constexpr std::size_t kDofsPerNode = 6U;
|
||||
constexpr std::size_t kTranslationsPerNode = 3U;
|
||||
constexpr std::size_t kShellDofCount = 24U;
|
||||
@@ -36,7 +48,8 @@ constexpr std::uint32_t kMaximumNewtonIterations = 100U;
|
||||
struct RuntimeShell {
|
||||
EntityIndex element_index;
|
||||
std::array<std::size_t, kShellDofCount> scatter;
|
||||
NonlinearMitc4Shell element;
|
||||
std::unique_ptr<NonlinearMitc4Shell> kernel;
|
||||
std::unique_ptr<ShellTrialElement> element;
|
||||
};
|
||||
|
||||
struct InternalAssembly {
|
||||
@@ -124,16 +137,6 @@ Status RecordFailure(NewtonIterationLogWriter& log,
|
||||
return log_status.IsOk() ? failure : log_status;
|
||||
}
|
||||
|
||||
Matrix InitialFrame(const ShellNodeInitialFrame& frame) {
|
||||
Matrix result{3U, 3U};
|
||||
for (std::size_t row = 0U; row < kTranslationsPerNode; ++row) {
|
||||
result(row, 0U) = frame.tangent_a[row];
|
||||
result(row, 1U) = frame.tangent_b[row];
|
||||
result(row, 2U) = frame.director[row];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Result<DofManager> BuildDofManager(const Domain& domain) {
|
||||
Result<AnalysisModel> model = AnalysisModel::Create(domain);
|
||||
if (!model.HasValue()) {
|
||||
@@ -239,9 +242,20 @@ Result<std::vector<RuntimeShell>> BuildRuntimeShells(const Domain& domain,
|
||||
return Result<std::vector<RuntimeShell>>::Failure(shell.GetStatus());
|
||||
}
|
||||
const EntityIndex element_index = static_cast<EntityIndex>(index);
|
||||
auto kernel =
|
||||
std::make_unique<NonlinearMitc4Shell>(std::move(shell.Value()));
|
||||
auto element = std::make_unique<Mitc4TrialAdapter>(
|
||||
ElementDofLayout{definition->SourceId(), definition->NodeIndices(),
|
||||
{DofComponent::kUx, DofComponent::kUy, DofComponent::kUz,
|
||||
DofComponent::kUrx, DofComponent::kUry, DofComponent::kUrz}},
|
||||
*kernel);
|
||||
auto mapped = dofs.ElementScatter(element->DofLayout());
|
||||
if (!mapped.HasValue())
|
||||
return Result<std::vector<RuntimeShell>>::Failure(mapped.GetStatus());
|
||||
std::array<std::size_t, kShellDofCount> scatter{};
|
||||
std::copy(mapped.Value().begin(), mapped.Value().end(), scatter.begin());
|
||||
shells.push_back(
|
||||
{element_index, dofs.ShellElementScatter(element_index),
|
||||
std::move(shell.Value())});
|
||||
{element_index, scatter, std::move(kernel), std::move(element)});
|
||||
}
|
||||
return Result<std::vector<RuntimeShell>>::Success(std::move(shells));
|
||||
}
|
||||
@@ -269,7 +283,7 @@ Status PopulateNodalKinematics(const Domain& domain,
|
||||
trial->generalized_coordinates[first + 4U],
|
||||
trial->generalized_coordinates[first + 5U]};
|
||||
Result<FiniteRotationState> rotation = MakeFiniteRotationState(
|
||||
phi, InitialFrame(domain.ShellNodeInitialFrames()[node]));
|
||||
phi, FrameMatrix(domain.ShellNodeInitialFrames()[node]));
|
||||
if (!rotation.HasValue()) {
|
||||
return rotation.GetStatus();
|
||||
}
|
||||
@@ -304,26 +318,6 @@ NonlinearMitc4TrialDofs ExtractElementTrial(
|
||||
return trial;
|
||||
}
|
||||
|
||||
bool IsFinite(const Vector& values) {
|
||||
for (std::size_t index = 0U; index < values.Size(); ++index) {
|
||||
if (!std::isfinite(values[index])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsFinite(const Matrix& values) {
|
||||
for (std::size_t row = 0U; row < values.Rows(); ++row) {
|
||||
for (std::size_t column = 0U; column < values.Columns(); ++column) {
|
||||
if (!std::isfinite(values(row, column))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Result<InternalAssembly> AssembleInternal(
|
||||
const std::vector<RuntimeShell>& shells, const DofManager& dofs,
|
||||
const Vector& coordinates, const ParallelFor& parallel_for) {
|
||||
@@ -332,9 +326,8 @@ Result<InternalAssembly> AssembleInternal(
|
||||
std::vector<std::optional<Status>> failures(shells.size());
|
||||
parallel_for.Execute(shells.size(), [&](const std::size_t index) {
|
||||
Result<NonlinearMitc4InternalEquilibrium> result =
|
||||
EvaluateInternalResidualAndTangent(shells[index].element,
|
||||
ExtractElementTrial(shells[index],
|
||||
coordinates));
|
||||
shells[index].element->EvaluateTrial(
|
||||
ExtractElementTrial(shells[index], coordinates));
|
||||
if (!result.HasValue()) {
|
||||
failures[index] = result.GetStatus();
|
||||
return;
|
||||
@@ -363,12 +356,9 @@ Result<InternalAssembly> AssembleInternal(
|
||||
}
|
||||
for (std::size_t row = 0U; row < kShellDofCount; ++row) {
|
||||
force[shells[index].scatter[row]] += local[index]->internal_residual[row];
|
||||
for (std::size_t column = 0U; column < kShellDofCount; ++column) {
|
||||
contributions.push_back(
|
||||
{shells[index].scatter[row], shells[index].scatter[column],
|
||||
local[index]->internal_tangent(row, column), index,
|
||||
row * kShellDofCount + column});
|
||||
}
|
||||
assembly_internal::AppendLocalMatrixRow(
|
||||
local[index]->internal_tangent, shells[index].scatter, row, index,
|
||||
contributions);
|
||||
}
|
||||
for (const NonlinearMitc4RecoveryPoint& point :
|
||||
local[index]->recovery.gauss_points) {
|
||||
@@ -573,19 +563,6 @@ Vector CurrentIncrementComponents(const Vector& current, const Vector& start,
|
||||
return SelectFreeComponents(difference, dofs, rotations);
|
||||
}
|
||||
|
||||
Vector3 SpatialMoment(const Matrix& transform,
|
||||
const std::array<double, 3>& generalized) {
|
||||
return {transform(0U, 0U) * generalized[0U] +
|
||||
transform(0U, 1U) * generalized[1U] +
|
||||
transform(0U, 2U) * generalized[2U],
|
||||
transform(1U, 0U) * generalized[0U] +
|
||||
transform(1U, 1U) * generalized[1U] +
|
||||
transform(1U, 2U) * generalized[2U],
|
||||
transform(2U, 0U) * generalized[0U] +
|
||||
transform(2U, 1U) * generalized[1U] +
|
||||
transform(2U, 2U) * generalized[2U]};
|
||||
}
|
||||
|
||||
/// @brief Computes accepted-state applied-plus-reaction balance evidence.
|
||||
Result<std::array<double, 6>> GlobalEquilibrium(
|
||||
const Domain& domain, const DofManager& dofs,
|
||||
@@ -634,7 +611,6 @@ Result<std::array<double, 6>> GlobalEquilibrium(
|
||||
constrained[first] ? trial.residual[first] : 0.0,
|
||||
constrained[first + 1U] ? trial.residual[first + 1U] : 0.0,
|
||||
constrained[first + 2U] ? trial.residual[first + 2U] : 0.0};
|
||||
const Matrix transform = InverseTransposeLeftJacobianSO3(phi);
|
||||
const std::array<double, 3> applied_generalized{
|
||||
trial.external_force[first + 3U], trial.external_force[first + 4U],
|
||||
trial.external_force[first + 5U]};
|
||||
@@ -642,9 +618,16 @@ Result<std::array<double, 6>> GlobalEquilibrium(
|
||||
constrained[first + 3U] ? trial.residual[first + 3U] : 0.0,
|
||||
constrained[first + 4U] ? trial.residual[first + 4U] : 0.0,
|
||||
constrained[first + 5U] ? trial.residual[first + 5U] : 0.0};
|
||||
const Vector3 applied_moment = SpatialMoment(transform, applied_generalized);
|
||||
const Vector3 reaction_moment =
|
||||
SpatialMoment(transform, reaction_generalized);
|
||||
const auto applied = SpatialMomentSO3(phi, Vector3(applied_generalized));
|
||||
const auto reaction = SpatialMomentSO3(phi, Vector3(reaction_generalized));
|
||||
if (!applied.HasValue() || !reaction.HasValue())
|
||||
return Result<std::array<double, 6>>::Failure(ProcedureFailure(
|
||||
FailureCategory::kSolver, "nonfinite-newton-data",
|
||||
{domain.SourcePath(), 0U}, "NONLINEAR_STATIC_PROCEDURE",
|
||||
domain.SourceContentIdentity(),
|
||||
"Nonlinear global equilibrium balance is nonfinite."));
|
||||
const Vector3 applied_moment = applied.Value();
|
||||
const Vector3 reaction_moment = reaction.Value();
|
||||
applied_force_resultant = applied_force_resultant + applied_force;
|
||||
applied_moment_resultant =
|
||||
applied_moment_resultant + position.Cross(applied_force) + applied_moment;
|
||||
@@ -694,11 +677,11 @@ Status ApplyIncrementConstraints(const StepDefinition& step,
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
AcceptedFrameState AcceptedFrame(const NonlinearAnalysisState& state,
|
||||
AcceptedFrameState AcceptedFrame(const std::uint32_t frame_index,
|
||||
const NonlinearTrialState& trial,
|
||||
const LoadFactorIncrement& increment,
|
||||
const std::uint32_t iteration) {
|
||||
return {static_cast<std::uint32_t>(state.AcceptedFrames().size()),
|
||||
return {frame_index,
|
||||
increment.increment_index,
|
||||
increment.load_factor,
|
||||
iteration,
|
||||
@@ -728,6 +711,15 @@ NonlinearStaticProcedureExecutor::NonlinearStaticProcedureExecutor(
|
||||
Result<NonlinearStaticExecutionResult>
|
||||
NonlinearStaticProcedureExecutor::Execute(
|
||||
const Domain& domain, NewtonIterationLogWriter& log) const {
|
||||
NewtonStepAlgorithm algorithm;
|
||||
return ExecuteNonlinearStaticCore(domain, log, parallel_for_, tangent_solver_,
|
||||
algorithm);
|
||||
}
|
||||
|
||||
Result<NonlinearStaticExecutionResult> ExecuteNonlinearStaticCore(
|
||||
const Domain& domain, NewtonIterationLogWriter& log,
|
||||
const ParallelFor& parallel_for, TangentSolver& tangent_solver,
|
||||
EquilibriumAlgorithm& algorithm) {
|
||||
if (domain.Steps().Size() != 1U || !domain.Steps()[0U].IsNonlinearStatic()) {
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(ProcedureFailure(
|
||||
FailureCategory::kInput, "invalid-nonlinear-static-data",
|
||||
@@ -782,199 +774,237 @@ NonlinearStaticProcedureExecutor::Execute(
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(constraint_status);
|
||||
}
|
||||
|
||||
for (std::uint32_t iteration = 1U; iteration <= kMaximumNewtonIterations;
|
||||
++iteration) {
|
||||
NewtonIterationLogRecord record =
|
||||
NewLogRecord(step, increment, iteration);
|
||||
trial.Value().increment_index = increment.increment_index;
|
||||
trial.Value().load_factor = increment.load_factor;
|
||||
trial.Value().is_factor_zero_baseline = false;
|
||||
struct ShellStepCandidate {
|
||||
AcceptedFrameState frame;
|
||||
NewtonIterationLogRecord record;
|
||||
};
|
||||
const auto next_frame_index =
|
||||
static_cast<std::uint32_t>(state.Value().AcceptedFrames().size());
|
||||
const auto report_failure = [&](NewtonIterationLogRecord* record,
|
||||
const Status& failure,
|
||||
NewtonIterationStage stage) {
|
||||
return RecordFailure(log, record, failure, stage);
|
||||
};
|
||||
const auto write_iteration = [&](const NewtonIterationLogRecord& record) {
|
||||
return WriteAndFlush(log, record);
|
||||
};
|
||||
ShellFullNewtonStep<ShellStepCandidate> iteration_step(
|
||||
[&](std::uint32_t iteration)
|
||||
-> Result<std::optional<ShellStepCandidate>> {
|
||||
NewtonIterationLogRecord record =
|
||||
NewLogRecord(step, increment, iteration);
|
||||
trial.Value().increment_index = increment.increment_index;
|
||||
trial.Value().load_factor = increment.load_factor;
|
||||
trial.Value().is_factor_zero_baseline = false;
|
||||
|
||||
Status kinematics = PopulateNodalKinematics(domain, &trial.Value());
|
||||
if (!kinematics.IsOk()) {
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(
|
||||
RecordFailure(log, &record, kinematics,
|
||||
NewtonIterationStage::kKinematicsEvaluated));
|
||||
}
|
||||
record.last_completed_stage = NewtonIterationStage::kKinematicsEvaluated;
|
||||
Status kinematics = PopulateNodalKinematics(domain, &trial.Value());
|
||||
if (!kinematics.IsOk()) {
|
||||
return Result<std::optional<ShellStepCandidate>>::Failure(
|
||||
report_failure(&record, kinematics,
|
||||
NewtonIterationStage::kKinematicsEvaluated));
|
||||
}
|
||||
record.last_completed_stage =
|
||||
NewtonIterationStage::kKinematicsEvaluated;
|
||||
|
||||
Result<InternalAssembly> internal = AssembleInternal(
|
||||
shells.Value(), dofs.Value(), trial.Value().generalized_coordinates,
|
||||
parallel_for_);
|
||||
if (!internal.HasValue()) {
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
||||
log, &record, internal.GetStatus(),
|
||||
NewtonIterationStage::kInternalAssembled));
|
||||
}
|
||||
record.last_completed_stage = NewtonIterationStage::kInternalAssembled;
|
||||
Result<InternalAssembly> internal = AssembleInternal(
|
||||
shells.Value(), dofs.Value(),
|
||||
trial.Value().generalized_coordinates, parallel_for);
|
||||
if (!internal.HasValue()) {
|
||||
return Result<std::optional<ShellStepCandidate>>::Failure(
|
||||
report_failure(&record, internal.GetStatus(),
|
||||
NewtonIterationStage::kInternalAssembled));
|
||||
}
|
||||
record.last_completed_stage =
|
||||
NewtonIterationStage::kInternalAssembled;
|
||||
|
||||
Result<GlobalFixedLoadContribution> fixed =
|
||||
AssembleGlobalFixedLoadContribution(
|
||||
domain, dofs.Value(), trial.Value().generalized_coordinates,
|
||||
increment.load_factor);
|
||||
if (!fixed.HasValue()) {
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(
|
||||
RecordFailure(log, &record, fixed.GetStatus(),
|
||||
NewtonIterationStage::kExternalAssembled));
|
||||
}
|
||||
Result<SurfacePressureAssemblyCandidate> pressure =
|
||||
AssembleSurfacePressure(
|
||||
analysis_model.Value(), dofs.Value(),
|
||||
trial.Value().generalized_coordinates, increment.load_factor,
|
||||
SurfacePressureConfiguration::kCurrent, parallel_for_);
|
||||
if (!pressure.HasValue()) {
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(
|
||||
RecordFailure(log, &record, pressure.GetStatus(),
|
||||
NewtonIterationStage::kExternalAssembled));
|
||||
}
|
||||
Result<GlobalFixedLoadContribution> external =
|
||||
CombineExternalContributions(fixed.Value(), pressure.Value(),
|
||||
dofs.Value());
|
||||
if (!external.HasValue()) {
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(
|
||||
RecordFailure(log, &record, external.GetStatus(),
|
||||
NewtonIterationStage::kExternalAssembled));
|
||||
}
|
||||
record.last_completed_stage = NewtonIterationStage::kExternalAssembled;
|
||||
Result<GlobalFixedLoadContribution> fixed =
|
||||
AssembleGlobalFixedLoadContribution(
|
||||
domain, dofs.Value(), trial.Value().generalized_coordinates,
|
||||
increment.load_factor);
|
||||
if (!fixed.HasValue()) {
|
||||
return Result<std::optional<ShellStepCandidate>>::Failure(
|
||||
report_failure(&record, fixed.GetStatus(),
|
||||
NewtonIterationStage::kExternalAssembled));
|
||||
}
|
||||
Result<SurfacePressureAssemblyCandidate> pressure =
|
||||
AssembleSurfacePressure(
|
||||
analysis_model.Value(), dofs.Value(),
|
||||
trial.Value().generalized_coordinates, increment.load_factor,
|
||||
SurfacePressureConfiguration::kCurrent, parallel_for);
|
||||
if (!pressure.HasValue()) {
|
||||
return Result<std::optional<ShellStepCandidate>>::Failure(
|
||||
report_failure(&record, pressure.GetStatus(),
|
||||
NewtonIterationStage::kExternalAssembled));
|
||||
}
|
||||
Result<GlobalFixedLoadContribution> external =
|
||||
CombineExternalContributions(fixed.Value(), pressure.Value(),
|
||||
dofs.Value());
|
||||
if (!external.HasValue()) {
|
||||
return Result<std::optional<ShellStepCandidate>>::Failure(
|
||||
report_failure(&record, external.GetStatus(),
|
||||
NewtonIterationStage::kExternalAssembled));
|
||||
}
|
||||
record.last_completed_stage =
|
||||
NewtonIterationStage::kExternalAssembled;
|
||||
|
||||
Result<Vector> residual =
|
||||
Difference(internal.Value().force,
|
||||
external.Value().generalized_external_force);
|
||||
if (!residual.HasValue()) {
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
||||
log, &record, residual.GetStatus(),
|
||||
NewtonIterationStage::kExternalAssembled));
|
||||
}
|
||||
Result<SparseMatrix> tangent =
|
||||
Difference(internal.Value().tangent,
|
||||
external.Value().generalized_external_tangent,
|
||||
dofs.Value());
|
||||
if (!tangent.HasValue()) {
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
||||
log, &record, tangent.GetStatus(),
|
||||
NewtonIterationStage::kExternalAssembled));
|
||||
}
|
||||
trial.Value().internal_force = std::move(internal.Value().force);
|
||||
trial.Value().external_force =
|
||||
std::move(external.Value().generalized_external_force);
|
||||
trial.Value().residual = std::move(residual.Value());
|
||||
trial.Value().reaction = trial.Value().residual;
|
||||
trial.Value().shell_results = std::move(internal.Value().shell_rows);
|
||||
trial.Value().physical_strain_energy =
|
||||
internal.Value().physical_strain_energy;
|
||||
Result<std::array<double, 6>> global_equilibrium =
|
||||
GlobalEquilibrium(domain, dofs.Value(), trial.Value());
|
||||
if (!global_equilibrium.HasValue()) {
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
||||
log, &record, global_equilibrium.GetStatus(),
|
||||
NewtonIterationStage::kExternalAssembled));
|
||||
}
|
||||
trial.Value().global_equilibrium = global_equilibrium.Value();
|
||||
Result<Vector> residual =
|
||||
Difference(internal.Value().force,
|
||||
external.Value().generalized_external_force);
|
||||
if (!residual.HasValue()) {
|
||||
return Result<std::optional<ShellStepCandidate>>::Failure(
|
||||
report_failure(&record, residual.GetStatus(),
|
||||
NewtonIterationStage::kExternalAssembled));
|
||||
}
|
||||
Result<SparseMatrix> tangent = Difference(
|
||||
internal.Value().tangent,
|
||||
external.Value().generalized_external_tangent, dofs.Value());
|
||||
if (!tangent.HasValue()) {
|
||||
return Result<std::optional<ShellStepCandidate>>::Failure(
|
||||
report_failure(&record, tangent.GetStatus(),
|
||||
NewtonIterationStage::kExternalAssembled));
|
||||
}
|
||||
trial.Value().internal_force = std::move(internal.Value().force);
|
||||
trial.Value().external_force =
|
||||
std::move(external.Value().generalized_external_force);
|
||||
trial.Value().residual = std::move(residual.Value());
|
||||
trial.Value().reaction = trial.Value().residual;
|
||||
trial.Value().shell_results = std::move(internal.Value().shell_rows);
|
||||
trial.Value().physical_strain_energy =
|
||||
internal.Value().physical_strain_energy;
|
||||
Result<std::array<double, 6>> global_equilibrium =
|
||||
GlobalEquilibrium(domain, dofs.Value(), trial.Value());
|
||||
if (!global_equilibrium.HasValue()) {
|
||||
return Result<std::optional<ShellStepCandidate>>::Failure(
|
||||
report_failure(&record, global_equilibrium.GetStatus(),
|
||||
NewtonIterationStage::kExternalAssembled));
|
||||
}
|
||||
trial.Value().global_equilibrium = global_equilibrium.Value();
|
||||
|
||||
Result<PartitionedStiffness> partition =
|
||||
constraints.Partition(tangent.Value(), dofs.Value());
|
||||
if (!partition.HasValue()) {
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
||||
log, &record, partition.GetStatus(),
|
||||
NewtonIterationStage::kExternalAssembled));
|
||||
}
|
||||
const Status factorization = tangent_solver_.FactorizeFresh(
|
||||
partition.Value().k_ff);
|
||||
if (!factorization.IsOk()) {
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
||||
log, &record, factorization,
|
||||
NewtonIterationStage::kSolvedCorrection));
|
||||
}
|
||||
Result<PartitionedStiffness> partition =
|
||||
constraints.Partition(tangent.Value(), dofs.Value());
|
||||
if (!partition.HasValue()) {
|
||||
return Result<std::optional<ShellStepCandidate>>::Failure(
|
||||
report_failure(&record, partition.GetStatus(),
|
||||
NewtonIterationStage::kExternalAssembled));
|
||||
}
|
||||
const Status factorization =
|
||||
tangent_solver.FactorizeFresh(partition.Value().k_ff);
|
||||
if (!factorization.IsOk()) {
|
||||
return Result<std::optional<ShellStepCandidate>>::Failure(
|
||||
report_failure(&record, factorization,
|
||||
NewtonIterationStage::kSolvedCorrection));
|
||||
}
|
||||
|
||||
Vector correction_rhs = constraints.GatherFree(trial.Value().residual,
|
||||
dofs.Value());
|
||||
for (std::size_t index = 0U; index < correction_rhs.Size(); ++index) {
|
||||
correction_rhs[index] = -correction_rhs[index];
|
||||
}
|
||||
Vector correction{dofs.Value().FreeDofCount()};
|
||||
const Status solve =
|
||||
tangent_solver_.SolveCorrection(correction_rhs, correction);
|
||||
if (!solve.IsOk()) {
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
||||
log, &record, solve, NewtonIterationStage::kSolvedCorrection));
|
||||
}
|
||||
record.last_completed_stage = NewtonIterationStage::kSolvedCorrection;
|
||||
Vector correction_rhs =
|
||||
constraints.GatherFree(trial.Value().residual, dofs.Value());
|
||||
for (std::size_t index = 0U; index < correction_rhs.Size(); ++index) {
|
||||
correction_rhs[index] = -correction_rhs[index];
|
||||
}
|
||||
Vector correction{dofs.Value().FreeDofCount()};
|
||||
const Status solve =
|
||||
tangent_solver.SolveCorrection(correction_rhs, correction);
|
||||
if (!solve.IsOk()) {
|
||||
return Result<std::optional<ShellStepCandidate>>::Failure(
|
||||
report_failure(&record, solve,
|
||||
NewtonIterationStage::kSolvedCorrection));
|
||||
}
|
||||
record.last_completed_stage = NewtonIterationStage::kSolvedCorrection;
|
||||
|
||||
const NewtonConvergenceResult convergence = EvaluateNewtonConvergence({
|
||||
{"residual_force", SelectFreeComponents(trial.Value().residual,
|
||||
dofs.Value(), false),
|
||||
SelectFreeComponents(trial.Value().internal_force, dofs.Value(),
|
||||
false),
|
||||
SelectFreeComponents(trial.Value().external_force, dofs.Value(),
|
||||
false)},
|
||||
{"residual_moment", SelectFreeComponents(trial.Value().residual,
|
||||
dofs.Value(), true),
|
||||
SelectFreeComponents(trial.Value().internal_force, dofs.Value(),
|
||||
true),
|
||||
SelectFreeComponents(trial.Value().external_force, dofs.Value(),
|
||||
true)},
|
||||
{"correction_translation",
|
||||
SelectCorrectionComponents(correction, dofs.Value(), false),
|
||||
CurrentIncrementComponents(trial.Value().generalized_coordinates,
|
||||
increment_start, dofs.Value(), false)},
|
||||
{"correction_rotation",
|
||||
SelectCorrectionComponents(correction, dofs.Value(), true),
|
||||
CurrentIncrementComponents(trial.Value().generalized_coordinates,
|
||||
increment_start, dofs.Value(), true)}});
|
||||
record.force_residual = convergence.force_residual;
|
||||
record.moment_residual = convergence.moment_residual;
|
||||
record.translation_correction = convergence.translation_correction;
|
||||
record.rotation_correction = convergence.rotation_correction;
|
||||
record.combined_passed = convergence.combined_passed;
|
||||
record.last_completed_stage = NewtonIterationStage::kConvergenceEvaluated;
|
||||
const NewtonConvergenceResult convergence = EvaluateNewtonConvergence(
|
||||
{{"residual_force",
|
||||
SelectFreeComponents(trial.Value().residual, dofs.Value(),
|
||||
false),
|
||||
SelectFreeComponents(trial.Value().internal_force, dofs.Value(),
|
||||
false),
|
||||
SelectFreeComponents(trial.Value().external_force, dofs.Value(),
|
||||
false)},
|
||||
{"residual_moment",
|
||||
SelectFreeComponents(trial.Value().residual, dofs.Value(),
|
||||
true),
|
||||
SelectFreeComponents(trial.Value().internal_force, dofs.Value(),
|
||||
true),
|
||||
SelectFreeComponents(trial.Value().external_force, dofs.Value(),
|
||||
true)},
|
||||
{"correction_translation",
|
||||
SelectCorrectionComponents(correction, dofs.Value(), false),
|
||||
CurrentIncrementComponents(
|
||||
trial.Value().generalized_coordinates, increment_start,
|
||||
dofs.Value(), false)},
|
||||
{"correction_rotation",
|
||||
SelectCorrectionComponents(correction, dofs.Value(), true),
|
||||
CurrentIncrementComponents(
|
||||
trial.Value().generalized_coordinates, increment_start,
|
||||
dofs.Value(), true)}});
|
||||
record.force_residual = convergence.force_residual;
|
||||
record.moment_residual = convergence.moment_residual;
|
||||
record.translation_correction = convergence.translation_correction;
|
||||
record.rotation_correction = convergence.rotation_correction;
|
||||
record.combined_passed = convergence.combined_passed;
|
||||
record.last_completed_stage =
|
||||
NewtonIterationStage::kConvergenceEvaluated;
|
||||
|
||||
if (convergence.combined_passed) {
|
||||
const Status committed = state.Value().CommitAcceptedFrame(
|
||||
AcceptedFrame(state.Value(), trial.Value(), increment, iteration));
|
||||
if (!committed.IsOk()) {
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
||||
log, &record, committed, NewtonIterationStage::kAccepted));
|
||||
}
|
||||
record.last_completed_stage = NewtonIterationStage::kAccepted;
|
||||
record.transition = "accepted";
|
||||
record.outcome = "accepted";
|
||||
const Status log_status = WriteAndFlush(log, record);
|
||||
if (!log_status.IsOk()) {
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(log_status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (convergence.combined_passed) {
|
||||
return Result<std::optional<ShellStepCandidate>>::Success(
|
||||
ShellStepCandidate{AcceptedFrame(next_frame_index,
|
||||
trial.Value(), increment,
|
||||
iteration),
|
||||
record});
|
||||
}
|
||||
|
||||
if (iteration == kMaximumNewtonIterations) {
|
||||
const Status failure = ProcedureFailure(
|
||||
FailureCategory::kSolver, "nonlinear-nonconvergence",
|
||||
step.Location(), "STATIC", step.Name(),
|
||||
"The nonlinear procedure did not converge within 100 iterations.");
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
||||
log, &record, failure,
|
||||
NewtonIterationStage::kConvergenceEvaluated));
|
||||
}
|
||||
if (iteration == kMaximumNewtonIterations) {
|
||||
const Status failure = ProcedureFailure(
|
||||
FailureCategory::kSolver, "nonlinear-nonconvergence",
|
||||
step.Location(), "STATIC", step.Name(),
|
||||
"The nonlinear procedure did not converge within 100 "
|
||||
"iterations.");
|
||||
return Result<std::optional<ShellStepCandidate>>::Failure(
|
||||
report_failure(&record, failure,
|
||||
NewtonIterationStage::kConvergenceEvaluated));
|
||||
}
|
||||
|
||||
for (std::size_t equation = 0U; equation < dofs.Value().FreeDofs().size();
|
||||
++equation) {
|
||||
const std::size_t full_dof = dofs.Value().FreeDofs()[equation];
|
||||
trial.Value().generalized_coordinates[full_dof] += correction[equation];
|
||||
if (!std::isfinite(trial.Value().generalized_coordinates[full_dof])) {
|
||||
const Status failure = ProcedureFailure(
|
||||
FailureCategory::kSolver, "nonfinite-newton-data", {},
|
||||
"NONLINEAR_STATIC_PROCEDURE", std::to_string(full_dof),
|
||||
"A nonlinear free-coordinate correction is nonfinite.");
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
||||
log, &record, failure, NewtonIterationStage::kSolvedCorrection));
|
||||
}
|
||||
}
|
||||
record.transition = "continue";
|
||||
record.outcome = "not-converged";
|
||||
const Status log_status = WriteAndFlush(log, record);
|
||||
if (!log_status.IsOk()) {
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(log_status);
|
||||
}
|
||||
for (std::size_t equation = 0U;
|
||||
equation < dofs.Value().FreeDofs().size(); ++equation) {
|
||||
const std::size_t full_dof = dofs.Value().FreeDofs()[equation];
|
||||
trial.Value().generalized_coordinates[full_dof] +=
|
||||
correction[equation];
|
||||
if (!std::isfinite(
|
||||
trial.Value().generalized_coordinates[full_dof])) {
|
||||
const Status failure = ProcedureFailure(
|
||||
FailureCategory::kSolver, "nonfinite-newton-data", {},
|
||||
"NONLINEAR_STATIC_PROCEDURE", std::to_string(full_dof),
|
||||
"A nonlinear free-coordinate correction is nonfinite.");
|
||||
return Result<std::optional<ShellStepCandidate>>::Failure(
|
||||
report_failure(&record, failure,
|
||||
NewtonIterationStage::kSolvedCorrection));
|
||||
}
|
||||
}
|
||||
record.transition = "continue";
|
||||
record.outcome = "not-converged";
|
||||
const Status log_status = write_iteration(record);
|
||||
if (!log_status.IsOk()) {
|
||||
return Result<std::optional<ShellStepCandidate>>::Failure(
|
||||
log_status);
|
||||
}
|
||||
return Result<std::optional<ShellStepCandidate>>::Success(
|
||||
std::nullopt);
|
||||
},
|
||||
kMaximumNewtonIterations);
|
||||
const Status solved = iteration_step.Solve(algorithm);
|
||||
if (!solved.IsOk())
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(solved);
|
||||
auto candidate = iteration_step.TakeCandidate();
|
||||
auto& record = candidate.record;
|
||||
const Status committed = state.Value().CommitAcceptedFrame(candidate.frame);
|
||||
if (!committed.IsOk()) {
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(RecordFailure(
|
||||
log, &record, committed, NewtonIterationStage::kAccepted));
|
||||
}
|
||||
record.last_completed_stage = NewtonIterationStage::kAccepted;
|
||||
record.transition = "accepted";
|
||||
record.outcome = "accepted";
|
||||
const Status log_status = WriteAndFlush(log, record);
|
||||
if (!log_status.IsOk())
|
||||
return Result<NonlinearStaticExecutionResult>::Failure(log_status);
|
||||
}
|
||||
|
||||
std::vector<Diagnostic> diagnostics = domain.Warnings();
|
||||
@@ -983,4 +1013,4 @@ NonlinearStaticProcedureExecutor::Execute(
|
||||
{std::move(state.Value()), std::move(diagnostics)});
|
||||
}
|
||||
|
||||
} // namespace fesa
|
||||
} // namespace fesa
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "fesa/analysis/prepared_analysis.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "analysis_procedures.h"
|
||||
|
||||
#include "fesa/analysis/analysis_model.h"
|
||||
#include "fesa/analysis/hht_dynamic_procedure.h"
|
||||
#include "fesa/analysis/linear_frequency_procedure.h"
|
||||
@@ -9,15 +12,13 @@
|
||||
#include "fesa/analysis/newton_iteration_log.h"
|
||||
#include "fesa/analysis/nonlinear_static_procedure.h"
|
||||
#include "fesa/analysis/solid_frequency_procedure.h"
|
||||
#include "fesa/analysis/solid_hht_dynamic_procedure.h"
|
||||
#include "fesa/analysis/solid_static_procedure.h"
|
||||
#include "fesa/analysis/transient_heat_procedure.h"
|
||||
#include "fesa/app/output_paths.h"
|
||||
#include "fesa/io/abaqus/domain_mapper.h"
|
||||
#include "fesa/io/abaqus/input_reader.h"
|
||||
#include "fesa/results/dynamic_results_writer.h"
|
||||
#include "fesa/results/eigen_results_writer.h"
|
||||
#include "fesa/results/nonlinear_results_writer.h"
|
||||
#include "fesa/results/results_writer.h"
|
||||
#include "fesa/results/solid_results_writer.h"
|
||||
#include "fesa/results/thermal_results_writer.h"
|
||||
#include "fesa/results/prepared_results_writer.h"
|
||||
|
||||
namespace fesa {
|
||||
|
||||
@@ -39,6 +40,185 @@ Result<PreparedAnalysisInput> PrepareAnalysisInput(
|
||||
return Result<PreparedAnalysisInput>::Success(std::move(prepared));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
enum class ProcedureKind {
|
||||
kLinearStatic,
|
||||
kNonlinearStatic,
|
||||
kLinearFrequency,
|
||||
kHhtDynamic,
|
||||
kSolidStatic,
|
||||
kSolidFrequency,
|
||||
kSolidHhtDynamic,
|
||||
kTransientHeat
|
||||
};
|
||||
|
||||
// Keep procedure precedence and step-count diagnostics common to both APIs.
|
||||
Result<ProcedureKind> SelectProcedure(const Domain& domain) {
|
||||
if (domain.ThermalStep() != nullptr)
|
||||
return Result<ProcedureKind>::Success(ProcedureKind::kTransientHeat);
|
||||
if (domain.Steps().Size() != 1U) {
|
||||
return Result<ProcedureKind>::Failure(Status::Failure(
|
||||
FailureCategory::kInput,
|
||||
{{Severity::kError,
|
||||
"invalid-analysis-step-count",
|
||||
{},
|
||||
"*STEP",
|
||||
{},
|
||||
"Prepared analysis requires exactly one analysis step."}}));
|
||||
}
|
||||
if (domain.IsSolid()) {
|
||||
if (domain.Dynamic() != nullptr)
|
||||
return Result<ProcedureKind>::Success(ProcedureKind::kSolidHhtDynamic);
|
||||
if (domain.Steps()[0U].IsFrequency())
|
||||
return Result<ProcedureKind>::Success(ProcedureKind::kSolidFrequency);
|
||||
return Result<ProcedureKind>::Success(ProcedureKind::kSolidStatic);
|
||||
}
|
||||
if (domain.Dynamic() != nullptr)
|
||||
return Result<ProcedureKind>::Success(ProcedureKind::kHhtDynamic);
|
||||
if (domain.Steps()[0U].IsFrequency())
|
||||
return Result<ProcedureKind>::Success(ProcedureKind::kLinearFrequency);
|
||||
if (domain.Steps()[0U].IsNonlinearStatic())
|
||||
return Result<ProcedureKind>::Success(ProcedureKind::kNonlinearStatic);
|
||||
return Result<ProcedureKind>::Success(ProcedureKind::kLinearStatic);
|
||||
}
|
||||
|
||||
template <typename Payload>
|
||||
Result<PreparedAnalysisOutcome> Completed(Result<Payload> result) {
|
||||
if (!result.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(result.GetStatus());
|
||||
return Result<PreparedAnalysisOutcome>::Success(
|
||||
PreparedAnalysisOutcome{std::move(result.Value())});
|
||||
}
|
||||
|
||||
Result<PreparedAnalysisOutcome>
|
||||
MissingExecutor(const char* code, const char* keyword, const char* message) {
|
||||
return Result<PreparedAnalysisOutcome>::Failure(
|
||||
Status::Failure(FailureCategory::kSolver,
|
||||
{{Severity::kError, code, {}, keyword, {}, message}}));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Result<PreparedAnalysisOutcome>
|
||||
LinearStaticProcedure::Execute(const Domain& domain) {
|
||||
return Completed(executor_.Execute(domain));
|
||||
}
|
||||
|
||||
Result<PreparedAnalysisOutcome>
|
||||
NonlinearStaticProcedure::Execute(const Domain& domain) {
|
||||
return Completed(executor_.Execute(domain, log_));
|
||||
}
|
||||
|
||||
Result<PreparedAnalysisOutcome>
|
||||
LinearFrequencyProcedure::Execute(const Domain& domain) {
|
||||
return Completed(executor_.Execute(domain));
|
||||
}
|
||||
|
||||
Result<PreparedAnalysisOutcome>
|
||||
HhtDynamicProcedure::Execute(const Domain& domain) {
|
||||
return Completed(executor_.Execute(domain, log_));
|
||||
}
|
||||
|
||||
Result<PreparedAnalysisOutcome>
|
||||
SolidStaticProcedure::Execute(const Domain& domain) {
|
||||
SolidStaticState state;
|
||||
const Status execution = executor_.ExecuteInto(domain, state);
|
||||
for (const auto& record : state.IterationRecords()) {
|
||||
const Status written = log_.Write(record, domain.Steps()[0U].Name());
|
||||
if (!written.IsOk())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(written);
|
||||
}
|
||||
if (!execution.IsOk())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(execution);
|
||||
return Result<PreparedAnalysisOutcome>::Success(PreparedAnalysisOutcome{
|
||||
SolidStaticExecutionResult{std::move(state), domain.Warnings()}});
|
||||
}
|
||||
|
||||
Result<PreparedAnalysisOutcome>
|
||||
SolidFrequencyProcedure::Execute(const Domain& domain) {
|
||||
return Completed(executor_.Execute(domain));
|
||||
}
|
||||
|
||||
Result<PreparedAnalysisOutcome>
|
||||
SolidHhtDynamicProcedure::Execute(const Domain& domain) {
|
||||
auto model = AnalysisModel::Create(domain);
|
||||
if (!model.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(model.GetStatus());
|
||||
auto dofs = DofManager::Create(model.Value());
|
||||
if (!dofs.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(dofs.GetStatus());
|
||||
auto state = SolidDynamicAnalysisState::Create(domain, dofs.Value());
|
||||
if (!state.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(state.GetStatus());
|
||||
const Status execution = executor_.ExecuteInto(domain, state.Value());
|
||||
for (const auto& record : state.Value().IterationRecords()) {
|
||||
const Status written = log_.Write(record);
|
||||
if (!written.IsOk())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(written);
|
||||
}
|
||||
if (!execution.IsOk())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(execution);
|
||||
return Result<PreparedAnalysisOutcome>::Success(
|
||||
PreparedAnalysisOutcome{SolidDynamicExecutionResult{
|
||||
std::move(dofs.Value()), std::move(state.Value())}});
|
||||
}
|
||||
|
||||
Result<PreparedAnalysisOutcome>
|
||||
TransientHeatProcedure::Execute(const Domain& domain) {
|
||||
return Completed(executor_.Execute(domain));
|
||||
}
|
||||
|
||||
Result<std::unique_ptr<AnalysisProcedure>> CreateAnalysisProcedure(
|
||||
const Domain& domain, const ParallelFor& parallel_for,
|
||||
LinearSolver& linear_solver, TangentSolver& tangent_solver,
|
||||
const EigenSolver& eigen_solver, NewtonIterationLogWriter& log) {
|
||||
const auto kind = SelectProcedure(domain);
|
||||
if (!kind.HasValue())
|
||||
return Result<std::unique_ptr<AnalysisProcedure>>::Failure(
|
||||
kind.GetStatus());
|
||||
std::unique_ptr<AnalysisProcedure> procedure;
|
||||
switch (kind.Value()) {
|
||||
case ProcedureKind::kLinearStatic:
|
||||
procedure =
|
||||
std::make_unique<LinearStaticProcedure>(parallel_for, linear_solver);
|
||||
break;
|
||||
case ProcedureKind::kNonlinearStatic:
|
||||
procedure = std::make_unique<NonlinearStaticProcedure>(parallel_for,
|
||||
tangent_solver, log);
|
||||
break;
|
||||
case ProcedureKind::kLinearFrequency:
|
||||
procedure = std::make_unique<LinearFrequencyProcedure>(eigen_solver);
|
||||
break;
|
||||
case ProcedureKind::kHhtDynamic:
|
||||
procedure = std::make_unique<HhtDynamicProcedure>(
|
||||
parallel_for, linear_solver, tangent_solver, log);
|
||||
break;
|
||||
case ProcedureKind::kSolidStatic:
|
||||
procedure = std::make_unique<SolidStaticProcedure>(
|
||||
parallel_for, linear_solver, tangent_solver, log);
|
||||
break;
|
||||
case ProcedureKind::kSolidFrequency:
|
||||
procedure = std::make_unique<SolidFrequencyProcedure>(eigen_solver);
|
||||
break;
|
||||
case ProcedureKind::kSolidHhtDynamic:
|
||||
procedure = std::make_unique<SolidHhtDynamicProcedure>(
|
||||
parallel_for, linear_solver, tangent_solver, log);
|
||||
break;
|
||||
case ProcedureKind::kTransientHeat:
|
||||
procedure =
|
||||
std::make_unique<TransientHeatProcedure>(parallel_for, linear_solver);
|
||||
break;
|
||||
}
|
||||
return Result<std::unique_ptr<AnalysisProcedure>>::Success(
|
||||
std::move(procedure));
|
||||
}
|
||||
|
||||
Result<PreparedAnalysisOutcome>
|
||||
ExecutePreparedAnalysis(const Domain& domain, AnalysisProcedure& procedure) {
|
||||
return procedure.Execute(domain);
|
||||
}
|
||||
|
||||
Result<PreparedAnalysisOutcome> ExecutePreparedAnalysis(
|
||||
const Domain& domain, LinearStaticProcedureExecutor& linear_executor,
|
||||
NonlinearStaticProcedureExecutor& nonlinear_executor,
|
||||
@@ -49,133 +229,59 @@ Result<PreparedAnalysisOutcome> ExecutePreparedAnalysis(
|
||||
const SolidStaticProcedureExecutor* solid_static_executor,
|
||||
const SolidFrequencyProcedureExecutor* solid_frequency_executor,
|
||||
const SolidHhtDynamicProcedureExecutor* solid_dynamic_executor) {
|
||||
if (domain.ThermalStep() != nullptr) {
|
||||
if (thermal_executor == nullptr) {
|
||||
return Result<PreparedAnalysisOutcome>::Failure(
|
||||
Status::Failure(FailureCategory::kSolver,
|
||||
{{Severity::kError,
|
||||
"missing-thermal-executor",
|
||||
{},
|
||||
"HEAT TRANSFER",
|
||||
{},
|
||||
"Thermal execution adapter is required."}}));
|
||||
}
|
||||
auto thermal = thermal_executor->Execute(domain);
|
||||
if (!thermal.HasValue()) {
|
||||
return Result<PreparedAnalysisOutcome>::Failure(thermal.GetStatus());
|
||||
}
|
||||
return Result<PreparedAnalysisOutcome>::Success(
|
||||
PreparedAnalysisOutcome{std::move(thermal.Value())});
|
||||
}
|
||||
if (domain.Steps().Size() != 1U) {
|
||||
return Result<PreparedAnalysisOutcome>::Failure(Status::Failure(
|
||||
FailureCategory::kInput,
|
||||
{{Severity::kError,
|
||||
"invalid-analysis-step-count",
|
||||
{},
|
||||
"*STEP",
|
||||
{},
|
||||
"Prepared analysis requires exactly one analysis step."}}));
|
||||
}
|
||||
|
||||
if (domain.IsSolid()) {
|
||||
const auto missing = []() {
|
||||
return Result<PreparedAnalysisOutcome>::Failure(Status::Failure(
|
||||
FailureCategory::kSolver,
|
||||
{{Severity::kError,
|
||||
"missing-solid-executor",
|
||||
{},
|
||||
"",
|
||||
{},
|
||||
"The selected Solid execution adapter is required."}}));
|
||||
};
|
||||
if (domain.Dynamic() != nullptr) {
|
||||
if (solid_dynamic_executor == nullptr) return missing();
|
||||
auto model = AnalysisModel::Create(domain);
|
||||
if (!model.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(model.GetStatus());
|
||||
auto dofs = DofManager::Create(model.Value());
|
||||
if (!dofs.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(dofs.GetStatus());
|
||||
auto state = SolidDynamicAnalysisState::Create(domain, dofs.Value());
|
||||
if (!state.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(state.GetStatus());
|
||||
const Status execution =
|
||||
solid_dynamic_executor->ExecuteInto(domain, state.Value());
|
||||
for (const auto& record : state.Value().IterationRecords()) {
|
||||
const Status written = log.Write(record);
|
||||
if (!written.IsOk())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(written);
|
||||
}
|
||||
if (!execution.IsOk())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(execution);
|
||||
return Result<PreparedAnalysisOutcome>::Success(
|
||||
PreparedAnalysisOutcome{SolidDynamicExecutionResult{
|
||||
std::move(dofs.Value()), std::move(state.Value())}});
|
||||
}
|
||||
if (domain.Steps()[0U].IsFrequency()) {
|
||||
if (solid_frequency_executor == nullptr) return missing();
|
||||
auto result = solid_frequency_executor->Execute(domain);
|
||||
if (!result.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(result.GetStatus());
|
||||
return Result<PreparedAnalysisOutcome>::Success(
|
||||
PreparedAnalysisOutcome{std::move(result.Value())});
|
||||
}
|
||||
if (solid_static_executor == nullptr) return missing();
|
||||
SolidStaticState state;
|
||||
const Status execution = solid_static_executor->ExecuteInto(domain, state);
|
||||
for (const auto& record : state.IterationRecords()) {
|
||||
const Status written = log.Write(record, domain.Steps()[0U].Name());
|
||||
if (!written.IsOk())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(written);
|
||||
}
|
||||
if (!execution.IsOk())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(execution);
|
||||
return Result<PreparedAnalysisOutcome>::Success(PreparedAnalysisOutcome{
|
||||
SolidStaticExecutionResult{std::move(state), domain.Warnings()}});
|
||||
}
|
||||
|
||||
if (domain.Dynamic() != nullptr) {
|
||||
const auto kind = SelectProcedure(domain);
|
||||
if (!kind.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(kind.GetStatus());
|
||||
std::unique_ptr<AnalysisProcedure> procedure;
|
||||
switch (kind.Value()) {
|
||||
case ProcedureKind::kLinearStatic:
|
||||
procedure = std::make_unique<LinearStaticProcedure>(linear_executor);
|
||||
break;
|
||||
case ProcedureKind::kNonlinearStatic:
|
||||
procedure =
|
||||
std::make_unique<NonlinearStaticProcedure>(nonlinear_executor, log);
|
||||
break;
|
||||
case ProcedureKind::kLinearFrequency:
|
||||
procedure = std::make_unique<LinearFrequencyProcedure>(frequency_executor);
|
||||
break;
|
||||
case ProcedureKind::kHhtDynamic:
|
||||
if (dynamic_executor == nullptr)
|
||||
return Result<PreparedAnalysisOutcome>::Failure(
|
||||
Status::Failure(FailureCategory::kSolver,
|
||||
{{Severity::kError,
|
||||
"missing-dynamic-executor",
|
||||
{},
|
||||
"DYNAMIC",
|
||||
{},
|
||||
"Dynamic execution adapter is required."}}));
|
||||
auto dynamic = dynamic_executor->Execute(domain, log);
|
||||
if (!dynamic.HasValue())
|
||||
return Result<PreparedAnalysisOutcome>::Failure(dynamic.GetStatus());
|
||||
return Result<PreparedAnalysisOutcome>::Success(
|
||||
PreparedAnalysisOutcome{std::move(dynamic.Value())});
|
||||
return MissingExecutor("missing-dynamic-executor", "DYNAMIC",
|
||||
"Dynamic execution adapter is required.");
|
||||
procedure = std::make_unique<HhtDynamicProcedure>(*dynamic_executor, log);
|
||||
break;
|
||||
case ProcedureKind::kSolidStatic:
|
||||
if (solid_static_executor == nullptr)
|
||||
return MissingExecutor(
|
||||
"missing-solid-executor", "",
|
||||
"The selected Solid execution adapter is required.");
|
||||
procedure =
|
||||
std::make_unique<SolidStaticProcedure>(*solid_static_executor, log);
|
||||
break;
|
||||
case ProcedureKind::kSolidFrequency:
|
||||
if (solid_frequency_executor == nullptr)
|
||||
return MissingExecutor(
|
||||
"missing-solid-executor", "",
|
||||
"The selected Solid execution adapter is required.");
|
||||
procedure =
|
||||
std::make_unique<SolidFrequencyProcedure>(*solid_frequency_executor);
|
||||
break;
|
||||
case ProcedureKind::kSolidHhtDynamic:
|
||||
if (solid_dynamic_executor == nullptr)
|
||||
return MissingExecutor(
|
||||
"missing-solid-executor", "",
|
||||
"The selected Solid execution adapter is required.");
|
||||
procedure = std::make_unique<SolidHhtDynamicProcedure>(
|
||||
*solid_dynamic_executor, log);
|
||||
break;
|
||||
case ProcedureKind::kTransientHeat:
|
||||
if (thermal_executor == nullptr)
|
||||
return MissingExecutor("missing-thermal-executor", "HEAT TRANSFER",
|
||||
"Thermal execution adapter is required.");
|
||||
procedure = std::make_unique<TransientHeatProcedure>(*thermal_executor);
|
||||
break;
|
||||
}
|
||||
if (domain.Steps()[0U].IsFrequency()) {
|
||||
auto eigen = frequency_executor.Execute(domain);
|
||||
if (!eigen.HasValue()) {
|
||||
return Result<PreparedAnalysisOutcome>::Failure(eigen.GetStatus());
|
||||
}
|
||||
return Result<PreparedAnalysisOutcome>::Success(
|
||||
PreparedAnalysisOutcome{std::move(eigen.Value())});
|
||||
}
|
||||
|
||||
if (domain.Steps()[0U].IsNonlinearStatic()) {
|
||||
Result<NonlinearStaticExecutionResult> nonlinear =
|
||||
nonlinear_executor.Execute(domain, log);
|
||||
if (!nonlinear.HasValue()) {
|
||||
return Result<PreparedAnalysisOutcome>::Failure(nonlinear.GetStatus());
|
||||
}
|
||||
return Result<PreparedAnalysisOutcome>::Success(
|
||||
PreparedAnalysisOutcome{std::move(nonlinear.Value())});
|
||||
}
|
||||
|
||||
Result<LinearStaticExecutionResult> linear = linear_executor.Execute(domain);
|
||||
if (!linear.HasValue()) {
|
||||
return Result<PreparedAnalysisOutcome>::Failure(linear.GetStatus());
|
||||
}
|
||||
return Result<PreparedAnalysisOutcome>::Success(
|
||||
PreparedAnalysisOutcome{std::move(linear.Value())});
|
||||
return ExecutePreparedAnalysis(domain, *procedure);
|
||||
}
|
||||
|
||||
Status FinalizePreparedAnalysisResult(
|
||||
@@ -185,68 +291,10 @@ Status FinalizePreparedAnalysisResult(
|
||||
NonlinearResultsWriter& nonlinear_writer, EigenResultsWriter& eigen_writer,
|
||||
DynamicResultsWriter* dynamic_writer, ThermalResultsWriter* thermal_writer,
|
||||
SolidResultsWriter* solid_writer) {
|
||||
if (std::holds_alternative<SolidStaticExecutionResult>(outcome) ||
|
||||
std::holds_alternative<SolidFrequencyState>(outcome) ||
|
||||
std::holds_alternative<SolidDynamicExecutionResult>(outcome)) {
|
||||
if (solid_writer == nullptr)
|
||||
return Status::Failure(FailureCategory::kOutput,
|
||||
{{Severity::kError,
|
||||
"missing-solid-writer",
|
||||
{},
|
||||
"",
|
||||
{},
|
||||
"Solid result writer is required."}});
|
||||
if (const auto* result = std::get_if<SolidStaticExecutionResult>(&outcome))
|
||||
return solid_writer->Write(paths.results_path, domain, result->state,
|
||||
diagnostics);
|
||||
if (const auto* result = std::get_if<SolidFrequencyState>(&outcome))
|
||||
return solid_writer->Write(paths.results_path, domain, *result,
|
||||
diagnostics);
|
||||
return solid_writer->Write(
|
||||
paths.results_path, domain,
|
||||
std::get<SolidDynamicExecutionResult>(outcome).state, diagnostics);
|
||||
}
|
||||
if (std::holds_alternative<ThermalExecutionResult>(outcome)) {
|
||||
if (thermal_writer == nullptr) {
|
||||
return Status::Failure(FailureCategory::kOutput,
|
||||
{{Severity::kError,
|
||||
"missing-thermal-writer",
|
||||
{},
|
||||
"HEAT TRANSFER",
|
||||
{},
|
||||
"Thermal result writer is required."}});
|
||||
}
|
||||
return thermal_writer->Write(
|
||||
paths.results_path, domain,
|
||||
std::get<ThermalExecutionResult>(outcome).state, diagnostics);
|
||||
}
|
||||
if (std::holds_alternative<DynamicExecutionResult>(outcome)) {
|
||||
if (dynamic_writer == nullptr)
|
||||
return Status::Failure(FailureCategory::kOutput,
|
||||
{{Severity::kError,
|
||||
"missing-dynamic-writer",
|
||||
{},
|
||||
"DYNAMIC",
|
||||
{},
|
||||
"Dynamic result writer is required."}});
|
||||
return dynamic_writer->Write(
|
||||
paths.results_path, domain,
|
||||
std::get<DynamicExecutionResult>(outcome).state, diagnostics);
|
||||
}
|
||||
if (std::holds_alternative<EigenAnalysisState>(outcome)) {
|
||||
return eigen_writer.Write(paths.results_path, domain,
|
||||
std::get<EigenAnalysisState>(outcome),
|
||||
diagnostics);
|
||||
}
|
||||
if (std::holds_alternative<LinearStaticExecutionResult>(outcome)) {
|
||||
return linear_writer.Write(
|
||||
paths.results_path, domain,
|
||||
std::get<LinearStaticExecutionResult>(outcome).state, diagnostics);
|
||||
}
|
||||
|
||||
return nonlinear_writer.Write(
|
||||
paths.results_path, domain,
|
||||
std::get<NonlinearStaticExecutionResult>(outcome).state, diagnostics);
|
||||
return WritePreparedAnalysisResult(
|
||||
paths.results_path, domain, outcome, diagnostics, linear_writer,
|
||||
nonlinear_writer, eigen_writer, dynamic_writer, thermal_writer,
|
||||
solid_writer);
|
||||
}
|
||||
|
||||
} // namespace fesa
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef FESA_ANALYSIS_SHELL_STATE_INVENTORY_H_
|
||||
#define FESA_ANALYSIS_SHELL_STATE_INVENTORY_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/elements/element_definition.h"
|
||||
#include "fesa/model/domain.h"
|
||||
|
||||
namespace fesa::analysis_internal {
|
||||
|
||||
/// @brief Collects unified MITC4 element indices in stable Domain order.
|
||||
inline std::vector<EntityIndex> ShellElementOrder(const Domain& domain) {
|
||||
std::vector<EntityIndex> order;
|
||||
for (std::size_t index = 0U; index < domain.Elements().Size(); ++index) {
|
||||
if (domain.Elements()[index].Kind() == ElementDefinitionKind::kMitc4Shell) {
|
||||
order.push_back(static_cast<EntityIndex>(index));
|
||||
}
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
} // namespace fesa::analysis_internal
|
||||
|
||||
#endif // FESA_ANALYSIS_SHELL_STATE_INVENTORY_H_
|
||||
@@ -1,13 +1,15 @@
|
||||
#include "fesa/analysis/solid_dynamic_analysis_state.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "fesa/core/source_identity.h"
|
||||
#include "fesa/elements/element_factory.h"
|
||||
#include "fesa/elements/hex8_element.h"
|
||||
#include "fesa/model/domain.h"
|
||||
#include "math/high_low_arithmetic.h"
|
||||
#include "math/finite_values.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
@@ -24,8 +26,7 @@ Status Failure(const std::string& message) {
|
||||
/// @brief Checks a physical record without replacing nonfinite quantities.
|
||||
template <std::size_t N>
|
||||
bool Finite(const std::array<double, N>& values) {
|
||||
return std::all_of(values.begin(), values.end(),
|
||||
[](double x) { return std::isfinite(x); });
|
||||
return math_internal::IsFinite(values);
|
||||
}
|
||||
bool ValidPair(double high, double low) {
|
||||
const math_internal::HighLow value(high, low);
|
||||
@@ -44,12 +45,6 @@ bool ValidPair(const std::array<double, N>& high,
|
||||
if (!ValidPair(high[i], low[i])) return false;
|
||||
return true;
|
||||
}
|
||||
/// @brief Preserves canonical and raw source identity independently of indices.
|
||||
bool SameId(const SourceEntityId& a, const SourceEntityId& b) {
|
||||
return a.instance_name == b.instance_name &&
|
||||
a.source_label == b.source_label &&
|
||||
a.source_label_text == b.source_label_text;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Result<SolidDynamicAnalysisState> SolidDynamicAnalysisState::Create(
|
||||
@@ -83,7 +78,7 @@ Result<SolidDynamicAnalysisState> SolidDynamicAnalysisState::Create(
|
||||
auto runtime = factory.Create(domain.Elements()[e], domain);
|
||||
if (!runtime.HasValue())
|
||||
return Result<SolidDynamicAnalysisState>::Failure(runtime.GetStatus());
|
||||
const auto* solid = dynamic_cast<const Hex8Element*>(runtime.Value().get());
|
||||
auto solid = factory.BindSolidTrial(*runtime.Value());
|
||||
if (solid == nullptr)
|
||||
return Result<SolidDynamicAnalysisState>::Failure(Failure(
|
||||
"Dynamic Solid inventory contains a different element family."));
|
||||
@@ -117,7 +112,7 @@ Status SolidDynamicAnalysisState::CommitAcceptedFrame(
|
||||
}
|
||||
for (std::size_t n = 0; n < source_ids_.size(); ++n)
|
||||
if (f.nodal_internal_order[n] != n ||
|
||||
!SameId(f.nodal_source_ids[n], source_ids_[n]))
|
||||
!SourceIdentityEquals(f.nodal_source_ids[n], source_ids_[n]))
|
||||
return Failure("Solid nodal source identity differs from its inventory.");
|
||||
const std::array<const Vector*, 10> high{
|
||||
&f.displacement, &f.velocity,
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "fesa/elements/hex8_element.h"
|
||||
#include "fesa/fem/dof_manager.h"
|
||||
#include "fesa/math/sparse_matrix.h"
|
||||
#include "fesa/solvers/eigen/mkl_feast_eigen_solver.h"
|
||||
#include "fesa/math/vector.h"
|
||||
#include "fesa/model/domain.h"
|
||||
|
||||
@@ -50,7 +51,8 @@ double Ratio(double numerator, double denominator) {
|
||||
return numerator / denominator;
|
||||
}
|
||||
/// @brief Builds an immutable candidate; all returns precede public commit.
|
||||
Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain) {
|
||||
Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain,
|
||||
const EigenSolver& solver) {
|
||||
const auto* frequency = domain.Frequency();
|
||||
if (!domain.IsSolid() || frequency == nullptr ||
|
||||
frequency->requested_mode_count <= 0)
|
||||
@@ -76,6 +78,7 @@ Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain) {
|
||||
}
|
||||
ElementFactory factory;
|
||||
std::vector<std::unique_ptr<Element>> owned;
|
||||
std::vector<std::unique_ptr<SolidTrialElement>> trials;
|
||||
ElementView view;
|
||||
for (const auto index : model.Value().ActiveElements()) {
|
||||
const auto& definition = domain.Elements()[index];
|
||||
@@ -87,11 +90,13 @@ Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain) {
|
||||
auto element = factory.Create(definition, domain);
|
||||
if (!element.HasValue())
|
||||
return Result<SolidFrequencyState>::Failure(element.GetStatus());
|
||||
if (dynamic_cast<const Hex8Element*>(element.Value().get()) == nullptr)
|
||||
auto trial = factory.BindSolidTrial(*element.Value());
|
||||
if (trial == nullptr)
|
||||
return Result<SolidFrequencyState>::Failure(
|
||||
Failure("unsupported-frequency-element", "Only C3D8 is supported."));
|
||||
view.emplace_back(*element.Value());
|
||||
owned.push_back(std::move(element.Value()));
|
||||
trials.push_back(std::move(trial));
|
||||
}
|
||||
DofManager dofs;
|
||||
const Status mapping = dofs.Build(model.Value(), view);
|
||||
@@ -121,7 +126,7 @@ Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain) {
|
||||
std::vector<std::size_t> additions_by_dof(dofs.FullDofCount(), 0U);
|
||||
constexpr double eps = std::numeric_limits<double>::epsilon();
|
||||
for (std::size_t e = 0; e < owned.size(); ++e) {
|
||||
const auto& hex = static_cast<const Hex8Element&>(*owned[e]);
|
||||
const auto& hex = *trials[e];
|
||||
auto mass = hex.ComputeMass();
|
||||
if (!mass.HasValue())
|
||||
return Result<SolidFrequencyState>::Failure(mass.GetStatus());
|
||||
@@ -220,7 +225,7 @@ Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain) {
|
||||
return Result<SolidFrequencyState>::Failure(
|
||||
Failure("invalid-solid-eigen-scale",
|
||||
"A positive finite classification scale is required."));
|
||||
auto backend = MklFeastEigenSolver{}.SolveGeneralized(k, m, requested,
|
||||
auto backend = solver.SolveGeneralized(k, m, requested,
|
||||
mass_lower, scale);
|
||||
if (!backend.HasValue())
|
||||
return Result<SolidFrequencyState>::Failure(backend.GetStatus());
|
||||
@@ -269,11 +274,11 @@ Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain) {
|
||||
"Normalized modal vector is not finite."));
|
||||
}
|
||||
mv = m.Multiply(vector);
|
||||
const Vector kv = k.Multiply(vector);
|
||||
Vector kv = k.Multiply(vector);
|
||||
const double lambda = state.backend.eigenvalues[c];
|
||||
const double modal_mass = vector.Dot(mv);
|
||||
const double energy = vector.Dot(kv);
|
||||
Vector residual = kv;
|
||||
Vector residual = std::move(kv);
|
||||
residual.Axpy(-lambda, mv);
|
||||
const double norm_phi = vector.Norm();
|
||||
const double residual_norm = residual.Norm();
|
||||
@@ -355,7 +360,8 @@ Result<SolidFrequencyState> ExecuteCandidate(const Domain& domain) {
|
||||
Result<SolidFrequencyState> SolidFrequencyProcedureExecutor::Execute(
|
||||
const Domain& domain) const {
|
||||
try {
|
||||
auto result = ExecuteCandidate(domain);
|
||||
const MklFeastEigenSolver default_solver;
|
||||
auto result = ExecuteCandidate(domain, solver_ ? *solver_ : default_solver);
|
||||
if (result.HasValue()) return result;
|
||||
auto diagnostics = result.GetStatus().Diagnostics();
|
||||
for (auto& diagnostic : diagnostics)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef FESA_ANALYSIS_SOLID_HHT_CORE_H_
|
||||
#define FESA_ANALYSIS_SOLID_HHT_CORE_H_
|
||||
#include "fesa/core/status.h"
|
||||
namespace fesa {
|
||||
namespace detail { class SolidHhtIntegrator; }
|
||||
class Domain;
|
||||
class SolidDynamicAnalysisState;
|
||||
class ParallelFor;
|
||||
class LinearSolver;
|
||||
class TangentSolver;
|
||||
class EquilibriumAlgorithm;
|
||||
/// @brief Runs the complete production core with borrowed iteration policy.
|
||||
/// @note Adapters outlive this synchronous call; history remains state-owned.
|
||||
Status ExecuteSolidHhtCore(
|
||||
const Domain& domain, SolidDynamicAnalysisState& state, double alpha,
|
||||
const ParallelFor& parallel_for, LinearSolver& mass_solver,
|
||||
TangentSolver& tangent_solver, const detail::SolidHhtIntegrator& integrator,
|
||||
EquilibriumAlgorithm& algorithm);
|
||||
} // namespace fesa
|
||||
#endif // FESA_ANALYSIS_SOLID_HHT_CORE_H_
|
||||
@@ -1,5 +1,9 @@
|
||||
#include "fesa/analysis/solid_hht_dynamic_procedure.h"
|
||||
|
||||
#include "equilibrium_algorithm.h"
|
||||
#include "analysis/solid_hht_core.h"
|
||||
#include "solid_hht_integrator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
@@ -85,7 +89,7 @@ Status CheckArithmeticEnvironment(const Domain& domain) {
|
||||
/// @brief Owns immutable kernels and the stable full-space translation scatter.
|
||||
struct RuntimeSolid {
|
||||
std::unique_ptr<Element> owner;
|
||||
const Hex8Element* solid;
|
||||
std::unique_ptr<SolidTrialElement> solid;
|
||||
std::vector<std::size_t> scatter;
|
||||
};
|
||||
/// @brief Creates checked kernels in declaration order, with no mutable element
|
||||
@@ -98,7 +102,7 @@ Result<std::vector<RuntimeSolid>> BuildSolids(const Domain& domain,
|
||||
auto owner = factory.Create(domain.Elements()[e], domain);
|
||||
if (!owner.HasValue())
|
||||
return Result<std::vector<RuntimeSolid>>::Failure(owner.GetStatus());
|
||||
const auto* solid = dynamic_cast<const Hex8Element*>(owner.Value().get());
|
||||
auto solid = factory.BindSolidTrial(*owner.Value());
|
||||
if (solid == nullptr)
|
||||
return Result<std::vector<RuntimeSolid>>::Failure(
|
||||
Failure(domain, "solid-dynamic-element-family",
|
||||
@@ -107,7 +111,7 @@ Result<std::vector<RuntimeSolid>> BuildSolids(const Domain& domain,
|
||||
if (!scatter.HasValue())
|
||||
return Result<std::vector<RuntimeSolid>>::Failure(scatter.GetStatus());
|
||||
solids.push_back(
|
||||
{std::move(owner.Value()), solid, std::move(scatter.Value())});
|
||||
{std::move(owner.Value()), std::move(solid), std::move(scatter.Value())});
|
||||
}
|
||||
return Result<std::vector<RuntimeSolid>>::Success(std::move(solids));
|
||||
}
|
||||
@@ -239,7 +243,7 @@ Result<double> ReferenceLength(const Domain& domain) {
|
||||
"Reference length is not finite positive."));
|
||||
return Result<double>::Success(length);
|
||||
}
|
||||
/// @brief Forms M/(beta*h²)+(1+alpha)*Kt in the unchanged full sparse pattern.
|
||||
/// @brief Forms M/(beta*h^2)+(1+alpha)*Kt in the unchanged full sparse pattern.
|
||||
Result<SparseMatrix> EffectiveTangent(const SparseMatrix& m,
|
||||
const PairedTangent& k,
|
||||
const DofManager& dofs, double h,
|
||||
@@ -549,24 +553,6 @@ bool CorrectionPass(const std::array<HighLow, 4>& metrics) {
|
||||
Compare(x, HighLow(1e-10)) <= 0;
|
||||
});
|
||||
}
|
||||
Status SetRates(const SolidDynamicAcceptedFrameState& old,
|
||||
const detail::SolidHhtPairedPredictor& predictor, double h,
|
||||
const HhtParameters& p,
|
||||
SolidDynamicAcceptedFrameState* candidate) {
|
||||
auto rates = detail::EvaluateSolidHhtHighLowRates(
|
||||
candidate->displacement, candidate->low.displacement, predictor,
|
||||
old.acceleration, old.low.acceleration, h, p);
|
||||
if (!rates.HasValue()) return rates.GetStatus();
|
||||
candidate->velocity = std::move(rates.Value().velocity);
|
||||
candidate->low.velocity = std::move(rates.Value().velocity_low);
|
||||
candidate->acceleration = std::move(rates.Value().acceleration);
|
||||
candidate->low.acceleration = std::move(rates.Value().acceleration_low);
|
||||
candidate->algorithmic_acceleration =
|
||||
std::move(rates.Value().algorithmic_acceleration);
|
||||
candidate->low.algorithmic_acceleration =
|
||||
std::move(rates.Value().algorithmic_acceleration_low);
|
||||
return Status::Ok();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SolidHhtDynamicProcedureExecutor::SolidHhtDynamicProcedureExecutor(
|
||||
@@ -600,6 +586,17 @@ Status SolidHhtDynamicProcedureExecutor::ExecuteInto(
|
||||
Status SolidHhtDynamicProcedureExecutor::ExecuteIntoForVerification(
|
||||
const Domain& domain, SolidDynamicAnalysisState& state,
|
||||
double alpha) const {
|
||||
const detail::PairedSolidHhtIntegrator integrator;
|
||||
NewtonStepAlgorithm algorithm;
|
||||
return ExecuteSolidHhtCore(domain, state, alpha, parallel_for_, mass_solver_,
|
||||
tangent_solver_, integrator, algorithm);
|
||||
}
|
||||
|
||||
Status ExecuteSolidHhtCore(
|
||||
const Domain& domain, SolidDynamicAnalysisState& state, double alpha,
|
||||
const ParallelFor& parallel_for, LinearSolver& mass_solver,
|
||||
TangentSolver& tangent_solver, const detail::SolidHhtIntegrator& integrator,
|
||||
EquilibriumAlgorithm& algorithm) {
|
||||
SolidDynamicIterationRecord record;
|
||||
auto fail = [&](const Status& failure) {
|
||||
record.stage = "failed";
|
||||
@@ -630,7 +627,7 @@ Status SolidHhtDynamicProcedureExecutor::ExecuteIntoForVerification(
|
||||
auto mass_partition = partition.Partition(mass.Value(), dofs);
|
||||
if (!mass_partition.HasValue()) return fail(mass_partition.GetStatus());
|
||||
if (dofs.FreeDofCount() != 0) {
|
||||
const auto status = mass_solver_.Factorize(mass_partition.Value().k_ff);
|
||||
const auto status = mass_solver.Factorize(mass_partition.Value().k_ff);
|
||||
if (!status.IsOk()) return fail(status);
|
||||
environment = CheckArithmeticEnvironment(domain);
|
||||
if (!environment.IsOk()) return fail(environment);
|
||||
@@ -655,7 +652,7 @@ Status SolidHhtDynamicProcedureExecutor::ExecuteIntoForVerification(
|
||||
if (!force.HasValue()) return fail(force.GetStatus());
|
||||
initial.external_force = std::move(force.Value());
|
||||
auto tangent =
|
||||
EvaluateSolids(domain, solids.Value(), dofs, parallel_for_, &initial);
|
||||
EvaluateSolids(domain, solids.Value(), dofs, parallel_for, &initial);
|
||||
if (!tangent.HasValue()) return fail(tangent.GetStatus());
|
||||
SolidDynamicIterationEvidence evidence;
|
||||
bool initial_converged = false;
|
||||
@@ -682,7 +679,7 @@ Status SolidHhtDynamicProcedureExecutor::ExecuteIntoForVerification(
|
||||
const auto i = dofs.FreeDofs()[j];
|
||||
rhs[j] = Rounded(Negate(Pair(initial.residual, initial.low.residual, i)));
|
||||
}
|
||||
status = mass_solver_.Solve(rhs, a);
|
||||
status = mass_solver.Solve(rhs, a);
|
||||
if (!status.IsOk()) return fail(status);
|
||||
environment = CheckArithmeticEnvironment(domain);
|
||||
if (!environment.IsOk()) return fail(environment);
|
||||
@@ -726,145 +723,188 @@ Status SolidHhtDynamicProcedureExecutor::ExecuteIntoForVerification(
|
||||
record = {};
|
||||
record.increment_index = increment;
|
||||
record.time = static_cast<double>(increment) * h;
|
||||
auto predictor = detail::PredictSolidHhtHighLow(old, h, p);
|
||||
if (!predictor.HasValue()) return fail(predictor.GetStatus());
|
||||
auto candidate = old;
|
||||
candidate.frame_index = candidate.increment_index = increment;
|
||||
candidate.iteration_count = 0;
|
||||
candidate.time = record.time;
|
||||
candidate.is_initial = false;
|
||||
candidate.iterations.clear();
|
||||
// Begin from certified accepted geometry, not an unchecked predictor.
|
||||
force = LoadAssembler::AssembleFullNodalLoad(model.Value(), dofs,
|
||||
candidate.time);
|
||||
if (!force.HasValue()) return fail(force.GetStatus());
|
||||
candidate.external_force = std::move(force.Value());
|
||||
candidate.low.external_force = Vector(dofs.FullDofCount());
|
||||
std::array<HighLow, 4> correction{}, fresh_correction{};
|
||||
bool accepted = false;
|
||||
for (std::uint32_t iteration = 0; iteration <= 100; ++iteration) {
|
||||
record.stage = "evaluation";
|
||||
record.outcome = "paired-candidate";
|
||||
record.evidence = {};
|
||||
record.evidence.iteration_index = iteration;
|
||||
status = SetRates(old, predictor.Value(), h, p, &candidate);
|
||||
if (!status.IsOk()) return fail(status);
|
||||
tangent = EvaluateSolids(domain, solids.Value(), dofs, parallel_for_,
|
||||
&candidate);
|
||||
if (!tangent.HasValue()) return fail(tangent.GetStatus());
|
||||
status = RecoverEndpoint(domain, dofs, mass.Value(), &old, initial_energy,
|
||||
&candidate, &record.evidence);
|
||||
if (!status.IsOk()) return fail(status);
|
||||
auto MA = detail::MultiplySolidHhtMassHighLow(
|
||||
mass.Value(), candidate.algorithmic_acceleration,
|
||||
candidate.low.algorithmic_acceleration);
|
||||
if (!MA.HasValue()) return fail(MA.GetStatus());
|
||||
Vector rhs(dofs.FreeDofCount());
|
||||
HighLow identity_metric, original_norm, solved_norm, old_norm;
|
||||
for (std::size_t j = 0; j < dofs.FreeDofCount(); ++j) {
|
||||
const auto i = dofs.FreeDofs()[j];
|
||||
detail::SolidHhtPairedForceIdentity identity;
|
||||
if (!detail::EvaluateSolidHhtHighLowForceIdentity(
|
||||
mass.Value(), i, Pair(MA.Value().high, MA.Value().low, i),
|
||||
candidate, old, p.alpha, &identity))
|
||||
return fail(
|
||||
Failure(domain, "solid-dynamic-nonfinite-residual",
|
||||
"Paired weighted force arithmetic is unsupported."));
|
||||
rhs[j] = Rounded(Negate(identity.solved));
|
||||
identity_metric = Maximum(identity_metric, identity.metric);
|
||||
original_norm = Maximum(original_norm, Abs(identity.original));
|
||||
solved_norm = Maximum(solved_norm, Abs(identity.solved));
|
||||
old_norm =
|
||||
Maximum(old_norm, Abs(Pair(old.residual, old.low.residual, i)));
|
||||
if (i == record.evidence.worst_full_dof)
|
||||
record.evidence.worst_weighted_terms = {
|
||||
Rounded(identity.original), Rounded(identity.solved),
|
||||
Rounded(Pair(old.residual, old.low.residual, i)),
|
||||
Rounded(identity.scale)};
|
||||
}
|
||||
if (!Valid(identity_metric) || !Valid(original_norm) ||
|
||||
!Valid(solved_norm) || !Valid(old_norm) ||
|
||||
Compare(identity_metric, HighLow(1e-12)) > 0)
|
||||
return fail(Failure(domain,
|
||||
"solid-dynamic-weighted-algebra-inconsistency",
|
||||
"Paired original and solved forces disagree beyond "
|
||||
"their uncancelled scale."));
|
||||
record.evidence.weighted_equivalence_metric = Rounded(identity_metric);
|
||||
record.evidence.weighted_residual_numerator = Rounded(original_norm);
|
||||
record.evidence.recombined_residual_numerator = Rounded(solved_norm);
|
||||
record.evidence.old_residual_numerator = Rounded(old_norm);
|
||||
for (std::size_t c = 0; c < 4; ++c) {
|
||||
if (!Valid(correction[c]) || !Valid(fresh_correction[c]))
|
||||
return fail(detail::SolidHhtPairedKinematicsFailure());
|
||||
record.evidence.correction_metrics[c] = Rounded(correction[c]);
|
||||
}
|
||||
record.evidence.correction_pass =
|
||||
CorrectionPass(correction) && CorrectionPass(fresh_correction);
|
||||
const auto update = CheckUpdates(old, candidate, p, h);
|
||||
if (!Valid(update))
|
||||
return fail(detail::SolidHhtPairedKinematicsFailure());
|
||||
record.evidence.update_metric = Rounded(update);
|
||||
record.evidence.update_pass = Compare(update, HighLow(1e-12)) <= 0;
|
||||
candidate.iterations.push_back(record.evidence);
|
||||
candidate.iteration_count = iteration;
|
||||
if (record.evidence.residual_pass && record.evidence.correction_pass &&
|
||||
record.evidence.update_pass) {
|
||||
status = state.CommitAcceptedFrame(candidate);
|
||||
if (!status.IsOk()) return fail(status);
|
||||
record.stage = "accepted";
|
||||
record.outcome = "increment";
|
||||
state.RecordIteration(record);
|
||||
accepted = true;
|
||||
break;
|
||||
}
|
||||
state.RecordIteration(record);
|
||||
if (iteration == 100)
|
||||
return fail(Failure(
|
||||
domain, "solid-dynamic-nonconvergence",
|
||||
"Paired HHT endpoint did not converge in100 Newton solves."));
|
||||
auto effective =
|
||||
EffectiveTangent(mass.Value(), tangent.Value(), dofs, h, p);
|
||||
if (!effective.HasValue()) return fail(effective.GetStatus());
|
||||
auto reduced = partition.Partition(effective.Value(), dofs);
|
||||
if (!reduced.HasValue()) return fail(reduced.GetStatus());
|
||||
Vector du(dofs.FreeDofCount());
|
||||
if (dofs.FreeDofCount() != 0) {
|
||||
status = tangent_solver_.FactorizeFresh(reduced.Value().k_ff);
|
||||
if (!status.IsOk()) return fail(status);
|
||||
environment = CheckArithmeticEnvironment(domain);
|
||||
if (!environment.IsOk()) return fail(environment);
|
||||
status = tangent_solver_.SolveCorrection(rhs, du);
|
||||
if (!status.IsOk()) return fail(status);
|
||||
environment = CheckArithmeticEnvironment(domain);
|
||||
if (!environment.IsOk()) return fail(environment);
|
||||
}
|
||||
if (du.Size() != dofs.FreeDofCount())
|
||||
return fail(Failure(domain, "solid-dynamic-invalid-backend-result",
|
||||
"Tangent solve returned incompatible correction."));
|
||||
for (std::size_t j = 0; j < du.Size(); ++j)
|
||||
if (!std::isfinite(du[j]))
|
||||
return fail(Failure(domain, "solid-dynamic-invalid-backend-result",
|
||||
"Tangent solve returned nonfinite correction."));
|
||||
fresh_correction = FreshCorrectionMetrics(du, length.Value(), p);
|
||||
const auto before = candidate;
|
||||
for (std::size_t j = 0; j < du.Size(); ++j) {
|
||||
const auto i = dofs.FreeDofs()[j];
|
||||
const auto value =
|
||||
Add(Pair(candidate.displacement, candidate.low.displacement, i),
|
||||
HighLow(du[j] == 0 ? 0.0 : du[j]));
|
||||
if (!Valid(value))
|
||||
return fail(detail::SolidHhtPairedKinematicsFailure());
|
||||
Store(value, &candidate.displacement, &candidate.low.displacement, i);
|
||||
}
|
||||
status = SetRates(old, predictor.Value(), h, p, &candidate);
|
||||
if (!status.IsOk()) return fail(status);
|
||||
correction =
|
||||
ObservedCorrectionMetrics(before, candidate, h, length.Value());
|
||||
}
|
||||
if (!accepted)
|
||||
return fail(Failure(domain, "solid-dynamic-nonconvergence",
|
||||
"No complete paired endpoint was accepted."));
|
||||
status = detail::RunSolidHhtStep(
|
||||
integrator, old, h, p, fail,
|
||||
[&](const detail::SolidHhtIntegrator& step_integrator,
|
||||
const detail::SolidHhtPairedPredictor& predictor)
|
||||
-> Result<SolidDynamicAcceptedFrameState> {
|
||||
auto candidate = old;
|
||||
candidate.frame_index = candidate.increment_index = increment;
|
||||
candidate.iteration_count = 0;
|
||||
candidate.time = record.time;
|
||||
candidate.is_initial = false;
|
||||
candidate.iterations.clear();
|
||||
// Begin from certified accepted geometry, not an unchecked predictor.
|
||||
force = LoadAssembler::AssembleFullNodalLoad(model.Value(), dofs,
|
||||
candidate.time);
|
||||
if (!force.HasValue())
|
||||
return Result<SolidDynamicAcceptedFrameState>::Failure(fail(force.GetStatus()));
|
||||
candidate.external_force = std::move(force.Value());
|
||||
candidate.low.external_force = Vector(dofs.FullDofCount());
|
||||
std::array<HighLow, 4> correction{}, fresh_correction{};
|
||||
using StepResult = Result<std::optional<SolidDynamicAcceptedFrameState>>;
|
||||
const auto report_failure = fail;
|
||||
const auto record_iteration = [&](const auto& value) {
|
||||
state.RecordIteration(value);
|
||||
};
|
||||
SolidHhtStep<SolidDynamicAcceptedFrameState> iteration_step(
|
||||
[&](std::uint32_t iteration) -> StepResult {
|
||||
const auto fail = [&](const Status& failure) {
|
||||
return StepResult::Failure(report_failure(failure));
|
||||
};
|
||||
record.stage = "evaluation";
|
||||
record.outcome = "paired-candidate";
|
||||
record.evidence = {};
|
||||
record.evidence.iteration_index = iteration;
|
||||
status =
|
||||
detail::SetSolidHhtRates(step_integrator, old, predictor, h, p, &candidate);
|
||||
if (!status.IsOk())
|
||||
return fail(status);
|
||||
tangent = EvaluateSolids(domain, solids.Value(), dofs, parallel_for,
|
||||
&candidate);
|
||||
if (!tangent.HasValue())
|
||||
return fail(tangent.GetStatus());
|
||||
status =
|
||||
RecoverEndpoint(domain, dofs, mass.Value(), &old, initial_energy,
|
||||
&candidate, &record.evidence);
|
||||
if (!status.IsOk())
|
||||
return fail(status);
|
||||
auto MA = detail::MultiplySolidHhtMassHighLow(
|
||||
mass.Value(), candidate.algorithmic_acceleration,
|
||||
candidate.low.algorithmic_acceleration);
|
||||
if (!MA.HasValue())
|
||||
return fail(MA.GetStatus());
|
||||
Vector rhs(dofs.FreeDofCount());
|
||||
HighLow identity_metric, original_norm, solved_norm, old_norm;
|
||||
for (std::size_t j = 0; j < dofs.FreeDofCount(); ++j) {
|
||||
const auto i = dofs.FreeDofs()[j];
|
||||
detail::SolidHhtPairedForceIdentity identity;
|
||||
if (!detail::EvaluateSolidHhtHighLowForceIdentity(
|
||||
mass.Value(), i, Pair(MA.Value().high, MA.Value().low, i),
|
||||
candidate, old, p.alpha, &identity))
|
||||
return fail(
|
||||
Failure(domain, "solid-dynamic-nonfinite-residual",
|
||||
"Paired weighted force arithmetic is unsupported."));
|
||||
rhs[j] = Rounded(Negate(identity.solved));
|
||||
identity_metric = Maximum(identity_metric, identity.metric);
|
||||
original_norm = Maximum(original_norm, Abs(identity.original));
|
||||
solved_norm = Maximum(solved_norm, Abs(identity.solved));
|
||||
old_norm =
|
||||
Maximum(old_norm, Abs(Pair(old.residual, old.low.residual, i)));
|
||||
if (i == record.evidence.worst_full_dof)
|
||||
record.evidence.worst_weighted_terms = {
|
||||
Rounded(identity.original), Rounded(identity.solved),
|
||||
Rounded(Pair(old.residual, old.low.residual, i)),
|
||||
Rounded(identity.scale)};
|
||||
}
|
||||
if (!Valid(identity_metric) || !Valid(original_norm) ||
|
||||
!Valid(solved_norm) || !Valid(old_norm) ||
|
||||
Compare(identity_metric, HighLow(1e-12)) > 0)
|
||||
return fail(
|
||||
Failure(domain, "solid-dynamic-weighted-algebra-inconsistency",
|
||||
"Paired original and solved forces disagree beyond "
|
||||
"their uncancelled scale."));
|
||||
record.evidence.weighted_equivalence_metric =
|
||||
Rounded(identity_metric);
|
||||
record.evidence.weighted_residual_numerator = Rounded(original_norm);
|
||||
record.evidence.recombined_residual_numerator = Rounded(solved_norm);
|
||||
record.evidence.old_residual_numerator = Rounded(old_norm);
|
||||
for (std::size_t c = 0; c < 4; ++c) {
|
||||
if (!Valid(correction[c]) || !Valid(fresh_correction[c]))
|
||||
return fail(detail::SolidHhtPairedKinematicsFailure());
|
||||
record.evidence.correction_metrics[c] = Rounded(correction[c]);
|
||||
}
|
||||
record.evidence.correction_pass =
|
||||
CorrectionPass(correction) && CorrectionPass(fresh_correction);
|
||||
const auto update = CheckUpdates(old, candidate, p, h);
|
||||
if (!Valid(update))
|
||||
return fail(detail::SolidHhtPairedKinematicsFailure());
|
||||
record.evidence.update_metric = Rounded(update);
|
||||
record.evidence.update_pass = Compare(update, HighLow(1e-12)) <= 0;
|
||||
candidate.iterations.push_back(record.evidence);
|
||||
candidate.iteration_count = iteration;
|
||||
if (record.evidence.residual_pass &&
|
||||
record.evidence.correction_pass && record.evidence.update_pass) {
|
||||
return StepResult::Success(candidate);
|
||||
}
|
||||
record_iteration(record);
|
||||
if (iteration == 100)
|
||||
return fail(Failure(
|
||||
domain, "solid-dynamic-nonconvergence",
|
||||
"Paired HHT endpoint did not converge in100 Newton solves."));
|
||||
auto effective =
|
||||
EffectiveTangent(mass.Value(), tangent.Value(), dofs, h, p);
|
||||
if (!effective.HasValue())
|
||||
return fail(effective.GetStatus());
|
||||
auto reduced = partition.Partition(effective.Value(), dofs);
|
||||
if (!reduced.HasValue())
|
||||
return fail(reduced.GetStatus());
|
||||
Vector du(dofs.FreeDofCount());
|
||||
if (dofs.FreeDofCount() != 0) {
|
||||
status = tangent_solver.FactorizeFresh(reduced.Value().k_ff);
|
||||
if (!status.IsOk())
|
||||
return fail(status);
|
||||
environment = CheckArithmeticEnvironment(domain);
|
||||
if (!environment.IsOk())
|
||||
return fail(environment);
|
||||
status = tangent_solver.SolveCorrection(rhs, du);
|
||||
if (!status.IsOk())
|
||||
return fail(status);
|
||||
environment = CheckArithmeticEnvironment(domain);
|
||||
if (!environment.IsOk())
|
||||
return fail(environment);
|
||||
}
|
||||
if (du.Size() != dofs.FreeDofCount())
|
||||
return fail(
|
||||
Failure(domain, "solid-dynamic-invalid-backend-result",
|
||||
"Tangent solve returned incompatible correction."));
|
||||
for (std::size_t j = 0; j < du.Size(); ++j)
|
||||
if (!std::isfinite(du[j]))
|
||||
return fail(
|
||||
Failure(domain, "solid-dynamic-invalid-backend-result",
|
||||
"Tangent solve returned nonfinite correction."));
|
||||
fresh_correction = FreshCorrectionMetrics(du, length.Value(), p);
|
||||
const auto before = candidate;
|
||||
for (std::size_t j = 0; j < du.Size(); ++j) {
|
||||
const auto i = dofs.FreeDofs()[j];
|
||||
const auto value =
|
||||
Add(Pair(candidate.displacement, candidate.low.displacement, i),
|
||||
HighLow(du[j] == 0 ? 0.0 : du[j]));
|
||||
if (!Valid(value))
|
||||
return fail(detail::SolidHhtPairedKinematicsFailure());
|
||||
Store(value, &candidate.displacement, &candidate.low.displacement,
|
||||
i);
|
||||
}
|
||||
status =
|
||||
detail::SetSolidHhtRates(step_integrator, old, predictor, h, p, &candidate);
|
||||
if (!status.IsOk())
|
||||
return fail(status);
|
||||
correction =
|
||||
ObservedCorrectionMetrics(before, candidate, h, length.Value());
|
||||
return StepResult::Success(std::nullopt);
|
||||
},
|
||||
100U);
|
||||
const Status solved = iteration_step.Solve(algorithm);
|
||||
if (!solved.IsOk())
|
||||
return Result<SolidDynamicAcceptedFrameState>::Failure(solved);
|
||||
if (!iteration_step.HasCandidate())
|
||||
return Result<SolidDynamicAcceptedFrameState>::Failure(
|
||||
fail(Failure(domain, "solid-dynamic-nonconvergence",
|
||||
"No complete paired endpoint was accepted.")));
|
||||
return Result<SolidDynamicAcceptedFrameState>::Success(
|
||||
iteration_step.TakeCandidate());
|
||||
},
|
||||
[&](SolidDynamicAcceptedFrameState&& accepted_candidate) -> Status {
|
||||
status = state.CommitAcceptedFrame(accepted_candidate);
|
||||
if (!status.IsOk())
|
||||
return fail(status);
|
||||
record.stage = "accepted";
|
||||
record.outcome = "increment";
|
||||
state.RecordIteration(record);
|
||||
return Status::Ok();
|
||||
});
|
||||
if (!status.IsOk()) return status;
|
||||
}
|
||||
return Status::Ok();
|
||||
}
|
||||
} // namespace fesa
|
||||
} // namespace fesa
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
#ifndef FESA_ANALYSIS_SOLID_HHT_INTEGRATOR_H_
|
||||
#define FESA_ANALYSIS_SOLID_HHT_INTEGRATOR_H_
|
||||
|
||||
#include <utility>
|
||||
#include "analysis/solid_hht_high_low.h"
|
||||
|
||||
namespace fesa::detail {
|
||||
|
||||
/// @brief Predicts and evaluates complete paired mechanical HHT kinematics.
|
||||
class SolidHhtIntegrator {
|
||||
public:
|
||||
virtual ~SolidHhtIntegrator() = default;
|
||||
virtual Result<SolidHhtPairedPredictor> Predict(
|
||||
const SolidDynamicAcceptedFrameState& old, double h,
|
||||
const HhtParameters& parameters) const = 0;
|
||||
virtual Result<SolidHhtPairedRates> EvaluateRates(
|
||||
const Vector& high, const Vector& low,
|
||||
const SolidHhtPairedPredictor& predictor, const Vector& old_acceleration,
|
||||
const Vector& old_acceleration_low, double h,
|
||||
const HhtParameters& parameters) const = 0;
|
||||
};
|
||||
|
||||
/// @brief Retains every high/low physical and auxiliary acceleration channel.
|
||||
class PairedSolidHhtIntegrator final : public SolidHhtIntegrator {
|
||||
public:
|
||||
Result<SolidHhtPairedPredictor> Predict(
|
||||
const SolidDynamicAcceptedFrameState& old, double h,
|
||||
const HhtParameters& parameters) const override {
|
||||
return PredictSolidHhtHighLow(old, h, parameters);
|
||||
}
|
||||
Result<SolidHhtPairedRates> EvaluateRates(
|
||||
const Vector& high, const Vector& low,
|
||||
const SolidHhtPairedPredictor& predictor, const Vector& old_acceleration,
|
||||
const Vector& old_acceleration_low, double h,
|
||||
const HhtParameters& parameters) const override {
|
||||
return EvaluateSolidHhtHighLowRates(high, low, predictor, old_acceleration,
|
||||
old_acceleration_low, h, parameters);
|
||||
}
|
||||
};
|
||||
|
||||
inline Status SetSolidHhtRates(const SolidHhtIntegrator& integrator,
|
||||
const SolidDynamicAcceptedFrameState& old,
|
||||
const SolidHhtPairedPredictor& predictor, double h,
|
||||
const HhtParameters& p,
|
||||
SolidDynamicAcceptedFrameState* candidate) {
|
||||
auto rates = integrator.EvaluateRates(
|
||||
candidate->displacement, candidate->low.displacement, predictor,
|
||||
old.acceleration, old.low.acceleration, h, p);
|
||||
if (!rates.HasValue()) return rates.GetStatus();
|
||||
candidate->velocity = std::move(rates.Value().velocity);
|
||||
candidate->low.velocity = std::move(rates.Value().velocity_low);
|
||||
candidate->acceleration = std::move(rates.Value().acceleration);
|
||||
candidate->low.acceleration = std::move(rates.Value().acceleration_low);
|
||||
candidate->algorithmic_acceleration =
|
||||
std::move(rates.Value().algorithmic_acceleration);
|
||||
candidate->low.algorithmic_acceleration =
|
||||
std::move(rates.Value().algorithmic_acceleration_low);
|
||||
return Status::Ok();
|
||||
}
|
||||
/// @brief Preserves predictor failure recording, endpoint evaluation and commit.
|
||||
/// @note Borrowed callbacks do not escape; only commit may update history.
|
||||
template <typename Failure, typename Evaluate, typename Commit>
|
||||
Status RunSolidHhtStep(const SolidHhtIntegrator& integrator,
|
||||
const SolidDynamicAcceptedFrameState& old, double h,
|
||||
const HhtParameters& parameters, Failure&& failure,
|
||||
Evaluate&& evaluate, Commit&& commit) {
|
||||
auto predictor = integrator.Predict(old, h, parameters);
|
||||
if (!predictor.HasValue()) return failure(predictor.GetStatus());
|
||||
auto pending = evaluate(integrator, predictor.Value());
|
||||
if (!pending.HasValue()) return pending.GetStatus();
|
||||
return commit(std::move(pending.Value()));
|
||||
}
|
||||
|
||||
} // namespace fesa::detail
|
||||
#endif // FESA_ANALYSIS_SOLID_HHT_INTEGRATOR_H_
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef FESA_ANALYSIS_SOLID_STATIC_CORE_H_
|
||||
#define FESA_ANALYSIS_SOLID_STATIC_CORE_H_
|
||||
#include "fesa/core/status.h"
|
||||
namespace fesa {
|
||||
class Domain;
|
||||
class SolidStaticState;
|
||||
class ParallelFor;
|
||||
class LinearSolver;
|
||||
class TangentSolver;
|
||||
class EquilibriumAlgorithm;
|
||||
/// @brief Borrows the production static core's execution/solve/iteration policy.
|
||||
/// @note Every adapter must outlive ExecuteInto; state owns accepted history.
|
||||
class SolidStaticCore {
|
||||
public:
|
||||
SolidStaticCore(const ParallelFor& parallel_for, LinearSolver& linear_solver,
|
||||
TangentSolver& tangent_solver, EquilibriumAlgorithm& algorithm)
|
||||
: parallel_for_(parallel_for), linear_solver_(linear_solver),
|
||||
tangent_solver_(tangent_solver), algorithm_(algorithm) {}
|
||||
Status ExecuteInto(const Domain& domain, SolidStaticState& state) const;
|
||||
private:
|
||||
const ParallelFor& parallel_for_;
|
||||
LinearSolver& linear_solver_;
|
||||
TangentSolver& tangent_solver_;
|
||||
EquilibriumAlgorithm& algorithm_;
|
||||
};
|
||||
} // namespace fesa
|
||||
#endif // FESA_ANALYSIS_SOLID_STATIC_CORE_H_
|
||||
@@ -1,5 +1,8 @@
|
||||
#include "fesa/analysis/solid_static_procedure.h"
|
||||
|
||||
#include "equilibrium_algorithm.h"
|
||||
#include "analysis/solid_static_core.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
@@ -21,6 +24,8 @@
|
||||
#include "fesa/solvers/linear/linear_solver.h"
|
||||
#include "fesa/solvers/linear/tangent_solver.h"
|
||||
|
||||
#include "assembly/ordered_contribution_internal.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
constexpr std::uint32_t kMaximumIterations = 100U;
|
||||
@@ -28,7 +33,7 @@ constexpr double kRoundoffBudget =
|
||||
512.0 * std::numeric_limits<double>::epsilon();
|
||||
|
||||
struct RuntimeSolid {
|
||||
const Hex8Element* element;
|
||||
std::unique_ptr<SolidTrialElement> element;
|
||||
std::vector<std::size_t> scatter;
|
||||
};
|
||||
struct SolidAssembly {
|
||||
@@ -131,9 +136,8 @@ Result<SolidAssembly> Assemble(const std::vector<RuntimeSolid>& solids,
|
||||
const auto& response = responses[e]->Value();
|
||||
for (std::size_t i = 0U; i < 24U; ++i) {
|
||||
force[solids[e].scatter[i]] += response.internal_force[i];
|
||||
for (std::size_t j = 0U; j < 24U; ++j)
|
||||
contributions.push_back({solids[e].scatter[i], solids[e].scatter[j],
|
||||
response.tangent(i, j), e, 24U * i + j});
|
||||
assembly_internal::AppendLocalMatrixRow(
|
||||
response.tangent, solids[e].scatter, i, e, contributions);
|
||||
}
|
||||
energy += response.strain_energy;
|
||||
points.insert(points.end(), response.points.begin(), response.points.end());
|
||||
@@ -283,6 +287,13 @@ SolidStaticProcedureExecutor::SolidStaticProcedureExecutor(
|
||||
|
||||
Status SolidStaticProcedureExecutor::ExecuteInto(
|
||||
const Domain& domain, SolidStaticState& state) const {
|
||||
NewtonStepAlgorithm algorithm;
|
||||
return SolidStaticCore(parallel_for_, linear_solver_, tangent_solver_, algorithm)
|
||||
.ExecuteInto(domain, state);
|
||||
}
|
||||
|
||||
Status SolidStaticCore::ExecuteInto(
|
||||
const Domain& domain, SolidStaticState& state) const {
|
||||
if (state.initialized_)
|
||||
return Failure(domain, "solid-static-observer-not-fresh",
|
||||
"Execution requires a fresh static observer.");
|
||||
@@ -309,14 +320,14 @@ Status SolidStaticProcedureExecutor::ExecuteInto(
|
||||
state = std::move(baseline.Value());
|
||||
std::vector<RuntimeSolid> solids;
|
||||
for (const auto& element : elements) {
|
||||
const auto* hex = dynamic_cast<const Hex8Element*>(element.get());
|
||||
auto hex = ElementFactory{}.BindSolidTrial(*element);
|
||||
if (hex == nullptr)
|
||||
return Failure(domain, "invalid-solid-static-element",
|
||||
"Solid STATIC requires full-integration Hex8 kernels.",
|
||||
FailureCategory::kModel);
|
||||
auto scatter = dofs.ElementScatter(hex->DofLayout());
|
||||
if (!scatter.HasValue()) return scatter.GetStatus();
|
||||
solids.push_back({hex, std::move(scatter.Value())});
|
||||
solids.push_back({std::move(hex), std::move(scatter.Value())});
|
||||
}
|
||||
auto stiffness = SparseAssembler::Assemble(view, dofs, parallel_for_);
|
||||
if (!stiffness.HasValue()) return stiffness.GetStatus();
|
||||
@@ -336,7 +347,7 @@ Status SolidStaticProcedureExecutor::ExecuteInto(
|
||||
// Preserve the required factorization-before-load-assembly lifecycle.
|
||||
auto load = LoadAssembler::AssembleFullNodalLoad(model.Value(), dofs);
|
||||
if (!load.HasValue()) return load.GetStatus();
|
||||
auto rhs = LoadAssembler::EffectiveFreeRhs(
|
||||
auto rhs = constraints.EffectiveFreeRhs(
|
||||
load.Value(), partition.Value().k_fc, dofs.PrescribedValues(), dofs);
|
||||
if (!rhs.HasValue()) return rhs.GetStatus();
|
||||
Vector free{dofs.FreeDofCount()};
|
||||
@@ -369,91 +380,128 @@ Status SolidStaticProcedureExecutor::ExecuteInto(
|
||||
for (std::size_t c = 0U; c < dofs.ConstrainedDofCount(); ++c)
|
||||
trial[dofs.ConstrainedDofs()[c]] =
|
||||
increment.load_factor * dofs.PrescribedValues()[c];
|
||||
for (std::uint32_t iteration = 1U; iteration <= kMaximumIterations;
|
||||
++iteration) {
|
||||
struct SolidStepCandidate {
|
||||
SolidStaticFrame frame;
|
||||
SolidStaticIterationRecord record;
|
||||
record.increment_index = increment.increment_index;
|
||||
record.load_factor = increment.load_factor;
|
||||
record.iteration = iteration;
|
||||
record.stage = "entered";
|
||||
const auto fail = [&](const Status& failure) {
|
||||
record.diagnostic_code = failure.Diagnostics().empty()
|
||||
? "solid-static-failure"
|
||||
: failure.Diagnostics()[0].code;
|
||||
record.failure_reason = failure.Diagnostics().empty()
|
||||
? "Static trial failed."
|
||||
: failure.Diagnostics()[0].message;
|
||||
state.iterations_.push_back(record);
|
||||
return failure;
|
||||
};
|
||||
record.stage = "trial-recovery";
|
||||
auto assembly = Assemble(solids, dofs, trial, true, parallel_for_);
|
||||
if (!assembly.HasValue()) return fail(assembly.GetStatus());
|
||||
SolidStaticFrame frame = state.committed_;
|
||||
frame.balance = {};
|
||||
frame.global_equilibrium = {};
|
||||
frame.frame_index =
|
||||
static_cast<std::uint32_t>(state.accepted_frames_.size());
|
||||
frame.increment_index = increment.increment_index;
|
||||
frame.load_factor = increment.load_factor;
|
||||
frame.iteration_count = iteration;
|
||||
frame.is_factor_one = increment.is_factor_one;
|
||||
frame.displacement = trial;
|
||||
frame.internal_force = std::move(assembly.Value().force);
|
||||
frame.external_force = base_load.Value();
|
||||
frame.external_force.Scale(increment.load_factor);
|
||||
frame.solid_results = std::move(assembly.Value().points);
|
||||
frame.physical_strain_energy = assembly.Value().energy;
|
||||
auto status = CompleteFrame(domain, dofs, true, stiffness.Value(), frame);
|
||||
if (!status.IsOk()) return fail(status);
|
||||
record.stage = "tangent-partition";
|
||||
auto partition = constraints.Partition(assembly.Value().tangent, dofs);
|
||||
if (!partition.HasValue()) return fail(partition.GetStatus());
|
||||
Vector correction{dofs.FreeDofCount()};
|
||||
// Empty free is vacuous; prescribed geometry/recovery above still runs.
|
||||
if (dofs.FreeDofCount() != 0U) {
|
||||
record.stage = "tangent-factorization";
|
||||
status = tangent_solver_.FactorizeFresh(partition.Value().k_ff);
|
||||
if (!status.IsOk()) return fail(status);
|
||||
auto rhs = constraints.GatherFree(frame.residual, dofs);
|
||||
rhs.Scale(-1.0);
|
||||
record.stage = "correction-solve";
|
||||
status = tangent_solver_.SolveCorrection(rhs, correction);
|
||||
if (!status.IsOk()) return fail(status);
|
||||
}
|
||||
Vector current_increment{dofs.FreeDofCount()};
|
||||
for (std::size_t f = 0U; f < dofs.FreeDofCount(); ++f) {
|
||||
const auto full = dofs.FreeDofs()[f];
|
||||
current_increment[f] = trial[full] - start[full];
|
||||
}
|
||||
record.stage = "convergence";
|
||||
auto convergence = EvaluateSolidStaticConvergence(
|
||||
constraints.GatherFree(frame.residual, dofs),
|
||||
constraints.GatherFree(frame.internal_force, dofs),
|
||||
constraints.GatherFree(frame.external_force, dofs), correction,
|
||||
current_increment);
|
||||
if (!convergence.HasValue()) return fail(convergence.GetStatus());
|
||||
record.force_residual = convergence.Value().force_residual;
|
||||
record.translation_correction =
|
||||
convergence.Value().translation_correction;
|
||||
record.convergence_evaluated = true;
|
||||
if (record.force_residual.passed &&
|
||||
record.translation_correction.passed) {
|
||||
record.stage = "commit";
|
||||
status = state.CommitAcceptedFrame(frame);
|
||||
if (!status.IsOk()) return fail(status);
|
||||
record.accepted = true;
|
||||
state.iterations_.push_back(record);
|
||||
break;
|
||||
}
|
||||
if (iteration == kMaximumIterations)
|
||||
return fail(Failure(domain, "solid-static-nonconvergence",
|
||||
"Solid STATIC exceeded 100 Newton iterations."));
|
||||
for (std::size_t f = 0U; f < dofs.FreeDofCount(); ++f)
|
||||
trial[dofs.FreeDofs()[f]] += correction[f];
|
||||
record.stage = "continue";
|
||||
state.iterations_.push_back(record);
|
||||
};
|
||||
const auto observe_iteration =
|
||||
[&](const SolidStaticIterationRecord& record) {
|
||||
state.iterations_.push_back(record);
|
||||
};
|
||||
SolidFullNewtonStep<SolidStepCandidate> iteration_step(
|
||||
[&](std::uint32_t iteration)
|
||||
-> Result<std::optional<SolidStepCandidate>> {
|
||||
SolidStaticIterationRecord record;
|
||||
record.increment_index = increment.increment_index;
|
||||
record.load_factor = increment.load_factor;
|
||||
record.iteration = iteration;
|
||||
record.stage = "entered";
|
||||
const auto fail = [&](const Status& failure) {
|
||||
record.diagnostic_code = failure.Diagnostics().empty()
|
||||
? "solid-static-failure"
|
||||
: failure.Diagnostics()[0].code;
|
||||
record.failure_reason = failure.Diagnostics().empty()
|
||||
? "Static trial failed."
|
||||
: failure.Diagnostics()[0].message;
|
||||
observe_iteration(record);
|
||||
return Result<std::optional<SolidStepCandidate>>::Failure(failure);
|
||||
};
|
||||
record.stage = "trial-recovery";
|
||||
auto assembly = Assemble(solids, dofs, trial, true, parallel_for_);
|
||||
if (!assembly.HasValue())
|
||||
return fail(assembly.GetStatus());
|
||||
SolidStaticFrame frame = state.committed_;
|
||||
frame.balance = {};
|
||||
frame.global_equilibrium = {};
|
||||
frame.frame_index =
|
||||
static_cast<std::uint32_t>(state.accepted_frames_.size());
|
||||
frame.increment_index = increment.increment_index;
|
||||
frame.load_factor = increment.load_factor;
|
||||
frame.iteration_count = iteration;
|
||||
frame.is_factor_one = increment.is_factor_one;
|
||||
frame.displacement = trial;
|
||||
frame.internal_force = std::move(assembly.Value().force);
|
||||
frame.external_force = base_load.Value();
|
||||
frame.external_force.Scale(increment.load_factor);
|
||||
frame.solid_results = std::move(assembly.Value().points);
|
||||
frame.physical_strain_energy = assembly.Value().energy;
|
||||
auto status =
|
||||
CompleteFrame(domain, dofs, true, stiffness.Value(), frame);
|
||||
if (!status.IsOk())
|
||||
return fail(status);
|
||||
record.stage = "tangent-partition";
|
||||
auto partition =
|
||||
constraints.Partition(assembly.Value().tangent, dofs);
|
||||
if (!partition.HasValue())
|
||||
return fail(partition.GetStatus());
|
||||
Vector correction{dofs.FreeDofCount()};
|
||||
// Empty free is vacuous; prescribed geometry/recovery above still
|
||||
// runs.
|
||||
if (dofs.FreeDofCount() != 0U) {
|
||||
record.stage = "tangent-factorization";
|
||||
status = tangent_solver_.FactorizeFresh(partition.Value().k_ff);
|
||||
if (!status.IsOk())
|
||||
return fail(status);
|
||||
auto rhs = constraints.GatherFree(frame.residual, dofs);
|
||||
rhs.Scale(-1.0);
|
||||
record.stage = "correction-solve";
|
||||
status = tangent_solver_.SolveCorrection(rhs, correction);
|
||||
if (!status.IsOk())
|
||||
return fail(status);
|
||||
}
|
||||
Vector current_increment{dofs.FreeDofCount()};
|
||||
for (std::size_t f = 0U; f < dofs.FreeDofCount(); ++f) {
|
||||
const auto full = dofs.FreeDofs()[f];
|
||||
current_increment[f] = trial[full] - start[full];
|
||||
}
|
||||
record.stage = "convergence";
|
||||
auto convergence = EvaluateSolidStaticConvergence(
|
||||
constraints.GatherFree(frame.residual, dofs),
|
||||
constraints.GatherFree(frame.internal_force, dofs),
|
||||
constraints.GatherFree(frame.external_force, dofs), correction,
|
||||
current_increment);
|
||||
if (!convergence.HasValue())
|
||||
return fail(convergence.GetStatus());
|
||||
record.force_residual = convergence.Value().force_residual;
|
||||
record.translation_correction =
|
||||
convergence.Value().translation_correction;
|
||||
record.convergence_evaluated = true;
|
||||
if (record.force_residual.passed &&
|
||||
record.translation_correction.passed) {
|
||||
record.stage = "commit";
|
||||
return Result<std::optional<SolidStepCandidate>>::Success(
|
||||
SolidStepCandidate{std::move(frame), record});
|
||||
}
|
||||
if (iteration == kMaximumIterations)
|
||||
return fail(
|
||||
Failure(domain, "solid-static-nonconvergence",
|
||||
"Solid STATIC exceeded 100 Newton iterations."));
|
||||
for (std::size_t f = 0U; f < dofs.FreeDofCount(); ++f)
|
||||
trial[dofs.FreeDofs()[f]] += correction[f];
|
||||
record.stage = "continue";
|
||||
observe_iteration(record);
|
||||
return Result<std::optional<SolidStepCandidate>>::Success(
|
||||
std::nullopt);
|
||||
},
|
||||
kMaximumIterations);
|
||||
const Status solved = iteration_step.Solve(algorithm_);
|
||||
if (!solved.IsOk())
|
||||
return solved;
|
||||
auto candidate = iteration_step.TakeCandidate();
|
||||
auto& record = candidate.record;
|
||||
const Status committed = state.CommitAcceptedFrame(candidate.frame);
|
||||
if (!committed.IsOk()) {
|
||||
record.diagnostic_code = committed.Diagnostics().empty()
|
||||
? "solid-static-failure"
|
||||
: committed.Diagnostics()[0].code;
|
||||
record.failure_reason = committed.Diagnostics().empty()
|
||||
? "Static trial failed."
|
||||
: committed.Diagnostics()[0].message;
|
||||
observe_iteration(record);
|
||||
return committed;
|
||||
}
|
||||
record.accepted = true;
|
||||
observe_iteration(record);
|
||||
}
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
@@ -37,10 +37,9 @@ double InfinityNorm(const Vector& values) {
|
||||
return norm;
|
||||
}
|
||||
/// @brief Compares immutable semantic and raw source identities.
|
||||
bool SameIdentity(const SourceEntityId& a, const SourceEntityId& b) {
|
||||
return a.instance_name == b.instance_name &&
|
||||
a.source_label == b.source_label &&
|
||||
a.source_label_text == b.source_label_text;
|
||||
bool SameIdentity(const SourceEntityId& a,
|
||||
const SourceEntityId& b) {
|
||||
return fesa::SourceIdentityEquals(a, b);
|
||||
}
|
||||
/// @brief Builds one raw criterion with the approved branch and physical unit.
|
||||
NewtonChannelDecision Decision(const char* name, double numerator,
|
||||
@@ -119,7 +118,7 @@ Result<SolidStaticState> SolidStaticState::Create(const Domain& domain,
|
||||
auto element = ElementFactory{}.Create(domain.Elements()[e], domain);
|
||||
if (!element.HasValue())
|
||||
return Result<SolidStaticState>::Failure(element.GetStatus());
|
||||
const auto* hex = dynamic_cast<const Hex8Element*>(element.Value().get());
|
||||
auto hex = ElementFactory{}.BindSolidTrial(*element.Value());
|
||||
if (hex == nullptr)
|
||||
return Result<SolidStaticState>::Failure(StateFailure(
|
||||
"invalid-solid-static-state", "Static state requires Hex8 kernels."));
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/assembly/thermal_assembler.h"
|
||||
#include "math/finite_values.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
|
||||
@@ -23,9 +26,7 @@ Status Failure(const std::string& message) {
|
||||
|
||||
/// @brief Checks every stored scalar without imposing a temperature sign bound.
|
||||
bool Finite(const Vector& vector) {
|
||||
for (std::size_t i = 0; i < vector.Size(); ++i)
|
||||
if (!std::isfinite(vector[i])) return false;
|
||||
return true;
|
||||
return math_internal::IsFinite(vector);
|
||||
}
|
||||
|
||||
/// @brief Compares prescribed row identities without conflating node and row
|
||||
@@ -64,8 +65,8 @@ Result<ThermalAnalysisState> ThermalAnalysisState::Create(
|
||||
for (const auto node : state.node_order_)
|
||||
state.node_source_ids_.push_back(domain.Nodes()[node].source_id);
|
||||
for (std::size_t index = 0; index < state.element_order_.size(); ++index) {
|
||||
state.element_source_ids_.push_back(assembly.Kernels()[index].SourceId());
|
||||
const auto frame = assembly.Kernels()[index].Recover({});
|
||||
state.element_source_ids_.push_back(assembly.Elements()[index]->DofLayout().source_id);
|
||||
const auto frame = assembly.Elements()[index]->Recover({});
|
||||
if (!frame.HasValue())
|
||||
return Result<ThermalAnalysisState>::Failure(frame.GetStatus());
|
||||
state.local_frames_.push_back(frame.Value().frames);
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "fesa/assembly/thermal_assembler.h"
|
||||
#include "thermal_transient_bounds.h"
|
||||
#include "math/dense_blas_internal.h"
|
||||
#include "math/finite_values.h"
|
||||
|
||||
namespace fesa::thermal_transient_internal {
|
||||
namespace {
|
||||
@@ -35,9 +37,7 @@ Status Failure(const std::string& message) {
|
||||
}
|
||||
/// @brief Checks every scalar while preserving signed temperatures and powers.
|
||||
bool Finite(const Vector& vector) {
|
||||
for (std::size_t i = 0; i < vector.Size(); ++i)
|
||||
if (!std::isfinite(vector[i])) return false;
|
||||
return true;
|
||||
return math_internal::IsFinite(vector);
|
||||
}
|
||||
/// @brief Checks aligned full operators and their coefficient-error storage.
|
||||
bool SamePattern(const SparseMatrix& a, const SparseMatrix& b) {
|
||||
@@ -500,6 +500,16 @@ Status RunThermalHistory(const ThermalAssembly& assembly,
|
||||
const DofManager& dofs, ThermalAnalysisState& state,
|
||||
LinearSolver& solver, const ThermalRecovery& recovery,
|
||||
std::vector<ThermalStepEvidence>* evidence) {
|
||||
const BackwardEulerHeatIntegrator integrator;
|
||||
return RunThermalHistory(assembly, dofs, state, solver, integrator, recovery,
|
||||
evidence);
|
||||
}
|
||||
|
||||
Status RunThermalHistory(const ThermalAssembly& assembly,
|
||||
const DofManager& dofs, ThermalAnalysisState& state,
|
||||
LinearSolver& solver, const HeatStepIntegrator& integrator,
|
||||
const ThermalRecovery& recovery,
|
||||
std::vector<ThermalStepEvidence>* evidence) {
|
||||
if (!dofs.ValidateInvariants().IsOk() ||
|
||||
dofs.Profile() != DofProfile::kThermal ||
|
||||
dofs.FullDofCount() != state.CommittedState().temperature.Size() ||
|
||||
@@ -517,19 +527,21 @@ Status RunThermalHistory(const ThermalAssembly& assembly,
|
||||
i <= count; ++i) {
|
||||
const auto frame = state.TimeGrid().Frame(static_cast<std::uint32_t>(i));
|
||||
if (!frame.HasValue()) return frame.GetStatus();
|
||||
auto candidate = SolveThermalIncrement(system, partition,
|
||||
state.CommittedState().temperature,
|
||||
frame.Value().delta, solver, cache);
|
||||
if (!candidate.HasValue()) return candidate.GetStatus();
|
||||
auto flux = recovery(candidate.Value().temperature);
|
||||
if (!flux.HasValue()) return flux.GetStatus();
|
||||
auto completed = state.MakeCompletedFrame(
|
||||
static_cast<std::uint32_t>(i), std::move(candidate.Value().temperature),
|
||||
std::move(candidate.Value().reaction), std::move(flux.Value()));
|
||||
if (!completed.HasValue()) return completed.GetStatus();
|
||||
const auto committed = state.CommitAcceptedFrame(completed.Value());
|
||||
if (!committed.IsOk()) return committed;
|
||||
if (evidence) evidence->push_back(candidate.Value().evidence);
|
||||
const Status step_status = RunThermalStep(
|
||||
integrator, system, partition, state.CommittedState().temperature,
|
||||
frame.Value().delta, solver, cache, recovery,
|
||||
[&](ThermalStepCandidate&& candidate,
|
||||
std::vector<ThermalElementFlux>&& flux) -> Status {
|
||||
auto completed = state.MakeCompletedFrame(
|
||||
static_cast<std::uint32_t>(i), std::move(candidate.temperature),
|
||||
std::move(candidate.reaction), std::move(flux));
|
||||
if (!completed.HasValue()) return completed.GetStatus();
|
||||
const auto committed = state.CommitAcceptedFrame(completed.Value());
|
||||
if (!committed.IsOk()) return committed;
|
||||
if (evidence) evidence->push_back(candidate.evidence);
|
||||
return Status::Ok();
|
||||
});
|
||||
if (!step_status.IsOk()) return step_status;
|
||||
}
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
#include "fesa/analysis/thermal_analysis_state.h"
|
||||
#include "fesa/math/sparse_matrix.h"
|
||||
#include "fesa/solvers/linear/linear_solver.h"
|
||||
|
||||
namespace fesa::thermal_transient_internal {
|
||||
@@ -84,11 +86,52 @@ Result<ThermalStepCandidate> SolveThermalIncrement(
|
||||
const Vector& old_temperature, double delta, LinearSolver& solver,
|
||||
ThermalFactorization& factorization);
|
||||
|
||||
/// @brief Solves one thermal increment without committing accepted history.
|
||||
class HeatStepIntegrator {
|
||||
public:
|
||||
virtual ~HeatStepIntegrator() = default;
|
||||
virtual Result<ThermalStepCandidate>
|
||||
SolveIncrement(const ThermalSystem &system, const ThermalPartition &partition,
|
||||
const Vector &old_temperature, double delta,
|
||||
LinearSolver &solver,
|
||||
ThermalFactorization &factorization) const = 0;
|
||||
};
|
||||
|
||||
/// @brief Preserves backward Euler actual-delta and factorization reuse policy.
|
||||
class BackwardEulerHeatIntegrator final : public HeatStepIntegrator {
|
||||
public:
|
||||
Result<ThermalStepCandidate>
|
||||
SolveIncrement(const ThermalSystem &system, const ThermalPartition &partition,
|
||||
const Vector &old_temperature, double delta,
|
||||
LinearSolver &solver,
|
||||
ThermalFactorization &factorization) const override {
|
||||
return SolveThermalIncrement(system, partition, old_temperature, delta,
|
||||
solver, factorization);
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Provides an internal recovery seam for the actual history commit
|
||||
/// path.
|
||||
using ThermalRecovery = std::function<Result<std::vector<ThermalElementFlux>>(
|
||||
const Vector& temperature)>;
|
||||
|
||||
/// @brief Runs the actual solve/recovery/commit sequence without owning history.
|
||||
/// @note Commit receives a validated unpublished candidate only after recovery.
|
||||
template <typename Commit>
|
||||
Status RunThermalStep(const HeatStepIntegrator& integrator,
|
||||
const ThermalSystem& system,
|
||||
const ThermalPartition& partition,
|
||||
const Vector& old_temperature, double delta,
|
||||
LinearSolver& solver, ThermalFactorization& factorization,
|
||||
const ThermalRecovery& recovery, Commit&& commit) {
|
||||
auto candidate = integrator.SolveIncrement(system, partition, old_temperature,
|
||||
delta, solver, factorization);
|
||||
if (!candidate.HasValue()) return candidate.GetStatus();
|
||||
auto flux = recovery(candidate.Value().temperature);
|
||||
if (!flux.HasValue()) return flux.GetStatus();
|
||||
return commit(std::move(candidate.Value()), std::move(flux.Value()));
|
||||
}
|
||||
|
||||
/// @brief Advances an existing raw/accepted history, preserving it on every
|
||||
/// failure.
|
||||
/// @note Recovery runs after algebra validation and before atomic commit.
|
||||
@@ -101,5 +144,13 @@ Status RunThermalHistory(const ThermalAssembly& assembly,
|
||||
LinearSolver& solver, const ThermalRecovery& recovery,
|
||||
std::vector<ThermalStepEvidence>* evidence = nullptr);
|
||||
|
||||
/// @brief Borrows a caller-selected integrator for the complete history core.
|
||||
/// @note Integrator and other referenced adapters outlive the synchronous call.
|
||||
Status RunThermalHistory(const ThermalAssembly& assembly,
|
||||
const DofManager& dofs, ThermalAnalysisState& state,
|
||||
LinearSolver& solver, const HeatStepIntegrator& integrator,
|
||||
const ThermalRecovery& recovery,
|
||||
std::vector<ThermalStepEvidence>* evidence = nullptr);
|
||||
|
||||
} // namespace fesa::thermal_transient_internal
|
||||
#endif // FESA_ANALYSIS_THERMAL_TRANSIENT_ALGEBRA_H_
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "fesa/analysis/transient_heat_procedure.h"
|
||||
|
||||
#include <utility>
|
||||
#include "fesa/assembly/thermal_assembler.h"
|
||||
|
||||
#include "thermal_transient_algebra.h"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user