#include "fesa/Results/Results.hpp" #include #include #include #include #include namespace { void check(bool value, const char* message) { if (!value) { throw std::runtime_error(message); } } std::string sourceRoot() { #ifdef FESA_SOURCE_DIR return FESA_SOURCE_DIR; #else return "."; #endif } fesa::Domain makeDomain() { fesa::Domain domain; domain.nodes[1] = {1, {0.0, 0.0, 0.0}}; domain.nodes[2] = {2, {1.0, 0.0, 0.0}}; domain.nodes[3] = {3, {1.0, 1.0, 0.0}}; domain.nodes[4] = {4, {0.0, 1.0, 0.0}}; domain.elements[10] = {10, fesa::ElementType::MITC4, {1, 2, 3, 4}, "all_elements"}; domain.steps.push_back({"Step-Results", "linear_static"}); domain.boundary_conditions.push_back({"1", 1, 6, 0.0}); return domain; } std::string readTextFile(const std::string& path) { std::ifstream input(path); if (!input) { throw std::runtime_error("failed to open " + path); } std::ostringstream buffer; buffer << input.rdbuf(); return buffer.str(); } } // namespace int main() { const auto domain = makeDomain(); const fesa::DofManager dofs(domain); std::vector u(static_cast(dofs.fullDofCount()), 0.0); std::vector rf(static_cast(dofs.fullDofCount()), 0.0); u[static_cast(dofs.fullIndex(2, fesa::Dof::UZ))] = -0.25; rf[static_cast(dofs.fullIndex(1, fesa::Dof::UZ))] = 3.0; fesa::InMemoryResultsWriter writer; writer.writeLinearStatic(domain, dofs, u, rf); const auto& result = writer.result(); check(result.schema_name == "FESA_RESULTS", "schema name changed"); check(result.schema_version == 1, "schema version changed"); check(result.solver_name == "FESA", "solver name changed"); check(result.dof_convention == "UX,UY,UZ,RX,RY,RZ", "DOF convention changed"); check(result.sign_convention == "Abaqus-compatible", "sign convention changed"); check(result.precision == "double", "precision metadata changed"); check(result.index_type == "int64", "index metadata changed"); check(result.node_ids.size() == domain.nodes.size(), "node model mirror changed"); check(result.element_types == std::vector({"MITC4"}), "element type label changed"); check(result.steps.size() == 1, "result step count changed"); check(result.steps[0].name == "Step-Results", "step name changed"); check(result.steps[0].frames.size() == 1, "result frame count changed"); const auto& frame = result.steps[0].frames[0]; check(frame.frame_id == 0, "Phase 1 frame id changed"); check(frame.increment == 1, "Phase 1 increment changed"); check(frame.iteration == 0, "Phase 1 iteration changed"); check(frame.converged, "Phase 1 convergence flag changed"); check(frame.field_outputs.count("U") == 1, "U field missing"); check(frame.field_outputs.count("RF") == 1, "RF field missing"); const auto& u_field = frame.field_outputs.at("U"); const auto& rf_field = frame.field_outputs.at("RF"); check(u_field.position == "NODAL" && u_field.entity_type == "node" && u_field.basis == "GLOBAL", "U field metadata changed"); check(rf_field.position == "NODAL" && rf_field.entity_type == "node" && rf_field.basis == "GLOBAL", "RF field metadata changed"); check(u_field.component_labels == fesa::displacementComponentLabels(), "U component labels changed"); check(rf_field.component_labels == fesa::reactionComponentLabels(), "RF component labels changed"); const auto required_columns = fesa::displacementCsvRequiredColumns(); check(required_columns.size() == 7, "required displacement CSV column count changed"); 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"); check(fesa::containsDiagnostic(missing_header.diagnostics, "FESA-CSV-MISSING-COLUMN"), "missing CSV header diagnostic changed"); const auto duplicate_node = 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"); check(fesa::containsDiagnostic(duplicate_node.diagnostics, "FESA-CSV-DUPLICATE-NODE"), "duplicate CSV node diagnostic changed"); fesa::CsvDisplacementTable expected; expected.rows[1] = {1, {0, 0, 0, 0, 0, 0}}; expected.rows[2] = {2, {0, 0, -0.25, 0, 0, 0}}; expected.rows[3] = {3, {0, 0, 0, 0, 0, 0}}; expected.rows[4] = {4, {0, 0, 0, 0, 0, 0}}; 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; }