feat: strengthen results comparator foundation

This commit is contained in:
NINI
2026-05-04 13:27:46 +09:00
parent b9b0963d50
commit 6de430f1ed
7 changed files with 229 additions and 32 deletions
+117 -4
View File
@@ -652,11 +652,40 @@ FESA_TEST(results_writer_uses_step_frame_fields_for_u_and_rf) {
writer.writeLinearStatic(domain, dofs, u, rf);
const auto& result = writer.result();
FESA_CHECK(result.schema_name == "FESA_RESULTS");
FESA_CHECK(result.schema_version == 1);
FESA_CHECK(result.solver_name == "FESA");
FESA_CHECK(result.dof_convention == "UX,UY,UZ,RX,RY,RZ");
FESA_CHECK(result.sign_convention == "Abaqus-compatible");
FESA_CHECK(result.precision == "double");
FESA_CHECK(result.index_type == "int64");
FESA_CHECK(result.steps.size() == 1);
FESA_CHECK(result.steps[0].frames[0].field_outputs.count("U") == 1);
FESA_CHECK(result.steps[0].frames[0].field_outputs.count("RF") == 1);
FESA_CHECK(result.steps[0].frames[0].field_outputs.at("U").component_labels[2] == "UZ");
FESA_CHECK(result.steps[0].frames[0].field_outputs.at("RF").component_labels[2] == "RFZ");
FESA_CHECK(result.steps[0].name == "Step-1");
FESA_CHECK(result.steps[0].frames.size() == 1);
const auto& frame = result.steps[0].frames[0];
FESA_CHECK(frame.frame_id == 0);
FESA_CHECK(frame.increment == 1);
FESA_CHECK(frame.iteration == 0);
FESA_CHECK(frame.converged);
FESA_CHECK_NEAR(frame.step_time, 1.0, 1.0e-15);
FESA_CHECK_NEAR(frame.total_time, 1.0, 1.0e-15);
FESA_CHECK(frame.field_outputs.count("U") == 1);
FESA_CHECK(frame.field_outputs.count("RF") == 1);
const auto& u_field = frame.field_outputs.at("U");
const auto& rf_field = frame.field_outputs.at("RF");
FESA_CHECK(u_field.name == "U");
FESA_CHECK(u_field.position == "NODAL");
FESA_CHECK(u_field.entity_type == "node");
FESA_CHECK(u_field.basis == "GLOBAL");
FESA_CHECK(u_field.component_labels == fesa::displacementComponentLabels());
FESA_CHECK(rf_field.name == "RF");
FESA_CHECK(rf_field.position == "NODAL");
FESA_CHECK(rf_field.entity_type == "node");
FESA_CHECK(rf_field.basis == "GLOBAL");
FESA_CHECK(rf_field.component_labels == fesa::reactionComponentLabels());
FESA_CHECK(u_field.component_labels[2] == "UZ");
FESA_CHECK(rf_field.component_labels[2] == "RFZ");
FESA_CHECK(u_field.entity_ids.size() == domain.nodes.size());
FESA_CHECK(rf_field.entity_ids.size() == domain.nodes.size());
}
FESA_TEST(displacement_csv_loader_accepts_quad01_format) {
@@ -674,9 +703,37 @@ FESA_TEST(displacement_csv_loader_accepts_quad02_format) {
FESA_CHECK(table.rows.at(2).values[2] < 0.0);
}
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",
"missing-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"
"1,0,0,0,0,0,0\n",
"duplicate-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"
"two,0,0,0,0,0,0\n"
"3,0,not-a-number,0,0,0,0\n",
"invalid-node-row.csv");
FESA_CHECK(diagnosticCount(table.diagnostics, "FESA-CSV-NODE") == 2);
FESA_CHECK(fesa::containsDiagnostic(table.diagnostics, "FESA-CSV-NUMERIC"));
}
FESA_TEST(displacement_comparator_matches_by_node_id_not_row_order) {
fesa::FieldOutput actual;
actual.name = "U";
actual.position = "NODAL";
actual.entity_type = "node";
actual.basis = "GLOBAL";
actual.entity_ids = {2, 1};
actual.component_labels = fesa::displacementComponentLabels();
actual.values = {{{2, 0, 0, 0, 0, 0}}, {{1, 0, 0, 0, 0, 0}}};
@@ -687,6 +744,62 @@ FESA_TEST(displacement_comparator_matches_by_node_id_not_row_order) {
FESA_CHECK(compared.pass);
}
FESA_TEST(displacement_comparator_uses_absolute_and_relative_tolerances) {
fesa::FieldOutput actual;
actual.name = "U";
actual.position = "NODAL";
actual.entity_type = "node";
actual.basis = "GLOBAL";
actual.entity_ids = {10};
actual.component_labels = fesa::displacementComponentLabels();
actual.values = {{{5.0e-7, 100.0005, 0, 0, 0, 0}}};
fesa::CsvDisplacementTable expected;
expected.rows[10] = {10, {0.0, 100.0, 0, 0, 0, 0}};
auto loose = fesa::compareDisplacements(actual, expected, {1.0e-6, 1.0e-5, 1.0});
FESA_CHECK(loose.pass);
FESA_CHECK_NEAR(loose.max_abs_error, 5.0e-4, 1.0e-12);
auto strict = fesa::compareDisplacements(actual, expected, {1.0e-8, 1.0e-8, 1.0});
FESA_CHECK(!strict.pass);
FESA_CHECK(fesa::containsDiagnostic(strict.diagnostics, "FESA-COMPARE-TOLERANCE"));
}
FESA_TEST(displacement_comparator_rejects_wrong_component_labels_and_missing_nodes) {
fesa::FieldOutput actual;
actual.name = "U";
actual.position = "NODAL";
actual.entity_type = "node";
actual.basis = "GLOBAL";
actual.entity_ids = {1};
actual.component_labels = fesa::reactionComponentLabels();
actual.values = {{{0, 0, 0, 0, 0, 0}}};
fesa::CsvDisplacementTable expected;
expected.rows[2] = {2, {0, 0, 0, 0, 0, 0}};
auto compared = fesa::compareDisplacements(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";
actual.position = "NODAL";
actual.entity_type = "node";
actual.basis = "GLOBAL";
actual.entity_ids = {1, 1};
actual.component_labels = fesa::displacementComponentLabels();
actual.values = {{{0, 0, 0, 0, 0, 0}}, {{0, 0, 0, 0, 0, 0}}};
fesa::CsvDisplacementTable expected;
expected.rows[1] = {1, {0, 0, 0, 0, 0, 0}};
auto compared = fesa::compareDisplacements(actual, expected, {1.0e-12, 1.0e-12, 1.0});
FESA_CHECK(!compared.pass);
FESA_CHECK(fesa::containsDiagnostic(compared.diagnostics, "FESA-COMPARE-DUPLICATE-ACTUAL"));
}
FESA_TEST(mitc4_shape_functions_and_stiffness_baseline) {
auto shape = fesa::shapeFunctions(0.25, -0.5);
const fesa::Real sum = shape.n[0] + shape.n[1] + shape.n[2] + shape.n[3];