#include "fesa/Analysis/Analysis.hpp" #include #include #include #include #include namespace { void check(bool value, const char* message) { if (!value) { throw std::runtime_error(message); } } void checkNear(fesa::Real actual, fesa::Real expected, fesa::Real tolerance, const char* message) { if (std::fabs(actual - expected) > tolerance) { throw std::runtime_error(message); } } std::string phase1Input() { return R"inp( *Node 1, 0, 0, 0 2, 1, 0, 0 3, 1, 1, 0 4, 0, 1, 0 *Element, type=S4, elset=EALL 1, 1, 2, 3, 4 *Nset, nset=LEFT 1, 4 *Nset, nset=RIGHT 2, 3 *Elset, elset=EALL 1 *Material, name=STEEL *Elastic 1000.0, 0.3 *Shell Section, elset=EALL, material=STEEL 0.1 *Boundary LEFT, 1, 6, 0 RIGHT, 1, 2, 0 RIGHT, 4, 6, 0 *Cload 2, 3, -1 3, 3, -1 *Step, name=Step-1 *Static *End Step )inp"; } class RecordingSolver final : public fesa::LinearSolver { public: explicit RecordingSolver(std::vector solution) : solution_(std::move(solution)) {} fesa::SolveResult solve(fesa::DenseMatrix a, std::vector b) const override { called = true; captured_a = std::move(a); captured_b = std::move(b); return {solution_, {}}; } mutable bool called = false; mutable fesa::DenseMatrix captured_a; mutable std::vector captured_b; private: std::vector solution_; }; } // namespace int main() { const auto workflow = fesa::runLinearStaticInputString(phase1Input(), "analysis-module.inp"); check(workflow.ok(), "linear static input workflow should remain valid"); check(workflow.model.ok(), "analysis model should remain valid"); check(workflow.model.step.name == "Step-1", "analysis step name changed"); check(workflow.state.converged, "analysis convergence flag changed"); check(workflow.state.u_full.size() == 24, "full displacement size changed"); check(workflow.state.f_external_full.size() == 24, "full external-force size changed"); check(workflow.state.f_internal_full.size() == 24, "full internal-force size changed"); check(workflow.state.reaction_full.size() == 24, "full reaction size changed"); check(workflow.result_file.steps.size() == 1, "result step count changed"); const auto& frame = workflow.result_file.steps.front().frames.front(); check(frame.field_outputs.count("U") == 1, "U field output missing"); check(frame.field_outputs.count("RF") == 1, "RF field output missing"); check(frame.field_outputs.at("U").component_labels == fesa::displacementComponentLabels(), "U labels changed"); check(frame.field_outputs.at("RF").component_labels == fesa::reactionComponentLabels(), "RF labels changed"); fesa::Real total_rf_z = 0.0; for (const auto& values : frame.field_outputs.at("RF").values) { total_rf_z += values[2]; } checkNear(total_rf_z, 2.0, 1.0e-8, "full-vector RF balance changed"); fesa::AbaqusInputParser parser; const auto parsed = parser.parseString(phase1Input()); check(parsed.ok(), "phase1 analysis input parse changed"); RecordingSolver solver({0.25, -0.50}); fesa::LinearStaticAnalysis analysis(&solver); const auto injected = analysis.run(parsed.domain); check(injected.ok(), "solver-injected analysis should remain valid"); check(solver.called, "linear solver adapter injection changed"); check(solver.captured_a.rows() == 2 && solver.captured_a.cols() == 2, "captured reduced stiffness size changed"); check(solver.captured_b.size() == 2, "captured reduced load size changed"); const fesa::DofManager dofs(parsed.domain); checkNear(injected.state.u_full[static_cast(dofs.fullIndex(2, fesa::Dof::UZ))], 0.25, 1.0e-15, "node 2 reconstructed UZ changed"); checkNear(injected.state.u_full[static_cast(dofs.fullIndex(3, fesa::Dof::UZ))], -0.50, 1.0e-15, "node 3 reconstructed UZ changed"); for (std::size_t i = 0; i < injected.state.reaction_full.size(); ++i) { checkNear(injected.state.reaction_full[i], injected.state.f_internal_full[i] - injected.state.f_external_full[i], 1.0e-10, "full-vector reaction formula changed"); } const auto parse_error = fesa::runLinearStaticInputString("*Part, name=P\n", "unsupported.inp"); check(!parse_error.ok(), "parse errors should still be routed through analysis result"); check(fesa::containsDiagnostic(parse_error.diagnostics, "FESA-PARSE-UNSUPPORTED-KEYWORD"), "parse diagnostic routing changed"); return 0; }