Files
FESADev/src/fesa/analysis/solid_static_state.cpp
T

217 lines
9.6 KiB
C++

#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