test: verify candidate HHT physical endpoint semantics

This commit is contained in:
KOKO\Mimi
2026-10-19 03:03:40 +09:00
parent dd54b0ae1e
commit 0c88cb24c8
2 changed files with 240 additions and 0 deletions
@@ -750,6 +750,86 @@ TEST(HhtDynamicProcedure, AcceptedFluxHistoryCountsEachIncrementExactlyOnce) {
EXPECT_EQ(n, 2U);
}
TEST(HhtDynamicProcedure, CandidateRigidTranslationMatchesIndependentPhysicalOracle) {
// Two disconnected unit squares: rho=2, thickness=.1, area=1 each.
// Each nodal lump has mass .05 and receives F_x=1, hence a_x=20.
// This exact rigid motion verifies endpoint semantics, not temporal order.
constexpr double kNumericalTolerance = 1e-3;
constexpr double kAcceleration = 20.0;
constexpr double kTotalMass = 0.4;
constexpr double kTotalForce = 8.0;
constexpr double kLength = 1.0;
constexpr double kTime = 0.5;
constexpr double kEnergy = 20.0;
fesa::SerialParallelFor parallel;
for (const bool constrain_transverse : {false, true}) {
SCOPED_TRACE(constrain_transverse);
auto definition = MakeRigidTranslationDefinition();
if (constrain_transverse) {
definition.dynamic_step->boundaries = {
{"All-A", 2, 6, 0.0, {"dynamic-state.inp", 30U}},
{"All-B", 2, 6, 0.0, {"dynamic-state.inp", 31U}}};
}
auto run = RunRigidTranslation(definition, parallel,
constrain_transverse ? "analytic-bc" : "analytic-free");
ASSERT_TRUE(run.HasValue()) << (run.HasValue() ? "" : FailureText(run.GetStatus()));
const auto& frames = run.Value().execution.state.AcceptedFrames();
ASSERT_EQ(frames.size(), 3U);
for (std::size_t index = 0U; index < frames.size(); ++index) {
const auto& frame = frames[index];
EXPECT_DOUBLE_EQ(frame.time, static_cast<double>(index) * 0.25);
const double q = 0.5 * kAcceleration * frame.time * frame.time;
const double v = kAcceleration * frame.time;
for (std::size_t node = 0U; node < 8U; ++node) {
const auto dof = 6U * node;
EXPECT_LE(std::abs(frame.generalized_coordinates[dof] - q) / kLength, kNumericalTolerance);
EXPECT_LE(std::abs(frame.velocity[dof] - v) * kTime / kLength, kNumericalTolerance);
EXPECT_LE(std::abs(frame.acceleration[dof] - kAcceleration) *
kTime * kTime / kLength, kNumericalTolerance);
EXPECT_LE(std::abs(frame.algorithmic_acceleration[dof] - kAcceleration) *
kTime * kTime / kLength, kNumericalTolerance);
EXPECT_DOUBLE_EQ(frame.external_force[dof], 1.0);
EXPECT_LE(std::abs(frame.inertia_force[dof] - 1.0), kNumericalTolerance);
for (std::size_t component = 1U; component < 6U; ++component) {
const double scale = component < 3U ? kLength : 1.0;
EXPECT_LE(std::abs(frame.generalized_coordinates[dof + component]) / scale, kNumericalTolerance);
EXPECT_LE(std::abs(frame.velocity[dof + component]) * kTime / scale, kNumericalTolerance);
EXPECT_LE(std::abs(frame.acceleration[dof + component]) * kTime * kTime / scale, kNumericalTolerance);
EXPECT_LE(std::abs(frame.algorithmic_acceleration[dof + component]) * kTime * kTime /
scale, kNumericalTolerance);
if (constrain_transverse) {
EXPECT_DOUBLE_EQ(frame.generalized_coordinates[dof + component], 0.0);
EXPECT_DOUBLE_EQ(frame.velocity[dof + component], 0.0);
EXPECT_DOUBLE_EQ(frame.acceleration[dof + component], 0.0);
EXPECT_DOUBLE_EQ(frame.algorithmic_acceleration[dof + component], 0.0);
}
}
}
EXPECT_LE(std::abs(frame.kinetic_energy - 0.5 * kTotalMass * v * v) /
kEnergy, kNumericalTolerance);
EXPECT_LE(std::abs(frame.external_work - kTotalForce * q) / kEnergy, kNumericalTolerance);
EXPECT_LE(std::abs(frame.physical_strain_energy) / kEnergy, kNumericalTolerance);
EXPECT_LE(std::abs(frame.drilling_energy) / kEnergy, kNumericalTolerance);
EXPECT_LE(std::abs(frame.energy_defect) / kEnergy, kNumericalTolerance);
for (const auto value : frame.global_equilibrium)
EXPECT_LE(std::abs(value), 1e-8);
}
std::size_t accepted = 0U;
std::istringstream input(run.Value().log);
std::string row;
while (std::getline(input, row)) {
const auto fields = LogFields(row);
if (fields.at("schema") != "hht-log-v2") continue;
EXPECT_EQ(fields.at("policy_id"), "abaqus2025-field-on-fesa-v1");
if (fields.at("accepted") == "1") {
EXPECT_EQ(fields.at("field_pass"), "1");
++accepted;
}
}
EXPECT_EQ(accepted, 2U);
}
}
TEST(HhtDynamicProcedure, LoadFluxPreservesSourceCancellationAndExplicitZeroEntries) {
auto definition = MakeDefinition(true);
definition.dynamic_step->loads = {