Connect MITC4 drilling evidence across analysis procedures

This commit is contained in:
KOKO\Mimi
2026-10-20 09:21:39 +09:00
parent 294ffbf8a1
commit c6188b3ad7
30 changed files with 763 additions and 28 deletions
+16
View File
@@ -1,5 +1,7 @@
#include "fesa/analysis/analysis_state.h"
#include "results/mitc4_drilling_evidence.h"
#include <algorithm>
#include <array>
#include <cmath>
@@ -167,9 +169,18 @@ Status AnalysisState::CommitShellResults(
"Shell energy, equilibrium, and normalized metrics must be finite.");
}
if (!results_internal::ValidDrillingEvidence(candidate.mitc4_drilling,
candidate.equilibrium) ||
(mitc4_drilling_ && !results_internal::SameDrillingInventory(
mitc4_drilling_, candidate.mitc4_drilling)))
return ShellCandidateFailure("invalid-shell-drilling-evidence",
identity_.step_name,
"Drilling evidence must be finite, consistent, and retain its mode.");
shell_results_ = std::move(candidate.rows);
physical_strain_energy_ = candidate.physical_strain_energy;
equilibrium_ = candidate.equilibrium;
mitc4_drilling_ = candidate.mitc4_drilling;
verification_metrics_ = candidate.verification_metrics;
return Status::Ok();
}
@@ -187,6 +198,11 @@ const std::array<double, 6>& AnalysisState::Equilibrium() const noexcept {
return equilibrium_;
}
const std::optional<Mitc4DrillingEvidence>& AnalysisState::Mitc4Drilling()
const noexcept {
return mitc4_drilling_;
}
const std::array<double, 3>& AnalysisState::VerificationMetrics()
const noexcept {
return verification_metrics_;
@@ -1,5 +1,7 @@
#include "fesa/analysis/dynamic_analysis_state.h"
#include "results/mitc4_drilling_evidence.h"
#include <cmath>
#include <limits>
#include <stdexcept>
@@ -273,6 +275,12 @@ Status DynamicAnalysisState::CommitAcceptedFrame(
return Failure(FailureCategory::kSolver,
"Frame physical evidence is nonfinite or has invalid size.");
}
if (!results_internal::ValidDrillingEvidence(frame.mitc4_drilling,
frame.global_equilibrium) ||
(!accepted_frames_.empty() && !results_internal::SameDrillingInventory(
frame.mitc4_drilling, accepted_frames_.back().mitc4_drilling)))
return Failure(FailureCategory::kSolver,
"Accepted drilling evidence must be finite, consistent, and retain its mode.");
for (const std::size_t dof : constrained_dofs_) {
if (frame.generalized_coordinates[dof] != 0.0 ||
frame.velocity[dof] != 0.0 || frame.acceleration[dof] != 0.0 ||
+14 -1
View File
@@ -64,6 +64,16 @@ bool Within(const double value, const double scale, const double tolerance) {
Status Validate(const Domain& domain, const DofManager& dofs,
const DynamicAssembly& assembly, const double length) {
const std::size_t count = dofs.FullDofCount();
if (assembly.frame.mitc4_drilling &&
assembly.frame.mitc4_drilling->mode == Mitc4DrillingMode::kDirectSpring) {
if (assembly.hidden_constraint.Size() != assembly.rotations.size())
return Failure(domain, "dynamic-invalid-algebra-state",
"Canonical hidden values must cover every node.");
for (std::size_t i = 0U; i < assembly.hidden_constraint.Size(); ++i)
if (!std::isfinite(assembly.hidden_constraint[i]))
return Failure(domain, "dynamic-invalid-algebra-state",
"Canonical hidden values must remain finite.");
}
if (!std::isfinite(length) || !(length > 0.0) || count % 6U != 0U ||
assembly.rotations.size() != count / 6U) {
return Failure(domain, "dynamic-invalid-algebra-state",
@@ -547,6 +557,8 @@ Status HiddenEvidence(const Domain& domain, const DynamicAssembly& assembly,
const SparseMatrix& gradient,
DynamicAcceptedFrameState* frame,
const bool require_acceleration) {
const bool direct = frame->mitc4_drilling &&
frame->mitc4_drilling->mode == Mitc4DrillingMode::kDirectSpring;
Vector psi{nodes.size()};
Vector psi_scale{nodes.size()};
Vector velocity_defect{nodes.size()};
@@ -575,9 +587,10 @@ Status HiddenEvidence(const Domain& domain, const DynamicAssembly& assembly,
const double g =
frame->internal_force[full] - frame->external_force[full];
const double n = rotation.drill_null[c];
psi[row] += n * g;
if (!direct) psi[row] += n * g;
psi_scale[row] += std::abs(n) * frame->residual_scale[full];
}
if (direct) psi[row] = assembly.hidden_constraint[node];
velocity_defect[row] += velocity_correction;
AddCompensated(assembly.hidden_second[node], &acceleration_defect[row],
&acceleration_correction);
+42 -2
View File
@@ -18,6 +18,7 @@
#include "fesa/elements/finite_rotation.h"
#include "fesa/loads/load.h"
#include "fesa/model/source_target_resolver.h"
#include "results/mitc4_drilling_evidence.h"
namespace fesa::hht_internal {
@@ -498,6 +499,10 @@ Result<DynamicAssembly> EvaluateAssembly(
internal[e] = std::move(f.Value());
inertia[e] = std::move(m.Value());
});
if (shells.empty())
return Result<DynamicAssembly>::Failure(Failure("HHT requires a shell kernel."));
frame.mitc4_drilling =
Mitc4DrillingEvidence{shells.front().internal_kernel->DrillingMode()};
frame.inertia_force = Vector(count);
frame.internal_force = Vector(count);
frame.residual = Vector(count);
@@ -515,6 +520,7 @@ Result<DynamicAssembly> EvaluateAssembly(
std::vector<std::map<std::size_t, double>>
constraint_gradients(domain.Nodes().size());
std::vector<bool> external_done(domain.Nodes().size(), false);
Vector hidden_constraint(directional_third ? domain.Nodes().size() : 0U);
Vector third(count);
Vector third_scale(count);
std::array<std::vector<CooContribution>, 6> entries;
@@ -532,9 +538,31 @@ Result<DynamicAssembly> EvaluateAssembly(
return Result<DynamicAssembly>::Failure(*failures[e]);
const auto& f = *internal[e];
const auto& m = *inertia[e];
if (shells[e].internal_kernel->DrillingMode() != frame.mitc4_drilling->mode ||
f.drilling_force.Size() != 24U)
return Result<DynamicAssembly>::Failure(Failure("Invalid actual drilling mode or force."));
for (std::size_t node = 0U; node < 4U; ++node) {
const auto local = 6U * node;
const auto global = shells[e].scatter[local];
const Vector3 force = Triple(f.drilling_force, local);
const auto spatial = SpatialMomentSO3(
Triple(frame.generalized_coordinates, global + 3U),
Triple(f.drilling_force, local + 3U));
if (!spatial.HasValue())
return Result<DynamicAssembly>::Failure(spatial.GetStatus());
const Vector3 position = Vector3(domain.Nodes()[global / 6U].coordinates) +
Triple(frame.generalized_coordinates, global);
const Vector3 moment = position.Cross(force) + spatial.Value();
for (std::size_t c = 0U; c < 3U; ++c)
frame.mitc4_drilling->drilling_moment[c] += moment[c];
}
for (const double value : frame.mitc4_drilling->drilling_moment)
if (!std::isfinite(value))
return Result<DynamicAssembly>::Failure(Failure("Drilling spatial moment overflowed."));
if (directional_third) {
for (std::size_t local = 0U; local < 4U; ++local) {
const auto node = shells[e].scatter[6U * local] / 6U;
hidden_constraint[node] += f.drilling_constraint[local];
auto& gradient = constraint_gradients[node];
auto& hessian = constraint_hessians[node];
for (std::size_t i = 0U; i < 24U; ++i)
@@ -555,6 +583,7 @@ Result<DynamicAssembly> EvaluateAssembly(
Triple(frame.external_force, first), {}, {});
if (!external.HasValue())
return Result<DynamicAssembly>::Failure(external.GetStatus());
hidden_constraint[node] += external.Value().value;
for (std::size_t i = 0U; i < 3U; ++i) {
gradient[first + i] += external.Value().gradient[i];
for (std::size_t j = 0U; j < 3U; ++j)
@@ -740,7 +769,8 @@ Result<DynamicAssembly> EvaluateAssembly(
std::move(third), std::move(third_scale), std::move(rotations),
momentum_scale, angular_scale, std::move(hidden_second),
std::move(hidden_second_scale), std::move(hidden_gradient),
std::move(trial_constraints), std::move(field_flux)});
std::move(trial_constraints), std::move(field_flux),
std::move(hidden_constraint)});
}
Status CompleteEquilibrium(const Domain& domain, const DofManager& dofs,
@@ -776,10 +806,20 @@ Status CompleteEquilibrium(const Domain& domain, const DofManager& dofs,
frame->global_equilibrium[c] = force_error[c];
frame->global_equilibrium[3U + c] = moment_error[c];
}
Vector3 acceptance_moment = moment_error;
if (frame->mitc4_drilling) {
results_internal::CompleteDrillingEvidence(frame->global_equilibrium,
&*frame->mitc4_drilling);
if (!results_internal::ValidDrillingEvidence(frame->mitc4_drilling,
frame->global_equilibrium))
return Failure("Invalid dynamic drilling equilibrium evidence.");
if (frame->mitc4_drilling->mode == Mitc4DrillingMode::kDirectSpring)
acceptance_moment = moment_error - Vector3(frame->mitc4_drilling->drilling_moment);
}
if (!force_error.IsFinite() || !moment_error.IsFinite() ||
!std::isfinite(force_scale) || !std::isfinite(moment_scale) ||
Ratio(force_error.Norm(), force_scale) > 1e-8 ||
Ratio(moment_error.Norm(), moment_scale) > 1e-8)
Ratio(acceptance_moment.Norm(), moment_scale) > 1e-8)
return Failure("Dynamic physical force or moment balance exceeds 1e-8.");
return Status::Ok();
}
+3
View File
@@ -97,6 +97,9 @@ struct DynamicAssembly {
std::optional<SparseMatrix> hidden_gradient;
std::optional<TrialConstraintData> trial_constraints;
std::array<std::vector<hht_field_convergence::FluxOccurrence>, 2> field_flux;
// Source-ordered scalar primitive, including the external term exactly once.
// Empty when directional primitives were not evaluated; never a zero fallback.
Vector hidden_constraint{0U};
};
/// @brief Builds checked shell evaluators in stable source order.
+2 -1
View File
@@ -82,7 +82,8 @@ const hht_internal::DirectionalShellConstraintData* MatchingConstraints(
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)
record.configuration.identity != configuration.identity ||
record.configuration.drilling_mode != configuration.drilling_mode)
return nullptr;
for (std::size_t i = 0U; i < configuration.values.size(); ++i)
if (!std::isfinite(record.configuration.values[i]) ||
@@ -217,6 +217,13 @@ Result<EigenAnalysisState> ExecuteCandidate(const Domain& domain,
if (!backend.HasValue())
return Result<EigenAnalysisState>::Failure(backend.GetStatus());
EigenAnalysisState state(std::move(backend.Value()));
for (const auto& reference : references) {
if (state.drilling_mode && *state.drilling_mode != reference->DrillingMode())
return Result<EigenAnalysisState>::Failure(Failure(
"invalid-shell-drilling-evidence", "mode",
"All frequency kernels must retain one actual drilling mode."));
state.drilling_mode = reference->DrillingMode();
}
state.full_dof_count = dofs.FullDofCount();
state.free_dof_count = reduction.Value().free_count;
state.mass_null_dimension = reduction.Value().null_count;
@@ -12,6 +12,7 @@
#include "fesa/elements/element_definition.h"
#include "math/finite_values.h"
#include "results/shell_result_recovery.h"
#include "results/mitc4_drilling_evidence.h"
#include "analysis/shell_state_inventory.h"
namespace fesa {
@@ -110,6 +111,13 @@ Status ValidateAcceptedFrame(const AcceptedFrameState& frame,
"Accepted frame physical result inventory is invalid.");
}
if (!results_internal::ValidDrillingEvidence(frame.mitc4_drilling,
frame.global_equilibrium) ||
(accepted_frame_count != 0U && !results_internal::SameDrillingInventory(
frame.mitc4_drilling, committed.mitc4_drilling)))
return StateFailure(FailureCategory::kSolver, "invalid-shell-drilling-evidence", {},
"Accepted drilling evidence must be finite and retain its mode.");
const double gauss = 1.0 / std::sqrt(3.0);
const std::array<ShellMidsurfaceLocation, kShellLocationsPerElement>
expected_locations{ShellMidsurfaceLocation::kGp1,
@@ -243,7 +251,7 @@ Status NonlinearAnalysisState::CommitAcceptedFrame(
frame.reaction,
frame.shell_results,
frame.physical_strain_energy,
frame.global_equilibrium};
frame.global_equilibrium, frame.mitc4_drilling};
std::vector<AcceptedFrameState> accepted_candidate = accepted_frames_;
accepted_candidate.push_back(frame);
@@ -33,6 +33,7 @@
#include "assembly/ordered_contribution_internal.h"
#include "math/finite_values.h"
#include "elements/shell_kernel_values.h"
#include "results/mitc4_drilling_evidence.h"
namespace fesa {
namespace {
@@ -57,6 +58,7 @@ struct InternalAssembly {
SparseMatrix tangent;
std::vector<ShellResultRow> shell_rows;
double physical_strain_energy;
Mitc4DrillingEvidence mitc4_drilling;
};
Status ProcedureFailure(const FailureCategory category, const std::string& code,
@@ -175,6 +177,10 @@ Result<NonlinearStateBaselineInput> BuildBaseline(const Domain& domain) {
Result<std::vector<RuntimeShell>> BuildRuntimeShells(const Domain& domain,
const DofManager& dofs) {
std::vector<RuntimeShell> shells;
if (domain.Elements().Empty())
return Result<std::vector<RuntimeShell>>::Failure(ProcedureFailure(
FailureCategory::kModel, "unsupported-nonlinear-element", {}, "ELEMENT",
{}, "The nonlinear static procedure requires a MITC4 shell element."));
shells.reserve(domain.Elements().Size());
for (std::size_t index = 0U; index < domain.Elements().Size(); ++index) {
const auto* definition =
@@ -320,7 +326,7 @@ NonlinearMitc4TrialDofs ExtractElementTrial(
Result<InternalAssembly> AssembleInternal(
const std::vector<RuntimeShell>& shells, const DofManager& dofs,
const Vector& coordinates, const ParallelFor& parallel_for) {
const Domain& domain, const Vector& coordinates, const ParallelFor& parallel_for) {
std::vector<std::optional<NonlinearMitc4InternalEquilibrium>> local(
shells.size());
std::vector<std::optional<Status>> failures(shells.size());
@@ -341,6 +347,7 @@ Result<InternalAssembly> AssembleInternal(
std::vector<ShellResultRow> rows;
rows.reserve(shells.size() * 4U);
double energy = 0.0;
Mitc4DrillingEvidence drilling{shells.front().kernel->DrillingMode()};
for (std::size_t index = 0U; index < shells.size(); ++index) {
if (failures[index].has_value()) {
return Result<InternalAssembly>::Failure(*failures[index]);
@@ -354,6 +361,28 @@ Result<InternalAssembly> AssembleInternal(
"NONLINEAR_STATIC_PROCEDURE", std::to_string(index),
"A nonlinear shell internal residual or tangent is nonfinite."));
}
if (shells[index].kernel->DrillingMode() != drilling.mode ||
local[index]->drilling_force.Size() != kShellDofCount ||
!IsFinite(local[index]->drilling_force))
return Result<InternalAssembly>::Failure(ProcedureFailure(
FailureCategory::kSolver, "invalid-shell-drilling-evidence", {},
"NONLINEAR_STATIC_PROCEDURE", {}, "Drilling mode or force is invalid."));
const auto element_trial = ExtractElementTrial(shells[index], coordinates);
for (std::size_t node = 0U; node < 4U; ++node) {
const auto first = 6U * node;
const auto global_node = shells[index].scatter[first] / 6U;
const auto& values = local[index]->drilling_force;
const Vector3 drilling_force{values[first], values[first + 1U], values[first + 2U]};
const auto spatial = SpatialMomentSO3(element_trial.rotation_vector[node],
{values[first + 3U], values[first + 4U], values[first + 5U]});
if (!spatial.HasValue())
return Result<InternalAssembly>::Failure(spatial.GetStatus());
const Vector3 position = Vector3(domain.Nodes()[global_node].coordinates) +
element_trial.displacement[node];
const Vector3 moment = position.Cross(drilling_force) + spatial.Value();
for (std::size_t c = 0U; c < 3U; ++c)
drilling.drilling_moment[c] += moment[c];
}
for (std::size_t row = 0U; row < kShellDofCount; ++row) {
force[shells[index].scatter[row]] += local[index]->internal_residual[row];
assembly_internal::AppendLocalMatrixRow(
@@ -373,7 +402,8 @@ Result<InternalAssembly> AssembleInternal(
}
energy += local[index]->physical_strain_energy;
}
if (!IsFinite(force) || !std::isfinite(energy)) {
if (!IsFinite(force) || !std::isfinite(energy) ||
!IsFinite(drilling.drilling_moment)) {
return Result<InternalAssembly>::Failure(ProcedureFailure(
FailureCategory::kSolver, "nonfinite-newton-data", {},
"NONLINEAR_STATIC_PROCEDURE", {},
@@ -387,7 +417,7 @@ Result<InternalAssembly> AssembleInternal(
return Result<InternalAssembly>::Failure(tangent.GetStatus());
}
return Result<InternalAssembly>::Success(
{std::move(force), std::move(tangent.Value()), std::move(rows), energy});
{std::move(force), std::move(tangent.Value()), std::move(rows), energy, drilling});
}
Result<Vector> Difference(const Vector& left, const Vector& right) {
@@ -699,7 +729,7 @@ AcceptedFrameState AcceptedFrame(const std::uint32_t frame_index,
trial.reaction,
trial.shell_results,
trial.physical_strain_energy,
trial.global_equilibrium};
trial.global_equilibrium, trial.mitc4_drilling};
}
} // namespace
@@ -807,7 +837,7 @@ Result<NonlinearStaticExecutionResult> ExecuteNonlinearStaticCore(
NewtonIterationStage::kKinematicsEvaluated;
Result<InternalAssembly> internal = AssembleInternal(
shells.Value(), dofs.Value(),
shells.Value(), dofs.Value(), domain,
trial.Value().generalized_coordinates, parallel_for);
if (!internal.HasValue()) {
return Result<std::optional<ShellStepCandidate>>::Failure(
@@ -879,6 +909,9 @@ Result<NonlinearStaticExecutionResult> ExecuteNonlinearStaticCore(
NewtonIterationStage::kExternalAssembled));
}
trial.Value().global_equilibrium = global_equilibrium.Value();
trial.Value().mitc4_drilling = internal.Value().mitc4_drilling;
results_internal::CompleteDrillingEvidence(
trial.Value().global_equilibrium, &*trial.Value().mitc4_drilling);
Result<PartitionedStiffness> partition =
constraints.Partition(tangent.Value(), dofs.Value());