feat(analysis): implement Hex8 solid static procedures
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
#include "fesa/analysis/solid_static_state.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
#include "fesa/elements/element_factory.h"
|
||||
#include "fesa/elements/hex8_element.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
/// @brief Keeps state/control failures separate from input syntax failures.
|
||||
Status StateFailure(const char* code, const char* message) {
|
||||
return Status::Failure(
|
||||
FailureCategory::kSolver,
|
||||
{{Severity::kError, code, {}, "SOLID_STATIC", {}, message}});
|
||||
}
|
||||
/// @brief Validates all components without a zero clamp or scale floor.
|
||||
template <class Range>
|
||||
bool Finite(const Range& values) {
|
||||
for (double value : values)
|
||||
if (!std::isfinite(value)) return false;
|
||||
return true;
|
||||
}
|
||||
/// @brief Validates owned vectors including their physical full-space size.
|
||||
bool FiniteVector(const Vector& values, std::size_t size) {
|
||||
if (values.Size() != size) return false;
|
||||
for (std::size_t i = 0U; i < size; ++i)
|
||||
if (!std::isfinite(values[i])) return false;
|
||||
return true;
|
||||
}
|
||||
/// @brief Calculates the infinity norm after separate finite validation.
|
||||
double InfinityNorm(const Vector& values) {
|
||||
double norm = 0.0;
|
||||
for (std::size_t i = 0U; i < values.Size(); ++i)
|
||||
norm = (std::max)(norm, std::abs(values[i]));
|
||||
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;
|
||||
}
|
||||
/// @brief Builds one raw criterion with the approved branch and physical unit.
|
||||
NewtonChannelDecision Decision(const char* name, double numerator,
|
||||
double denominator, NewtonChannelUnit unit) {
|
||||
const bool absolute = denominator == 0.0;
|
||||
const double threshold =
|
||||
absolute ? kNewtonZeroScaleThreshold : kNewtonRelativeThreshold;
|
||||
return {
|
||||
name,
|
||||
numerator,
|
||||
denominator,
|
||||
absolute ? NewtonConvergenceBranch::kZeroScale
|
||||
: NewtonConvergenceBranch::kRelative,
|
||||
threshold,
|
||||
absolute ? unit : NewtonChannelUnit::kDimensionless,
|
||||
absolute ? numerator <= threshold : numerator / denominator <= threshold};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Result<SolidStaticIterationRecord> EvaluateSolidStaticConvergence(
|
||||
const Vector& residual, const Vector& internal, const Vector& external,
|
||||
const Vector& correction, const Vector& increment) {
|
||||
const std::size_t size = residual.Size();
|
||||
if (!FiniteVector(residual, size) || !FiniteVector(internal, size) ||
|
||||
!FiniteVector(external, size) || !FiniteVector(correction, size) ||
|
||||
!FiniteVector(increment, size))
|
||||
return Result<SolidStaticIterationRecord>::Failure(StateFailure(
|
||||
"nonfinite-solid-newton-data",
|
||||
"Static channels require finite vectors of the same free dimension."));
|
||||
const double external_norm = InfinityNorm(external);
|
||||
const double force_scale =
|
||||
external_norm == 0.0 ? 0.0
|
||||
: (std::max)(external_norm, InfinityNorm(internal));
|
||||
SolidStaticIterationRecord record;
|
||||
record.force_residual = Decision("residual_force", InfinityNorm(residual),
|
||||
force_scale, NewtonChannelUnit::kForce);
|
||||
record.translation_correction =
|
||||
Decision("correction_translation", InfinityNorm(correction),
|
||||
InfinityNorm(increment), NewtonChannelUnit::kLength);
|
||||
record.convergence_evaluated = true;
|
||||
return Result<SolidStaticIterationRecord>::Success(std::move(record));
|
||||
}
|
||||
|
||||
Result<SolidStaticState> SolidStaticState::Create(const Domain& domain,
|
||||
const DofManager& dofs) {
|
||||
if (!domain.IsSolid() || domain.Steps().Size() != 1U ||
|
||||
domain.Frequency() != nullptr || domain.Dynamic() != nullptr ||
|
||||
dofs.Profile() != DofProfile::kSolidStructural ||
|
||||
dofs.ComponentsPerNode() != 3U ||
|
||||
dofs.FullDofCount() != 3U * domain.Nodes().size())
|
||||
return Result<SolidStaticState>::Failure(
|
||||
StateFailure("invalid-solid-static-state",
|
||||
"Static state requires one Solid static step and matching "
|
||||
"3N mapping."));
|
||||
const Status mapping = dofs.ValidateInvariants();
|
||||
if (!mapping.IsOk()) return Result<SolidStaticState>::Failure(mapping);
|
||||
SolidStaticState state;
|
||||
state.initialized_ = true;
|
||||
state.nonlinear_ = domain.Steps()[0].IsNonlinearStatic();
|
||||
state.constrained_dofs_ = dofs.ConstrainedDofs();
|
||||
state.prescribed_values_ = dofs.PrescribedValues();
|
||||
const std::size_t count = dofs.FullDofCount();
|
||||
state.committed_.displacement = Vector{count};
|
||||
state.committed_.internal_force = Vector{count};
|
||||
state.committed_.external_force = Vector{count};
|
||||
state.committed_.residual = Vector{count};
|
||||
state.committed_.reaction = Vector{count};
|
||||
for (std::size_t node = 0U; node < domain.Nodes().size(); ++node) {
|
||||
state.committed_.nodal_internal_order.push_back(
|
||||
static_cast<EntityIndex>(node));
|
||||
state.committed_.nodal_source_ids.push_back(domain.Nodes()[node].source_id);
|
||||
}
|
||||
// Capture the immutable point coordinates from certified zero-state kernels.
|
||||
// Later state commits cannot substitute another element or result location.
|
||||
for (std::size_t e = 0U; e < domain.Elements().Size(); ++e) {
|
||||
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());
|
||||
if (hex == nullptr)
|
||||
return Result<SolidStaticState>::Failure(StateFailure(
|
||||
"invalid-solid-static-state", "Static state requires Hex8 kernels."));
|
||||
auto response = hex->EvaluateLinear(Vector{24U});
|
||||
if (!response.HasValue())
|
||||
return Result<SolidStaticState>::Failure(response.GetStatus());
|
||||
state.reference_points_.insert(state.reference_points_.end(),
|
||||
response.Value().points.begin(),
|
||||
response.Value().points.end());
|
||||
}
|
||||
return Result<SolidStaticState>::Success(std::move(state));
|
||||
}
|
||||
|
||||
Status SolidStaticState::CommitAcceptedFrame(const SolidStaticFrame& frame) {
|
||||
const auto invalid = []() {
|
||||
return StateFailure(
|
||||
"invalid-solid-static-frame",
|
||||
"Static candidate identity, projection or recovery is invalid.");
|
||||
};
|
||||
if (!initialized_ || frame.frame_index != accepted_frames_.size() ||
|
||||
frame.increment_index == 0U || !std::isfinite(frame.load_factor) ||
|
||||
!(frame.load_factor > committed_.load_factor) ||
|
||||
frame.load_factor > 1.0 ||
|
||||
frame.is_factor_one != (frame.load_factor == 1.0) ||
|
||||
(nonlinear_ &&
|
||||
(frame.increment_index != committed_.increment_index + 1U ||
|
||||
frame.iteration_count == 0U || frame.iteration_count > 100U)) ||
|
||||
(!nonlinear_ && (!accepted_frames_.empty() || frame.load_factor != 1.0 ||
|
||||
frame.iteration_count != 0U)))
|
||||
return invalid();
|
||||
const std::size_t count = committed_.displacement.Size();
|
||||
if (frame.nodal_internal_order != committed_.nodal_internal_order ||
|
||||
frame.nodal_source_ids.size() != committed_.nodal_source_ids.size() ||
|
||||
!FiniteVector(frame.displacement, count) ||
|
||||
!FiniteVector(frame.internal_force, count) ||
|
||||
!FiniteVector(frame.external_force, count) ||
|
||||
!FiniteVector(frame.residual, count) ||
|
||||
!FiniteVector(frame.reaction, count) ||
|
||||
frame.solid_results.size() != reference_points_.size() ||
|
||||
!std::isfinite(frame.physical_strain_energy) ||
|
||||
!Finite(frame.global_equilibrium) ||
|
||||
!Finite(frame.balance.internal_force_sum) ||
|
||||
!Finite(frame.balance.reference_internal_moment) ||
|
||||
!Finite(frame.balance.current_internal_moment) ||
|
||||
!Finite(frame.balance.free_residual_moment) ||
|
||||
!Finite(frame.balance.linearization_moment) ||
|
||||
!Finite(frame.balance.internal_force_scale) ||
|
||||
!Finite(frame.balance.reference_moment_scale) ||
|
||||
!Finite(frame.balance.current_moment_scale))
|
||||
return invalid();
|
||||
for (std::size_t node = 0U; node < frame.nodal_source_ids.size(); ++node)
|
||||
if (!SameIdentity(frame.nodal_source_ids[node],
|
||||
committed_.nodal_source_ids[node]))
|
||||
return invalid();
|
||||
std::vector<bool> constrained(count, false);
|
||||
for (std::size_t c = 0U; c < constrained_dofs_.size(); ++c) {
|
||||
const auto full = constrained_dofs_[c];
|
||||
constrained[full] = true;
|
||||
if (frame.displacement[full] != frame.load_factor * prescribed_values_[c])
|
||||
return invalid();
|
||||
}
|
||||
for (std::size_t i = 0U; i < count; ++i)
|
||||
if (frame.residual[i] !=
|
||||
frame.internal_force[i] - frame.external_force[i] ||
|
||||
frame.reaction[i] != (constrained[i] ? frame.residual[i] : 0.0))
|
||||
return invalid();
|
||||
for (std::size_t g = 0U; g < reference_points_.size(); ++g) {
|
||||
const auto& row = frame.solid_results[g];
|
||||
const auto& reference = reference_points_[g];
|
||||
if (row.element != reference.element ||
|
||||
row.integration_point != reference.integration_point ||
|
||||
row.natural_coordinates != reference.natural_coordinates ||
|
||||
row.reference_coordinates != reference.reference_coordinates ||
|
||||
!Finite(row.cauchy_stress) || !Finite(row.strain))
|
||||
return invalid();
|
||||
}
|
||||
// Prepare both copies before mutating committed history.
|
||||
SolidStaticFrame next = frame;
|
||||
accepted_frames_.push_back(frame);
|
||||
committed_ = std::move(next);
|
||||
return Status::Ok();
|
||||
}
|
||||
const std::vector<SolidStaticFrame>& SolidStaticState::AcceptedFrames()
|
||||
const noexcept {
|
||||
return accepted_frames_;
|
||||
}
|
||||
const SolidStaticFrame& SolidStaticState::CommittedState() const noexcept {
|
||||
return committed_;
|
||||
}
|
||||
const std::vector<SolidStaticIterationRecord>&
|
||||
SolidStaticState::IterationRecords() const noexcept {
|
||||
return iterations_;
|
||||
}
|
||||
} // namespace fesa
|
||||
Reference in New Issue
Block a user