test: onboard quad02 reaction reference

This commit is contained in:
NINI
2026-05-05 23:56:27 +09:00
parent 9741671f70
commit c47557885d
15 changed files with 597 additions and 24 deletions
+93 -4
View File
@@ -213,10 +213,14 @@ std::string readTextFile(const std::string& path) {
void checkComparisonPass(const fesa::ComparisonResult& comparison) {
if (!comparison.pass) {
throw std::runtime_error("reference comparison failed: max_abs_error=" +
std::to_string(comparison.max_abs_error) +
", max_rel_error=" + std::to_string(comparison.max_rel_error) +
", diagnostics=" + std::to_string(comparison.diagnostics.size()));
std::string message = "reference comparison failed: max_abs_error=" +
std::to_string(comparison.max_abs_error) +
", max_rel_error=" + std::to_string(comparison.max_rel_error) +
", diagnostics=" + std::to_string(comparison.diagnostics.size());
if (!comparison.diagnostics.empty()) {
message += ", first_diagnostic=" + comparison.diagnostics.front().message;
}
throw std::runtime_error(message);
}
}
@@ -1009,6 +1013,20 @@ FESA_TEST(displacement_csv_loader_accepts_quad02_format) {
FESA_CHECK(table.rows.at(2).values[2] < 0.0);
}
FESA_TEST(reaction_csv_loader_accepts_quad02_reactionforces_format) {
const auto required_columns = fesa::reactionCsvRequiredColumns();
FESA_CHECK(required_columns == std::vector<std::string>({"Node Label", "RF-RF1", "RF-RF2", "RF-RF3",
"RM-RM1", "RM-RM2", "RM-RM3"}));
auto table = fesa::loadReactionCsv(sourceRoot() + "/references/quad_02_reactionforces.csv");
FESA_CHECK(!fesa::hasError(table.diagnostics));
FESA_CHECK(table.rows.size() == 121);
FESA_CHECK(table.rows.count(1) == 1);
FESA_CHECK(table.rows.count(2) == 1);
FESA_CHECK_NEAR(table.rows.at(1).values[2], 6.86e3, 1.0e-9);
FESA_CHECK_NEAR(table.rows.at(1).values[4], 2.40e4, 1.0e-8);
}
FESA_TEST(displacement_csv_loader_reports_required_header_errors) {
auto table = fesa::loadDisplacementCsvFromString("Node Label,U-U1,U-U2,U-U3,UR-UR1,UR-UR2\n"
"1,0,0,0,0,0\n",
@@ -1016,6 +1034,13 @@ FESA_TEST(displacement_csv_loader_reports_required_header_errors) {
FESA_CHECK(fesa::containsDiagnostic(table.diagnostics, "FESA-CSV-MISSING-COLUMN"));
}
FESA_TEST(reaction_csv_loader_reports_required_header_errors) {
auto table = fesa::loadReactionCsvFromString("Node Label,RF-RF1,RF-RF2,RF-RF3,RM-RM1,RM-RM2\n"
"1,0,0,0,0,0\n",
"missing-reaction-header.csv");
FESA_CHECK(fesa::containsDiagnostic(table.diagnostics, "FESA-CSV-MISSING-COLUMN"));
}
FESA_TEST(displacement_csv_loader_reports_duplicate_node_rows) {
auto table = fesa::loadDisplacementCsvFromString("Node Label,U-U1,U-U2,U-U3,UR-UR1,UR-UR2,UR-UR3\n"
"1,0,0,0,0,0,0\n"
@@ -1024,6 +1049,14 @@ FESA_TEST(displacement_csv_loader_reports_duplicate_node_rows) {
FESA_CHECK(fesa::containsDiagnostic(table.diagnostics, "FESA-CSV-DUPLICATE-NODE"));
}
FESA_TEST(reaction_csv_loader_reports_duplicate_node_rows) {
auto table = fesa::loadReactionCsvFromString("Node Label,RF-RF1,RF-RF2,RF-RF3,RM-RM1,RM-RM2,RM-RM3\n"
"1,0,0,0,0,0,0\n"
"1,0,0,0,0,0,0\n",
"duplicate-reaction-node.csv");
FESA_CHECK(fesa::containsDiagnostic(table.diagnostics, "FESA-CSV-DUPLICATE-NODE"));
}
FESA_TEST(displacement_csv_loader_reports_missing_and_non_numeric_node_rows) {
auto table = fesa::loadDisplacementCsvFromString("Node Label,U-U1,U-U2,U-U3,UR-UR1,UR-UR2,UR-UR3\n"
",0,0,0,0,0,0\n"
@@ -1050,6 +1083,22 @@ FESA_TEST(displacement_comparator_matches_by_node_id_not_row_order) {
FESA_CHECK(compared.pass);
}
FESA_TEST(reaction_comparator_matches_by_node_id_not_row_order) {
fesa::FieldOutput actual;
actual.name = "RF";
actual.position = "NODAL";
actual.entity_type = "node";
actual.basis = "GLOBAL";
actual.entity_ids = {2, 1};
actual.component_labels = fesa::reactionComponentLabels();
actual.values = {{{0, 0, 2, 0, 20, 0}}, {{0, 0, 1, 0, 10, 0}}};
fesa::CsvReactionTable expected;
expected.rows[1] = {1, {0, 0, 1, 0, 10, 0}};
expected.rows[2] = {2, {0, 0, 2, 0, 20, 0}};
auto compared = fesa::compareReactions(actual, expected, {1.0e-12, 1.0e-12, 1.0});
FESA_CHECK(compared.pass);
}
FESA_TEST(displacement_comparator_uses_absolute_and_relative_tolerances) {
fesa::FieldOutput actual;
actual.name = "U";
@@ -1089,6 +1138,24 @@ FESA_TEST(displacement_comparator_rejects_wrong_component_labels_and_missing_nod
FESA_CHECK(fesa::containsDiagnostic(compared.diagnostics, "FESA-COMPARE-MISSING-ACTUAL"));
}
FESA_TEST(reaction_comparator_rejects_wrong_component_labels_and_missing_nodes) {
fesa::FieldOutput actual;
actual.name = "RF";
actual.position = "NODAL";
actual.entity_type = "node";
actual.basis = "GLOBAL";
actual.entity_ids = {1};
actual.component_labels = fesa::displacementComponentLabels();
actual.values = {{{0, 0, 0, 0, 0, 0}}};
fesa::CsvReactionTable expected;
expected.rows[2] = {2, {0, 0, 0, 0, 0, 0}};
auto compared = fesa::compareReactions(actual, expected, {1.0e-12, 1.0e-12, 1.0});
FESA_CHECK(!compared.pass);
FESA_CHECK(fesa::containsDiagnostic(compared.diagnostics, "FESA-COMPARE-COMPONENT-LABELS"));
FESA_CHECK(fesa::containsDiagnostic(compared.diagnostics, "FESA-COMPARE-MISSING-ACTUAL"));
}
FESA_TEST(displacement_comparator_reports_duplicate_actual_nodes) {
fesa::FieldOutput actual;
actual.name = "U";
@@ -1122,9 +1189,13 @@ FESA_TEST(quad02_reference_fixture_discovery_is_consistent) {
const auto reference = fesa::loadDisplacementCsv(sourceRoot() + "/references/quad_02_displacements.csv");
FESA_CHECK(!fesa::hasError(reference.diagnostics));
FESA_CHECK(reference.rows.size() == normalized.domain.nodes.size());
const auto reactions = fesa::loadReactionCsv(sourceRoot() + "/references/quad_02_reactionforces.csv");
FESA_CHECK(!fesa::hasError(reactions.diagnostics));
FESA_CHECK(reactions.rows.size() == normalized.domain.nodes.size());
for (const auto& [node_id, node] : normalized.domain.nodes) {
(void)node;
FESA_CHECK(reference.rows.count(node_id) == 1);
FESA_CHECK(reactions.rows.count(node_id) == 1);
}
}
@@ -1145,6 +1216,24 @@ FESA_TEST(quad02_phase1_stored_displacement_reference_regression) {
FESA_CHECK(comparison.max_rel_error <= 1.0e-5);
}
FESA_TEST(quad02_phase1_stored_reaction_reference_comparison_reports_current_gap) {
const auto input_text = readTextFile(sourceRoot() + "/references/quad_02_phase1.inp");
const auto analysis = fesa::runLinearStaticInputString(input_text, "quad_02_phase1.inp");
FESA_CHECK(analysis.ok());
FESA_CHECK(analysis.state.converged);
FESA_CHECK(analysis.result_file.steps.size() == 1);
const auto& frame = analysis.result_file.steps[0].frames[0];
FESA_CHECK(frame.field_outputs.count("RF") == 1);
const auto expected = fesa::loadReactionCsv(sourceRoot() + "/references/quad_02_reactionforces.csv");
FESA_CHECK(!fesa::hasError(expected.diagnostics));
const auto comparison = fesa::compareReactions(frame.field_outputs.at("RF"), expected, {1.0e-6, 1.0e-5, 1.0});
FESA_CHECK(!comparison.pass);
FESA_CHECK(fesa::containsDiagnostic(comparison.diagnostics, "FESA-COMPARE-TOLERANCE"));
FESA_CHECK(comparison.max_abs_error > 100.0);
FESA_CHECK(comparison.max_rel_error > 0.1);
}
FESA_TEST(mitc4_shape_functions_node_order_and_tying_points) {
auto center = fesa::shapeFunctions(0.0, 0.0);
const fesa::Real sum = center.n[0] + center.n[1] + center.n[2] + center.n[3];
+17
View File
@@ -91,6 +91,11 @@ int main() {
check(required_columns.front() == "Node Label", "CSV node label column changed");
check(required_columns.back() == "UR-UR3", "CSV rotation column changed");
const auto required_reaction_columns = fesa::reactionCsvRequiredColumns();
check(required_reaction_columns.size() == 7, "required reaction CSV column count changed");
check(required_reaction_columns.front() == "Node Label", "reaction CSV node label column changed");
check(required_reaction_columns.back() == "RM-RM3", "reaction CSV moment column changed");
const auto missing_header = fesa::loadDisplacementCsvFromString("Node Label,U-U1,U-U2,U-U3,UR-UR1,UR-UR2\n"
"1,0,0,0,0,0\n",
"missing-header.csv");
@@ -112,9 +117,21 @@ int main() {
const auto comparison = fesa::compareDisplacements(u_field, expected, {1.0e-12, 1.0e-12, 1.0});
check(comparison.pass, "displacement comparator no longer matches by node id");
fesa::CsvReactionTable expected_reactions;
expected_reactions.rows[1] = {1, {0, 0, 3.0, 0, 0, 0}};
expected_reactions.rows[2] = {2, {0, 0, 0, 0, 0, 0}};
expected_reactions.rows[3] = {3, {0, 0, 0, 0, 0, 0}};
expected_reactions.rows[4] = {4, {0, 0, 0, 0, 0, 0}};
const auto reaction_comparison = fesa::compareReactions(rf_field, expected_reactions, {1.0e-12, 1.0e-12, 1.0});
check(reaction_comparison.pass, "reaction comparator no longer matches by node id");
const auto quad02 = fesa::loadDisplacementCsv(sourceRoot() + "/references/quad_02_displacements.csv");
check(!fesa::hasError(quad02.diagnostics), "quad_02 displacement CSV no longer loads");
check(quad02.rows.size() == 121, "quad_02 displacement CSV row count changed");
const auto quad02_reactions = fesa::loadReactionCsv(sourceRoot() + "/references/quad_02_reactionforces.csv");
check(!fesa::hasError(quad02_reactions.diagnostics), "quad_02 reaction CSV no longer loads");
check(quad02_reactions.rows.size() == 121, "quad_02 reaction CSV row count changed");
return 0;
}