feat: add linear static workflow model

This commit is contained in:
NINI
2026-05-04 23:45:13 +09:00
parent d373969732
commit 948a9448ff
5 changed files with 259 additions and 8 deletions
+133
View File
@@ -327,6 +327,24 @@ FESA_TEST(parser_accepts_phase1_subset) {
FESA_CHECK(parsed.domain.loads.size() == 2);
}
FESA_TEST(analysis_model_activates_single_linear_static_step) {
auto domain = parsedPhase1Domain();
const auto node_count = domain.nodes.size();
const auto element_count = domain.elements.size();
const auto model = fesa::buildLinearStaticAnalysisModel(domain);
FESA_CHECK(model.ok());
FESA_CHECK(model.step.name == "Step-1");
FESA_CHECK(model.step.analysis_type == "linear_static");
FESA_CHECK(model.active_element_ids == std::vector<fesa::GlobalId>({1}));
FESA_CHECK(model.active_boundary_condition_indices == std::vector<std::size_t>({0, 1, 2}));
FESA_CHECK(model.active_load_indices == std::vector<std::size_t>({0, 1}));
FESA_CHECK(model.active_shell_section_indices == std::vector<std::size_t>({0}));
FESA_CHECK(model.active_material_keys == std::vector<std::string>({"steel"}));
FESA_CHECK(domain.nodes.size() == node_count);
FESA_CHECK(domain.elements.size() == element_count);
}
FESA_TEST(parser_accepts_repeated_and_generated_sets) {
const std::string text = R"inp(
*Node
@@ -519,6 +537,21 @@ FESA_TEST(quad02_phase1_normalized_input_uses_supported_subset) {
FESA_CHECK(parsed.domain.shell_sections.front().thickness == 1.0);
}
FESA_TEST(analysis_model_rejects_multiple_steps_for_phase1_execution) {
const std::string text = phase1Input() + R"inp(
*Step, name=Step-2
*Static
*End Step
)inp";
fesa::AbaqusInputParser parser;
auto parsed = parser.parseString(text);
FESA_CHECK(parsed.ok());
FESA_CHECK(parsed.domain.steps.size() == 2);
const auto model = fesa::buildLinearStaticAnalysisModel(parsed.domain);
FESA_CHECK(!model.ok());
FESA_CHECK(fesa::containsDiagnostic(model.diagnostics, "FESA-ANALYSIS-MULTIPLE-STEPS"));
}
FESA_TEST(domain_validation_reports_missing_property_and_targets) {
fesa::Domain domain;
domain.nodes[1] = {1, {0, 0, 0}};
@@ -881,6 +914,7 @@ FESA_TEST(results_writer_uses_step_frame_fields_for_u_and_rf) {
FESA_CHECK(result.sign_convention == "Abaqus-compatible");
FESA_CHECK(result.precision == "double");
FESA_CHECK(result.index_type == "int64");
FESA_CHECK(result.element_types == std::vector<std::string>({"MITC4"}));
FESA_CHECK(result.steps.size() == 1);
FESA_CHECK(result.steps[0].name == "Step-1");
FESA_CHECK(result.steps[0].frames.size() == 1);
@@ -1705,6 +1739,105 @@ FESA_TEST(linear_static_analysis_solves_u_and_recovers_full_vector_rf) {
FESA_CHECK_NEAR(total_rf_z, 2.0, 1.0e-8);
}
FESA_TEST(linear_static_input_workflow_produces_schema_ready_u_and_rf_fields) {
const auto result = fesa::runLinearStaticInputString(phase1Input(), "phase1-workflow.inp");
FESA_CHECK(result.ok());
FESA_CHECK(result.model.ok());
FESA_CHECK(result.model.step.name == "Step-1");
FESA_CHECK(result.model.active_element_ids == std::vector<fesa::GlobalId>({1}));
FESA_CHECK(result.state.converged);
FESA_CHECK(result.state.u_full.size() == 24);
FESA_CHECK(result.state.f_external_full.size() == 24);
FESA_CHECK(result.state.f_internal_full.size() == 24);
FESA_CHECK(result.state.reaction_full.size() == 24);
const auto& result_file = result.result_file;
FESA_CHECK(result_file.schema_name == "FESA_RESULTS");
FESA_CHECK(result_file.schema_version == 1);
FESA_CHECK(result_file.solver_name == "FESA");
FESA_CHECK(result_file.dof_convention == "UX,UY,UZ,RX,RY,RZ");
FESA_CHECK(result_file.sign_convention == "Abaqus-compatible");
FESA_CHECK(result_file.precision == "double");
FESA_CHECK(result_file.index_type == "int64");
FESA_CHECK(result_file.node_ids == std::vector<fesa::GlobalId>({1, 2, 3, 4}));
FESA_CHECK(result_file.element_ids == std::vector<fesa::GlobalId>({1}));
FESA_CHECK(result_file.element_types == std::vector<std::string>({"MITC4"}));
FESA_CHECK(result_file.connectivity.front() == (std::array<fesa::GlobalId, 4>{1, 2, 3, 4}));
FESA_CHECK(result_file.steps.size() == 1);
FESA_CHECK(result_file.steps[0].name == "Step-1");
FESA_CHECK(result_file.steps[0].frames.size() == 1);
const auto& frame = result_file.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 = frame.field_outputs.at("U");
const auto& rf = frame.field_outputs.at("RF");
FESA_CHECK(u.component_labels == fesa::displacementComponentLabels());
FESA_CHECK(rf.component_labels == fesa::reactionComponentLabels());
FESA_CHECK(u.entity_ids == result_file.node_ids);
FESA_CHECK(rf.entity_ids == result_file.node_ids);
FESA_CHECK(u.values.size() == result_file.node_ids.size());
FESA_CHECK(rf.values.size() == result_file.node_ids.size());
fesa::Real total_rf_z = 0.0;
for (const auto& values : rf.values) {
total_rf_z += values[2];
}
FESA_CHECK_NEAR(total_rf_z, 2.0, 1.0e-8);
FESA_CHECK_NEAR(u.values[0][2], 0.0, 1.0e-15);
FESA_CHECK_NEAR(u.values[3][2], 0.0, 1.0e-15);
}
FESA_TEST(linear_static_input_workflow_routes_parse_errors) {
const std::string text = R"inp(
*Part, name=P1
*Node
1, 0, 0, 0
*Element, type=S4R
1, 1, 2, 3, 4
*Step, nlgeom=YES
*Static
*End Step
)inp";
const auto result = fesa::runLinearStaticInputString(text, "unsupported-workflow.inp");
FESA_CHECK(!result.ok());
FESA_CHECK(!result.state.converged);
FESA_CHECK(fesa::containsDiagnostic(result.diagnostics, "FESA-PARSE-UNSUPPORTED-KEYWORD"));
FESA_CHECK(fesa::containsDiagnostic(result.diagnostics, "FESA-PARSE-UNSUPPORTED-ELEMENT"));
FESA_CHECK(fesa::containsDiagnostic(result.diagnostics, "FESA-PARSE-UNSUPPORTED-NLGEOM"));
}
FESA_TEST(linear_static_input_workflow_routes_validation_errors_without_bypassing_validator) {
const std::string text = R"inp(
*Node
1, 0, 0, 0
2, 1, 0, 0
3, 1, 1, 0
4, 0, 1, 0
*Element, type=S4
1, 1, 2, 3, 4
*Nset, nset=FIXED
1, 4
*Boundary
FIXED, 1, 6
*Cload
2, 3, -1
*Step, name=Step-1
*Static
*End Step
)inp";
const auto result = fesa::runLinearStaticInputString(text, "missing-property-workflow.inp");
FESA_CHECK(!result.ok());
FESA_CHECK(!result.state.converged);
FESA_CHECK(fesa::containsDiagnostic(result.diagnostics, "FESA-VALIDATION-MISSING-PROPERTY"));
}
FESA_TEST(linear_static_analysis_uses_solver_adapter_and_reconstructs_full_vectors) {
auto domain = parsedPhase1Domain();
RecordingSolver solver({0.25, -0.50});