test: verify candidate HHT physical endpoint semantics
This commit is contained in:
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user