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
@@ -17,9 +17,12 @@
#include "fesa/assembly/parallel_for.h"
#include "fesa/assembly/sparse_assembler.h"
#include "fesa/elements/element.h"
#include "fesa/elements/element_factory.h"
#include "fesa/elements/mitc4_shell.h"
#include "fesa/fem/dof_manager.h"
#include "fesa/math/vector3.h"
#include "fesa/model/domain.h"
#include "fesa/results/result_recovery.h"
#include "results/analysis_state_commit.h"
#include "results/beam_result_recovery.h"
#include "results/global_equilibrium_recovery.h"
@@ -627,3 +630,99 @@ TEST(ResultRecoveryComponents,
ExpectStatusCode(status, "nonfinite-recovery-value");
ExpectAnalysisStateEqual(state, prior);
}
TEST(Mitc4DrillingEvidence, LinearDirectSpringAcceptsKnownArtificialTorque) {
auto definition = MakeShellDefinition();
for (std::size_t node = 0U; node < 4U; ++node) {
definition.steps[0U].boundaries.push_back(
{std::to_string(node + 1U), 1, 5, 0.0, {definition.source_path, 51U}});
definition.steps[0U].boundaries.push_back(
{std::to_string(node + 1U), 6, 6, 0.125, {definition.source_path, 52U}});
}
const auto fixture = MakeShellFixture(std::move(definition));
auto state = fesa::AnalysisState::Create(*fixture.dofs, {"Step-1", 0U});
for (std::size_t node = 0U; node < 4U; ++node)
state.Displacement()[6U * node + 5U] = 0.125;
const auto status = fesa::ResultRecovery::Recover(
*fixture.model, *fixture.dofs, *fixture.stiffness, state);
ASSERT_TRUE(status.IsOk()) << (status.Diagnostics().empty() ? "" : status.Diagnostics()[0U].message);
}
TEST(Mitc4DrillingEvidence, LinearTorqueOracleAndSignedGateKeepRealImbalance) {
auto definition = MakeShellDefinition();
for (std::size_t node = 0U; node < 4U; ++node) {
definition.steps[0U].boundaries.push_back(
{std::to_string(node + 1U), 1, 5, 0.0, {definition.source_path, 51U}});
definition.steps[0U].boundaries.push_back(
{std::to_string(node + 1U), 6, 6, 0.125, {definition.source_path, 52U}});
}
const auto fixture = MakeShellFixture(std::move(definition));
auto element = fesa::ElementFactory{}.Create(fixture.domain->Elements()[0U],
*fixture.domain);
ASSERT_TRUE(element.HasValue());
const auto* shell = dynamic_cast<const fesa::Mitc4Shell*>(element.Value().get());
ASSERT_NE(shell, nullptr);
const auto stiffness = shell->Stiffness();
ASSERT_TRUE(stiffness.HasValue());
double rotary_trace = 0.0;
for (std::size_t node = 0U; node < 4U; ++node)
for (std::size_t c = 3U; c < 5U; ++c)
rotary_trace += stiffness.Value().physical_local20(5U * node + c, 5U * node + c);
const double expected_torque = 4.0 * 0.125 * (1e-3 * rotary_trace / 8.0);
auto state = fesa::AnalysisState::Create(*fixture.dofs, {"Step-1", 0U});
for (std::size_t node = 0U; node < 4U; ++node)
state.Displacement()[6U * node + 5U] = 0.125;
ASSERT_TRUE(fesa::ResultRecovery::Recover(*fixture.model, *fixture.dofs,
*fixture.stiffness, state).IsOk());
ASSERT_TRUE(state.Mitc4Drilling());
EXPECT_EQ(state.Mitc4Drilling()->mode, fesa::Mitc4DrillingMode::kDirectSpring);
EXPECT_NEAR(state.Equilibrium()[5U], expected_torque, 1e-14);
EXPECT_NEAR(state.Mitc4Drilling()->drilling_moment[2U], expected_torque, 1e-14);
EXPECT_DOUBLE_EQ(state.Mitc4Drilling()->drilling_moment[0U], 0.0);
EXPECT_DOUBLE_EQ(state.Mitc4Drilling()->drilling_moment[1U], 0.0);
for (double value : state.Mitc4Drilling()->equilibrium_with_drilling)
EXPECT_NEAR(value, 0.0, 1e-14);
EXPECT_DOUBLE_EQ(state.PhysicalStrainEnergy(), 0.0);
auto global = fesa::results_internal::GlobalEquilibriumRecovery(
*fixture.domain, *fixture.dofs, *fixture.stiffness, state.Displacement(),
state.ExternalForce());
ASSERT_TRUE(global.HasValue());
for (const int kind : {0, 1, 2, 3}) {
auto changed = global.Value();
fesa::results_internal::RecoveryCandidate candidate{24U};
candidate.shell.mitc4_drilling = *state.Mitc4Drilling();
if (kind == 0) candidate.shell.mitc4_drilling->drilling_moment[2U] *= -1.0;
if (kind == 1) changed.residual[0U] += 1.0;
if (kind == 2) changed.residual[4U] += 1.0;
if (kind == 3) candidate.shell.mitc4_drilling->mode =
fesa::Mitc4DrillingMode::kLegacyCorotatedTwist;
const auto rejected = fesa::results_internal::RecoverShellGlobalEvidence(
*fixture.domain, *fixture.dofs, state.ExternalForce(), changed, candidate);
EXPECT_FALSE(rejected.IsOk()) << kind;
}
}
TEST(Mitc4DrillingEvidence, StaticCommitRejectsInvalidEvidenceAtomically) {
const auto fixture = MakeShellFixture(MakeShellDefinition());
auto state = fesa::AnalysisState::Create(*fixture.dofs, {"Step-1", 0U});
fesa::ShellStateCandidate candidate{};
candidate.rows = MakeShellRows(0U, 1.0).rows;
candidate.mitc4_drilling = fesa::Mitc4DrillingEvidence{
fesa::Mitc4DrillingMode::kDirectSpring, {0.0, 0.0, 2.0}, {0, 0, 0, 0, 0, -2}};
ASSERT_TRUE(state.CommitShellResults({0U}, candidate).IsOk());
for (const int kind : {0, 1, 2, 3, 4}) {
auto invalid = candidate;
invalid.physical_strain_energy = 77.0;
if (kind == 0) invalid.mitc4_drilling->mode = static_cast<fesa::Mitc4DrillingMode>(99);
if (kind == 1) invalid.mitc4_drilling->mode = fesa::Mitc4DrillingMode::kLegacyCorotatedTwist;
if (kind == 2) invalid.mitc4_drilling->drilling_moment[0U] = std::numeric_limits<double>::infinity();
if (kind == 3) invalid.mitc4_drilling->equilibrium_with_drilling[5U] = 2.0;
if (kind == 4) invalid.mitc4_drilling.reset();
EXPECT_FALSE(state.CommitShellResults({0U}, invalid).IsOk()) << kind;
EXPECT_DOUBLE_EQ(state.PhysicalStrainEnergy(), 0.0);
ASSERT_TRUE(state.Mitc4Drilling());
EXPECT_EQ(state.Mitc4Drilling()->mode, candidate.mitc4_drilling->mode);
EXPECT_EQ(state.Mitc4Drilling()->drilling_moment, candidate.mitc4_drilling->drilling_moment);
}
}