feat(result-contract-completion): step 1 — complete-result-contract

This commit is contained in:
KOKO\Mimi
2026-08-02 01:28:37 +09:00
parent 218cfa9d50
commit e0a6a6fa70
8 changed files with 454 additions and 5 deletions
+13 -1
View File
@@ -598,6 +598,12 @@ add_test(
--gtest_filter=ResultDatabase.*
)
add_test(
NAME ElementFrame
COMMAND "$<TARGET_FILE:fesa_result_database_tests>"
--gtest_filter=ElementFrame.*
)
add_executable(fesa_hdf5_results_tests
integration/io/hdf5_results_test.cpp
)
@@ -674,8 +680,14 @@ add_test(
--gtest_filter=StaticEquilibrium.*
)
add_test(
NAME CompleteResultContract
COMMAND "$<TARGET_FILE:fesa_linear_static_analysis_tests>"
--gtest_filter=CompleteResultContract.*
)
set_property(
TEST LinearStaticAnalysis StaticEquilibrium
TEST LinearStaticAnalysis StaticEquilibrium CompleteResultContract
PROPERTY ENVIRONMENT_MODIFICATION
${FESA_DEPENDENCY_RUNTIME_MODIFICATIONS}
)
@@ -226,6 +226,12 @@ fesa::ResultDatabase make_database() {
1.25,
{
{fesa::NodeId{7}, fesa::NodeId{42}},
{
fesa::EntityOrigin{
"BeamPart", "Beam-1", 1002},
fesa::EntityOrigin{
"BeamPart", "Beam-1", 1001},
},
{
{1.0, 2.0, 3.0, 4.0, 5.0, 6.0},
{-1.0, -2.0, -3.0, -4.0, -5.0, -6.0},
@@ -236,6 +242,7 @@ fesa::ResultDatabase make_database() {
},
},
{},
{},
}},
}},
};
@@ -279,6 +286,12 @@ TEST(ResultRoundTrip, PreservesMinimalSchemaModelAndNodalResults) {
EXPECT_EQ(
frame.nodal.node_ids,
(std::vector<fesa::NodeId>{fesa::NodeId{7}, fesa::NodeId{42}}));
EXPECT_EQ(
frame.nodal.origins,
(std::vector<fesa::EntityOrigin>{
{"BeamPart", "Beam-1", 1002},
{"BeamPart", "Beam-1", 1001},
}));
EXPECT_EQ(
frame.nodal.displacement[0],
(std::array<double, 6>{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}));
@@ -59,7 +59,7 @@ fesa::Domain build_axial_domain(const bool all_constrained) {
2.0,
fesa::ShearPropertySource::input,
fesa::Vec3{0.0, 1.0, 0.0},
{},
{{1.0, 2.0}, {-1.0, -2.0}},
});
builder.add_beam_element({
fesa::ElementId{0},
@@ -214,4 +214,49 @@ TEST(LinearStaticAnalysis, SolvesAllConstrainedSystemWithoutPardiso) {
EXPECT_NEAR(nodal.reaction[1][0], -5.0, 1.0e-12);
}
TEST(CompleteResultContract, PreservesOriginsConnectivityAndBeamRecovery) {
const fesa::Domain domain = build_axial_domain(false);
const fesa::AnalysisRunResult run =
fesa::LinearStaticAnalysis{}.run(domain);
ASSERT_TRUE(run.succeeded);
ASSERT_TRUE(run.results.has_value());
const fesa::ResultFrame& frame = run.results->steps[0].frames[0];
EXPECT_EQ(
frame.nodal.origins,
(std::vector<fesa::EntityOrigin>{
{"BeamPart", "Beam-1", 2},
{"BeamPart", "Beam-1", 1},
}));
ASSERT_EQ(frame.element.beams.size(), 1U);
const fesa::BeamElementFrame& beam = frame.element.beams[0];
EXPECT_EQ(beam.element, fesa::ElementId{0});
EXPECT_EQ(
beam.origin,
(fesa::EntityOrigin{"BeamPart", "Beam-1", 1}));
EXPECT_DOUBLE_EQ(beam.local_frame.ex.x, 1.0);
EXPECT_DOUBLE_EQ(beam.local_frame.ex.y, 0.0);
EXPECT_DOUBLE_EQ(beam.local_frame.ex.z, 0.0);
EXPECT_DOUBLE_EQ(beam.local_frame.ey.x, 0.0);
EXPECT_DOUBLE_EQ(beam.local_frame.ey.y, 1.0);
EXPECT_DOUBLE_EQ(beam.local_frame.ey.z, 0.0);
EXPECT_DOUBLE_EQ(beam.local_frame.ez.x, 0.0);
EXPECT_DOUBLE_EQ(beam.local_frame.ez.y, 0.0);
EXPECT_DOUBLE_EQ(beam.local_frame.ez.z, 1.0);
EXPECT_EQ(beam.end_results[0].end_node, fesa::NodeId{20});
EXPECT_EQ(beam.end_results[1].end_node, fesa::NodeId{4});
EXPECT_DOUBLE_EQ(beam.end_results[0].xi, -1.0);
EXPECT_DOUBLE_EQ(beam.end_results[1].xi, 1.0);
for (const fesa::BeamSectionResult& end : beam.end_results) {
EXPECT_NEAR(end.section_strain[0], 0.05, 1.0e-12);
EXPECT_NEAR(end.section_force[0], 10.0, 1.0e-12);
EXPECT_NEAR(end.centroid_sigma_xx, 5.0, 1.0e-12);
ASSERT_EQ(end.sigma_xx.size(), 2U);
EXPECT_NEAR(end.sigma_xx[0], 5.0, 1.0e-12);
EXPECT_NEAR(end.sigma_xx[1], 5.0, 1.0e-12);
}
}
} // namespace
+154 -1
View File
@@ -17,6 +17,10 @@ std::array<double, 6> zeros() {
fesa::ResultDatabase valid_database() {
fesa::NodalFrame nodal{
{fesa::NodeId{0}, fesa::NodeId{1}},
{
fesa::EntityOrigin{"BeamPart", "Beam-1", 10},
fesa::EntityOrigin{"BeamPart", "Beam-1", 20},
},
{
zeros(),
{1.0, 2.0, 3.0, 0.1, 0.2, 0.3},
@@ -26,7 +30,39 @@ fesa::ResultDatabase valid_database() {
zeros(),
},
};
fesa::ResultFrame frame{1.0, std::move(nodal), {}};
fesa::BeamElementFrame beam{
fesa::ElementId{3},
fesa::EntityOrigin{"BeamPart", "Beam-1", 30},
{
fesa::Vec3{1.0, 0.0, 0.0},
fesa::Vec3{0.0, 1.0, 0.0},
fesa::Vec3{0.0, 0.0, 1.0},
},
{{
{
-1.0,
fesa::NodeId{0},
zeros(),
zeros(),
0.0,
{1.0},
},
{
1.0,
fesa::NodeId{1},
zeros(),
zeros(),
0.0,
{2.0},
},
}},
};
fesa::ResultFrame frame{
1.0,
std::move(nodal),
{{std::move(beam)}},
{},
};
fesa::ResultStep step{"Load", {std::move(frame)}};
return {"1.0.0", {std::move(step)}};
}
@@ -72,6 +108,17 @@ TEST(NodalFrame, RejectsReactionSizeMismatch) {
EXPECT_TRUE(has_diagnostic(status, "results.nodal_size_mismatch"));
}
TEST(CompleteResultContract, RejectsNodeOriginSizeMismatch) {
auto database = valid_database();
database.steps[0].frames[0].nodal.origins.pop_back();
const auto status = fesa::validate_result_database(database);
EXPECT_FALSE(status.succeeded);
EXPECT_TRUE(has_diagnostic(
status, "results.nodal_origin_size_mismatch"));
}
TEST(NodalFrame, RejectsDuplicateNodeId) {
auto database = valid_database();
database.steps[0].frames[0].nodal.node_ids[1] = fesa::NodeId{0};
@@ -135,4 +182,110 @@ TEST(ResultDatabase, RejectsNonfiniteFrameTime) {
EXPECT_TRUE(has_diagnostic(status, "results.nonfinite_value"));
}
TEST(CompleteResultContract, DeclaresCoordinatesAndComponentOrdering) {
EXPECT_EQ(
fesa::NodalFrame::coordinate_system,
fesa::FieldCoordinateSystem::global);
EXPECT_EQ(
fesa::NodalFrame::displacement_components,
(std::array<std::string_view, 6>{
"Ux", "Uy", "Uz", "Rx", "Ry", "Rz"}));
EXPECT_EQ(
fesa::NodalFrame::reaction_components,
(std::array<std::string_view, 6>{
"RFx", "RFy", "RFz", "RMx", "RMy", "RMz"}));
EXPECT_EQ(
fesa::BeamElementFrame::coordinate_system,
fesa::FieldCoordinateSystem::element_local);
EXPECT_EQ(
fesa::BeamElementFrame::section_strain_components,
(std::array<std::string_view, 6>{
"epsilon", "gamma_y", "gamma_z", "kappa_x", "kappa_y",
"kappa_z"}));
EXPECT_EQ(
fesa::BeamElementFrame::section_force_components,
(std::array<std::string_view, 6>{
"N", "Vy", "Vz", "T", "My", "Mz"}));
EXPECT_EQ(
fesa::BeamElementFrame::axial_stress_component,
std::string_view{"sigma_xx"});
}
TEST(ElementFrame, AcceptsFiniteConnectedBeamResults) {
const auto status = fesa::validate_result_database(valid_database());
EXPECT_TRUE(status.succeeded);
EXPECT_TRUE(status.diagnostics.empty());
}
TEST(ElementFrame, RejectsDuplicateElementId) {
auto database = valid_database();
auto& beams = database.steps[0].frames[0].element.beams;
beams.push_back(beams[0]);
const auto status = fesa::validate_result_database(database);
EXPECT_FALSE(status.succeeded);
EXPECT_TRUE(has_diagnostic(status, "results.duplicate_element_id"));
}
TEST(ElementFrame, RejectsDuplicateEndNode) {
auto database = valid_database();
auto& ends = database.steps[0].frames[0]
.element.beams[0].end_results;
ends[1].end_node = ends[0].end_node;
const auto status = fesa::validate_result_database(database);
EXPECT_FALSE(status.succeeded);
EXPECT_TRUE(has_diagnostic(status, "results.duplicate_beam_end_node"));
}
TEST(ElementFrame, RejectsWrongEndConnectivity) {
auto database = valid_database();
database.steps[0].frames[0]
.element.beams[0].end_results[0].xi = 1.0;
const auto status = fesa::validate_result_database(database);
EXPECT_FALSE(status.succeeded);
EXPECT_TRUE(has_diagnostic(status, "results.invalid_beam_connectivity"));
}
TEST(ElementFrame, RejectsEndNodeAbsentFromNodalFrame) {
auto database = valid_database();
database.steps[0].frames[0]
.element.beams[0].end_results[1].end_node = fesa::NodeId{99};
const auto status = fesa::validate_result_database(database);
EXPECT_FALSE(status.succeeded);
EXPECT_TRUE(has_diagnostic(status, "results.invalid_beam_connectivity"));
}
TEST(ElementFrame, RejectsNonfiniteLocalFrameAndSectionValue) {
auto database = valid_database();
auto& beam = database.steps[0].frames[0].element.beams[0];
beam.local_frame.ey.y = std::numeric_limits<double>::infinity();
beam.end_results[1].section_force[4] =
std::numeric_limits<double>::quiet_NaN();
const auto status = fesa::validate_result_database(database);
EXPECT_FALSE(status.succeeded);
EXPECT_TRUE(has_diagnostic(status, "results.nonfinite_value"));
}
TEST(ElementFrame, RejectsMismatchedRecoveryPointCount) {
auto database = valid_database();
database.steps[0].frames[0]
.element.beams[0].end_results[1].sigma_xx.push_back(3.0);
const auto status = fesa::validate_result_database(database);
EXPECT_FALSE(status.succeeded);
EXPECT_TRUE(has_diagnostic(
status, "results.recovery_point_count_mismatch"));
}
} // namespace