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
@@ -320,3 +320,27 @@ TEST(DynamicAnalysisState, CreateRejectsInvalidScheduleAndNonzeroBoundary) {
}
} // namespace
TEST(DynamicAnalysisState, DrillingEvidenceRejectsPartialCommitAndModeChanges) {
auto fixture = MakeFixture();
auto initial = MakeFrame(*fixture.domain, 0U);
initial.mitc4_drilling = fesa::Mitc4DrillingEvidence{fesa::Mitc4DrillingMode::kDirectSpring};
ASSERT_TRUE(fixture.state.CommitAcceptedFrame(initial).IsOk());
for (const int kind : {0, 1, 2, 3, 4}) {
auto next = MakeFrame(*fixture.domain, 1U);
next.mitc4_drilling = initial.mitc4_drilling;
if (kind == 0) next.mitc4_drilling.reset();
if (kind == 1) next.mitc4_drilling->mode = fesa::Mitc4DrillingMode::kLegacyCorotatedTwist;
if (kind == 2) next.mitc4_drilling->mode = static_cast<fesa::Mitc4DrillingMode>(99);
if (kind == 3) next.mitc4_drilling->equilibrium_with_drilling[3U] = 1.0;
if (kind == 4) next.mitc4_drilling->drilling_moment[0U] = std::numeric_limits<double>::quiet_NaN();
EXPECT_FALSE(fixture.state.CommitAcceptedFrame(next).IsOk()) << kind;
ASSERT_EQ(fixture.state.AcceptedFrames().size(), 1U);
EXPECT_EQ(fixture.state.CommittedState().frame_index, 0U);
EXPECT_EQ(fixture.state.CommittedState().mitc4_drilling->mode,
fesa::Mitc4DrillingMode::kDirectSpring);
}
auto valid = MakeFrame(*fixture.domain, 1U);
valid.mitc4_drilling = initial.mitc4_drilling;
EXPECT_TRUE(fixture.state.CommitAcceptedFrame(valid).IsOk());
}
@@ -216,6 +216,12 @@ void ExpectSameDynamicHistory(const RigidTranslationRun& left,
EXPECT_DOUBLE_EQ(lhs.drilling_energy, rhs.drilling_energy);
EXPECT_DOUBLE_EQ(lhs.external_work, rhs.external_work);
EXPECT_DOUBLE_EQ(lhs.energy_defect, rhs.energy_defect);
ASSERT_TRUE(lhs.mitc4_drilling);
ASSERT_TRUE(rhs.mitc4_drilling);
EXPECT_EQ(lhs.mitc4_drilling->mode, rhs.mitc4_drilling->mode);
EXPECT_EQ(lhs.mitc4_drilling->drilling_moment, rhs.mitc4_drilling->drilling_moment);
EXPECT_EQ(lhs.mitc4_drilling->equilibrium_with_drilling,
rhs.mitc4_drilling->equilibrium_with_drilling);
}
}
@@ -357,6 +363,10 @@ TEST(HhtDynamicProcedure, ClampedStepLoadHasPhysicalReactionAndZeroMotion) {
EXPECT_EQ(frame.time_integration_formulation,
fesa::DynamicTimeIntegrationFormulation::kReducedCoordinateHht);
EXPECT_EQ(frame.iteration_count, 0U);
ASSERT_TRUE(frame.mitc4_drilling);
EXPECT_EQ(frame.mitc4_drilling->mode, fesa::Mitc4DrillingMode::kDirectSpring);
EXPECT_EQ(frame.mitc4_drilling->drilling_moment, (std::array<double, 3>{}));
EXPECT_EQ(frame.mitc4_drilling->equilibrium_with_drilling, frame.global_equilibrium);
EXPECT_DOUBLE_EQ(frame.reaction[2U], -4.0);
EXPECT_DOUBLE_EQ(frame.external_force[2U], 4.0);
EXPECT_DOUBLE_EQ(frame.kinetic_energy, 0.0);
+212 -10
View File
@@ -22,10 +22,13 @@
#include "analysis/hht_reduced_internal.h"
#include "math/dense_blas_internal.h"
#include "fesa/elements/mitc4_shell.h"
#include "elements/mitc4_drilling_internal.h"
#include "fesa/analysis/analysis_model.h"
#include "fesa/analysis/hht_time_integration.h"
#include "fesa/assembly/parallel_for.h"
#include "fesa/solvers/linear/mkl_pardiso_tangent_solver.h"
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
namespace {
@@ -625,6 +628,10 @@ TEST(HhtReducedOperators,
[](Assembly& a) {
a.trial_constraints->shells[0U].configuration.identity += "x";
},
[](Assembly& a) {
a.trial_constraints->shells[0U].configuration.drilling_mode =
fesa::Mitc4DrillingMode::kLegacyCorotatedTwist;
},
[](Assembly& a) { a.trial_constraints->shells[0U].q[0U] = -0.0; },
[](Assembly& a) { a.trial_constraints->shells[0U].v[0U] = -0.0; },
[](Assembly& a) {
@@ -935,11 +942,15 @@ TEST(HhtReducedOperators, NonfiniteMatchingTimeKeyMissPreservesFreshSuccess) {
}
}
TEST(HhtReducedOperators, ScatterShellDerivativesByConstraintRow) {
void CheckShellConstraintScatter(const fesa::Mitc4DrillingMode mode) {
const auto fixture = MakeFixture(false);
const auto shells = fesa::hht_internal::BuildRuntimeShells(*fixture.domain,
fixture.dofs);
ASSERT_TRUE(shells.HasValue());
SCOPED_TRACE(static_cast<int>(mode));
for (const auto& shell : shells.Value())
*shell.internal_kernel = fesa::mitc4_drilling_internal::TestAccess::WithMode(
*shell.internal_kernel, mode);
fesa::DynamicAcceptedFrameState frame{};
for (auto* values : {&frame.generalized_coordinates, &frame.velocity,
&frame.acceleration, &frame.algorithmic_acceleration,
@@ -980,7 +991,10 @@ TEST(HhtReducedOperators, ScatterShellDerivativesByConstraintRow) {
assembly.Value().frame.residual_scale[6U * node + 3U + component];
EXPECT_DOUBLE_EQ(row.value_scale, expected_scale);
EXPECT_FALSE(row.gradient.empty());
EXPECT_FALSE(row.hessian.empty());
if (mode == fesa::Mitc4DrillingMode::kLegacyCorotatedTwist)
EXPECT_FALSE(row.hessian.empty());
else
EXPECT_TRUE(row.hessian.empty());
EXPECT_LT(row.gradient.size(), fixture.dofs.FullDofCount());
for (const auto& entry : row.hessian) {
EXPECT_LT(entry.row, fixture.dofs.FullDofCount());
@@ -989,6 +1003,14 @@ TEST(HhtReducedOperators, ScatterShellDerivativesByConstraintRow) {
}
}
TEST(HhtReducedOperators, ScatterShellDerivativesByConstraintRow) {
CheckShellConstraintScatter(fesa::Mitc4DrillingMode::kLegacyCorotatedTwist);
}
TEST(HhtReducedOperators, DirectSpringScattersZeroInitialHessianByConstraintRow) {
CheckShellConstraintScatter(fesa::Mitc4DrillingMode::kDirectSpring);
}
TEST(HhtReducedEmbedding, RejectsNonfiniteGraphResidualBeforeAcceptance) {
const auto fixture = MakeFixture(false);
const auto chart = fesa::hht_reduced_internal::BuildFixedChart(
@@ -1184,11 +1206,15 @@ TEST(HhtReducedEmbedding,
EXPECT_DOUBLE_EQ(rates.Value().z_acceleration[0U], 1.0);
}
TEST(HhtReducedEmbedding, ReconstructsActualShellRatesWithMixedTimeLoad) {
void CheckActualShellRatesWithMixedTimeLoad(const fesa::Mitc4DrillingMode mode) {
const auto fixture = MakeFixture(false, true);
const auto shells = fesa::hht_internal::BuildRuntimeShells(*fixture.domain,
fixture.dofs);
ASSERT_TRUE(shells.HasValue());
SCOPED_TRACE(static_cast<int>(mode));
for (const auto& shell : shells.Value())
*shell.internal_kernel = fesa::mitc4_drilling_internal::TestAccess::WithMode(
*shell.internal_kernel, mode);
const fesa::Vector zero(fixture.dofs.FullDofCount());
const auto initial = Assemble(fixture, shells.Value(), zero, zero, zero,
zero, true);
@@ -1470,13 +1496,26 @@ TEST(HhtReducedEmbedding, ReconstructsActualShellRatesWithMixedTimeLoad) {
++row) {
auto& omitted = omitted_external_gradient.rows[row].gradient;
const auto& no_force = no_force_operators.Value().rows[row].gradient;
ASSERT_EQ(omitted.size(), no_force.size());
for (std::size_t entry = 0U; entry < omitted.size(); ++entry) {
ASSERT_EQ(omitted[entry].dof, no_force[entry].dof);
saw_external_position_gradient =
saw_external_position_gradient ||
omitted[entry].value != no_force[entry].value;
omitted[entry].value = no_force[entry].value;
if (mode == fesa::Mitc4DrillingMode::kLegacyCorotatedTwist) {
ASSERT_EQ(omitted.size(), no_force.size());
for (std::size_t entry = 0U; entry < omitted.size(); ++entry) {
ASSERT_EQ(omitted[entry].dof, no_force[entry].dof);
saw_external_position_gradient =
saw_external_position_gradient ||
omitted[entry].value != no_force[entry].value;
omitted[entry].value = no_force[entry].value;
}
} else {
// Direct internal/external derivatives can cancel exact sparse entries.
// Compare the full union before replacing only the external contribution.
fesa::Vector with_force(fixture.dofs.FullDofCount());
fesa::Vector without_force(fixture.dofs.FullDofCount());
for (const auto& entry : omitted) with_force[entry.dof] = entry.value;
for (const auto& entry : no_force) without_force[entry.dof] = entry.value;
for (std::size_t dof = 0U; dof < with_force.Size(); ++dof)
saw_external_position_gradient = saw_external_position_gradient ||
with_force[dof] != without_force[dof];
omitted = no_force;
}
}
ASSERT_TRUE(saw_external_position_gradient);
@@ -1560,6 +1599,14 @@ TEST(HhtReducedEmbedding, ReconstructsActualShellRatesWithMixedTimeLoad) {
EXPECT_GT(omitted_second_time_hidden, 1.0e-8);
}
TEST(HhtReducedEmbedding, ReconstructsActualShellRatesWithMixedTimeLoad) {
CheckActualShellRatesWithMixedTimeLoad(fesa::Mitc4DrillingMode::kLegacyCorotatedTwist);
}
TEST(HhtReducedEmbedding, DirectSpringReconstructsActualShellRatesWithMixedTimeLoad) {
CheckActualShellRatesWithMixedTimeLoad(fesa::Mitc4DrillingMode::kDirectSpring);
}
TEST(HhtReducedChannelNewton, RequiresEveryTranslationGateAndThaws) {
fesa::hht_reduced_internal::FixedChart chart{};
chart.full_dof_count = 6U;
@@ -1737,3 +1784,158 @@ TEST(HhtReducedOperators, NullMetricKeepsFiniteLeftAssociatedProducts) {
}
} // namespace
TEST(Mitc4DrillingEvidence, HhtActualMomentMatchesClosedFormAndSourceOrder) {
const auto fixture = MakeFixture(false, true);
const auto shells = fesa::hht_internal::BuildRuntimeShells(*fixture.domain, fixture.dofs);
ASSERT_TRUE(shells.HasValue());
const fesa::Vector zero(fixture.dofs.FullDofCount());
auto q = zero;
for (std::size_t node = 0U; node < fixture.domain->Nodes().size(); ++node) {
q[6U * node + 0U] = 0.03 * static_cast<double>(node);
q[6U * node + 3U] = 0.13 + 0.01 * static_cast<double>(node);
q[6U * node + 4U] = -0.09;
q[6U * node + 5U] = 0.21 - 0.01 * static_cast<double>(node);
}
const auto serial = Assemble(fixture, shells.Value(), q, zero, zero, zero, true);
ASSERT_TRUE(serial.HasValue());
ASSERT_TRUE(serial.Value().frame.mitc4_drilling);
fesa::Vector3 expected;
for (std::size_t element = 0U; element < fixture.domain->ShellElements().Size(); ++element) {
const auto& definition = fixture.domain->ShellElements()[element];
std::array<const fesa::Node*, 4> nodes{};
std::array<std::array<double, 3>, 4> directors{};
for (std::size_t i = 0U; i < 4U; ++i) {
nodes[i] = &fixture.domain->Nodes()[definition.node_indices[i]];
directors[i] = fixture.domain->ShellNodeInitialFrames()[definition.node_indices[i]].director;
}
const auto* section = dynamic_cast<const fesa::ShellSection*>(
&fixture.domain->Properties()[definition.section_index]);
const auto* material = dynamic_cast<const fesa::LinearElasticMaterial*>(
&fixture.domain->Materials()[definition.material_index]);
ASSERT_NE(section, nullptr);
ASSERT_NE(material, nullptr);
const auto kernel = fesa::Mitc4Shell::Create(nodes, directors, *section, *material);
ASSERT_TRUE(kernel.HasValue());
const auto stiffness = kernel.Value().Stiffness();
ASSERT_TRUE(stiffness.HasValue());
double trace = 0.0;
for (std::size_t i = 0U; i < 4U; ++i)
for (std::size_t c = 3U; c < 5U; ++c)
trace += stiffness.Value().physical_local20(5U * i + c, 5U * i + c);
const double kd = 1e-3 * trace / 8.0;
for (std::size_t i = 0U; i < 4U; ++i) {
const auto node = definition.node_indices[i];
const fesa::Vector3 phi{q[6U * node + 3U], q[6U * node + 4U], q[6U * node + 5U]};
const fesa::Vector3 director{directors[i]};
const double angle = phi.Norm();
const double coefficient = (1.0 - 0.5 * angle / std::tan(0.5 * angle)) / (angle * angle);
// Independent closed inverse-transpose Rodrigues identity; no dense solve.
expected = expected + kd * phi.Dot(director) *
(director + 0.5 * phi.Cross(director) + coefficient * phi.Cross(phi.Cross(director)));
}
}
for (std::size_t c = 0U; c < 3U; ++c)
EXPECT_NEAR(serial.Value().frame.mitc4_drilling->drilling_moment[c], expected[c], 2e-14);
EXPECT_EQ(serial.Value().frame.mitc4_drilling->mode, fesa::Mitc4DrillingMode::kDirectSpring);
auto frame = serial.Value().frame;
const auto parameters = fesa::MakeHhtParameters(-0.05);
ASSERT_TRUE(parameters.HasValue());
fesa::TbbParallelFor parallel;
const auto tbb = fesa::hht_internal::EvaluateAssembly(*fixture.domain, fixture.dofs,
shells.Value(), frame, zero, 0.1, parameters.Value(), parallel, true);
ASSERT_TRUE(tbb.HasValue());
ASSERT_TRUE(tbb.Value().frame.mitc4_drilling);
for (std::size_t c = 0U; c < 3U; ++c)
ExpectSameBits(tbb.Value().frame.mitc4_drilling->drilling_moment[c],
serial.Value().frame.mitc4_drilling->drilling_moment[c]);
}
TEST(Mitc4DrillingEvidence, HhtCorrectedGateRejectsOppositeSignAndRealImbalance) {
const auto fixture = MakeFixture(false);
const auto shells = fesa::hht_internal::BuildRuntimeShells(*fixture.domain, fixture.dofs);
ASSERT_TRUE(shells.HasValue());
const fesa::Vector zero(fixture.dofs.FullDofCount());
auto q = zero;
for (std::size_t node = 0U; node < 4U; ++node) q[6U * node + 5U] = 0.125;
const auto assembly = Assemble(fixture, shells.Value(), q, zero, zero, zero, false);
ASSERT_TRUE(assembly.HasValue());
EXPECT_EQ(assembly.Value().hidden_constraint.Size(), 0U);
const auto directional = Assemble(fixture, shells.Value(), q, zero, zero, zero, true);
ASSERT_TRUE(directional.HasValue());
ASSERT_EQ(directional.Value().hidden_constraint.Size(), 4U);
for (std::size_t node = 0U; node < 4U; ++node)
EXPECT_GT(directional.Value().hidden_constraint[node], 0.0);
fesa::MklPardisoSolver positive;
fesa::MklPardisoTangentSolver tangent;
auto unavailable_frame = assembly.Value().frame;
const auto unavailable = fesa::hht_internal::CompleteConstraintEvidence(
*fixture.domain, fixture.dofs, assembly.Value(), 2.0,
fesa::hht_internal::NullNodes(fixture.dofs, assembly.Value().rotations),
positive, tangent, &unavailable_frame);
EXPECT_FALSE(unavailable.IsOk());
EXPECT_EQ(unavailable_frame.hidden_constraint.Size(), 0U);
auto frame = assembly.Value().frame;
// A support supplying the complete internal force gives exact static balance.
frame.reaction = frame.internal_force;
ASSERT_TRUE(fesa::hht_internal::CompleteEquilibrium(*fixture.domain, fixture.dofs,
assembly.Value(), &frame).IsOk());
ASSERT_TRUE(frame.mitc4_drilling);
EXPECT_GT(frame.global_equilibrium[5U], 0.0);
EXPECT_NEAR(frame.global_equilibrium[5U], frame.mitc4_drilling->drilling_moment[2U], 1e-14);
for (const double value : frame.mitc4_drilling->equilibrium_with_drilling)
EXPECT_NEAR(value, 0.0, 1e-14);
for (const int kind : {0, 1, 2, 3}) {
auto invalid = frame;
if (kind == 0) invalid.mitc4_drilling->drilling_moment[2U] *= -1.0;
if (kind == 1) invalid.reaction[0U] += 1.0;
if (kind == 2) invalid.reaction[4U] += 1.0;
if (kind == 3) invalid.mitc4_drilling->mode = fesa::Mitc4DrillingMode::kLegacyCorotatedTwist;
EXPECT_FALSE(fesa::hht_internal::CompleteEquilibrium(*fixture.domain, fixture.dofs,
assembly.Value(), &invalid).IsOk()) << kind;
}
}
TEST(Mitc4DrillingEvidence, DirectHiddenCertificateUsesCanonicalPrimitive) {
const auto fixture = MakeFixture(false);
const auto shells = fesa::hht_internal::BuildRuntimeShells(*fixture.domain, fixture.dofs);
ASSERT_TRUE(shells.HasValue());
const fesa::Vector zero(fixture.dofs.FullDofCount());
auto q = zero;
auto external = zero;
fesa::NonlinearMitc4TrialDofs trial{};
for (std::size_t node = 0U; node < 4U; ++node) {
q[6U * node] = 0.03 * static_cast<double>(node);
trial.displacement[node] = {q[6U * node], 0.0, 0.0};
trial.rotation_vector[node] = {0.00005, 0.000035, 0.00002};
for (std::size_t c = 0U; c < 3U; ++c)
q[6U * node + 3U + c] = trial.rotation_vector[node][c];
}
const auto local = shells.Value()[0U].internal->EvaluateTrial(trial, &zero);
ASSERT_TRUE(local.HasValue());
external = local.Value().drilling_force;
const auto assembly = Assemble(fixture, shells.Value(), q, zero, zero, external, true);
ASSERT_TRUE(assembly.HasValue());
const auto length = fesa::hht_internal::ReferenceLength(*fixture.domain);
ASSERT_TRUE(length.HasValue());
const auto null_nodes = fesa::hht_internal::NullNodes(fixture.dofs, assembly.Value().rotations);
ASSERT_EQ(null_nodes.size(), 4U);
fesa::MklPardisoSolver positive;
fesa::MklPardisoTangentSolver tangent;
auto frame = assembly.Value().frame;
const auto status = fesa::hht_internal::CompleteConstraintEvidence(
*fixture.domain, fixture.dofs, assembly.Value(), length.Value(), null_nodes,
positive, tangent, &frame);
ASSERT_TRUE(status.IsOk()) << (status.Diagnostics().empty() ? "" : status.Diagnostics()[0U].message);
const auto chart = fesa::hht_reduced_internal::BuildFixedChart(fixture.dofs, assembly.Value().rotations);
ASSERT_TRUE(chart.HasValue());
const auto canonical = fesa::hht_reduced_internal::EvaluateConstraintOperators(
*fixture.domain, fixture.dofs, shells.Value(), assembly.Value(), chart.Value(), zero, zero);
ASSERT_TRUE(canonical.HasValue());
for (std::size_t row = 0U; row < 4U; ++row) {
EXPECT_DOUBLE_EQ(frame.hidden_constraint[row], canonical.Value().rows[row].value);
EXPECT_NEAR(frame.hidden_constraint[row], 0.0, 1e-17);
EXPECT_DOUBLE_EQ(frame.hidden_velocity_defect[row], 0.0);
EXPECT_DOUBLE_EQ(frame.hidden_acceleration_defect[row], 0.0);
}
}
@@ -28,6 +28,7 @@
#include "fesa/analysis/newton_iteration_log.h"
#include "fesa/assembly/parallel_for.h"
#include "fesa/elements/finite_rotation.h"
#include "elements/mitc4_drilling_internal.h"
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
#include "fesa/solvers/linear/mkl_pardiso_tangent_solver.h"
@@ -120,6 +121,14 @@ struct Fixture {
double length;
};
// The finite rigid-orbit and free-spin C08 fixtures prescribe the historical
// objective potential and its hidden velocity equation explicitly.
void UseLegacyDrilling(const Fixture& fixture) {
for (const auto& shell : fixture.shells)
*shell.internal_kernel = fesa::mitc4_drilling_internal::TestAccess::WithMode(
*shell.internal_kernel, fesa::Mitc4DrillingMode::kLegacyCorotatedTwist);
}
Fixture MakeFixture(const std::size_t divisions,
const bool lifecycle_translation = false) {
fesa::ModelDefinition definition{};
@@ -643,6 +652,7 @@ TEST(HhtShellPhysics, ManufacturedHiddenCountsExternalHessianOnce) {
TEST(HhtShellPhysics,
C08aRigidOrbitMatchesIndependentVolumeForcePowerAndMomentum) {
const auto fixture = MakeFixture(1U);
UseLegacyDrilling(fixture);
const auto parameters = Require(fesa::MakeHhtParameters(-0.05));
const auto initial =
Evaluate(fixture, EmptyFrame(fixture), 1.0 / 16.0, parameters, false);
@@ -3078,6 +3088,7 @@ std::array<double, 2> AccelerationSplit(const Frame& basis,
TEST(HhtShellPhysics, ReducedManufacturedControllerKeepsExactInitialOrbit) {
const auto fixture = MakeFixture(1U);
UseLegacyDrilling(fixture);
const auto grid = RunPlate(fixture, 1.0 / 64.0, true);
ASSERT_EQ(grid.history.size(), 65U);
const auto exact = RigidOrbit(fixture, 0.0);
@@ -3115,6 +3126,7 @@ TEST(HhtShellPhysics, ReducedManufacturedControllerKeepsExactInitialOrbit) {
TEST(HhtShellPhysics, C08aRigidManufacturedOrbitReducedCoordinateTimeConvergence) {
const auto fixture = MakeFixture(1U);
UseLegacyDrilling(fixture);
std::vector<GridEvidence> grids;
std::vector<std::array<double, 6>> errors;
for (const unsigned denominator : {16U, 32U, 64U, 128U, 256U, 512U, 1024U}) {
@@ -3297,6 +3309,7 @@ TEST(HhtShellPhysics, C08bInitialPhysicalSpectrumProbe) {
TEST(HhtShellPhysics,
PredictorStartCompletesPreviouslyFailingLumpedFreePlateGrid) {
const auto fixture = MakeFixture(2U);
UseLegacyDrilling(fixture);
const auto evidence = RunPlate(fixture, 1.0 / 256.0, false);
ASSERT_EQ(evidence.history.size(), 65U);
EXPECT_DOUBLE_EQ(evidence.history.back().time, kEndTime);
@@ -3307,6 +3320,7 @@ TEST(HhtShellPhysics,
TEST(HhtShellPhysics,
C08bFreeFiniteRotationPlateSelfConvergenceAndPhysicalInvariants) {
const auto fixture = MakeFixture(2U);
UseLegacyDrilling(fixture);
std::vector<GridEvidence> grids;
for (const unsigned denominator :
{64U, 128U, 256U, 512U, 1024U, 32768U, 65536U, 131072U}) {
@@ -454,3 +454,28 @@ TEST(NonlinearAnalysisState, StoresNoUnapprovedVerificationMetrics) {
EXPECT_FALSE((HasVerificationMetrics<fesa::NonlinearTrialState>::value));
EXPECT_FALSE((HasVerificationMetrics<fesa::AcceptedFrameState>::value));
}
TEST(NonlinearAnalysisState, DrillingEvidenceRetainsModeAndRejectsPartialCommit) {
const auto fixture = MakeFixture();
auto result = fesa::NonlinearAnalysisState::Create(*fixture.domain, *fixture.dofs,
MakeBaseline(*fixture.domain));
ASSERT_TRUE(result.HasValue());
auto& state = result.Value();
auto first = MakeFrame(state.CommittedState(), 0U, 1U, 0.5, 1U);
first.mitc4_drilling = fesa::Mitc4DrillingEvidence{fesa::Mitc4DrillingMode::kDirectSpring};
ASSERT_TRUE(state.CommitAcceptedFrame(first).IsOk());
for (const int kind : {0, 1, 2, 3, 4}) {
auto next = MakeFrame(state.CommittedState(), 1U, 2U, 1.0, 1U);
next.mitc4_drilling = first.mitc4_drilling;
if (kind == 0) next.mitc4_drilling.reset();
if (kind == 1) next.mitc4_drilling->mode = fesa::Mitc4DrillingMode::kLegacyCorotatedTwist;
if (kind == 2) next.mitc4_drilling->mode = static_cast<fesa::Mitc4DrillingMode>(99);
if (kind == 3) next.mitc4_drilling->equilibrium_with_drilling[3U] = 1.0;
if (kind == 4) next.mitc4_drilling->drilling_moment[0U] = std::numeric_limits<double>::infinity();
EXPECT_FALSE(state.CommitAcceptedFrame(next).IsOk()) << kind;
ASSERT_EQ(state.AcceptedFrames().size(), 1U);
EXPECT_DOUBLE_EQ(state.CommittedState().load_factor, 0.5);
EXPECT_EQ(state.CommittedState().mitc4_drilling->mode,
fesa::Mitc4DrillingMode::kDirectSpring);
}
}