refactor: extract assembly analysis workflow

This commit is contained in:
NINI
2026-05-05 23:33:01 +09:00
parent 918e219c48
commit 9a91696014
11 changed files with 559 additions and 285 deletions
+129
View File
@@ -0,0 +1,129 @@
#include "fesa/Analysis/Analysis.hpp"
#include <cmath>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
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<fesa::Real> solution) : solution_(std::move(solution)) {}
fesa::SolveResult solve(fesa::DenseMatrix a, std::vector<fesa::Real> 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<fesa::Real> captured_b;
private:
std::vector<fesa::Real> 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<std::size_t>(dofs.fullIndex(2, fesa::Dof::UZ))], 0.25, 1.0e-15,
"node 2 reconstructed UZ changed");
checkNear(injected.state.u_full[static_cast<std::size_t>(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;
}
+83
View File
@@ -0,0 +1,83 @@
#include "fesa/Assembly/Assembly.hpp"
#include <cmath>
#include <stdexcept>
#include <vector>
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);
}
}
fesa::Domain assemblyDomain() {
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[1] = {1, fesa::ElementType::MITC4, {1, 2, 3, 4}, "EALL"};
domain.element_sets["eall"] = {"EALL", {1}};
domain.node_sets["left"] = {"LEFT", {1, 4}};
domain.node_sets["right"] = {"RIGHT", {2, 3}};
domain.materials["steel"] = {"STEEL", 1000.0, 0.30};
domain.shell_sections.push_back({"EALL", "STEEL", 0.1});
domain.boundary_conditions.push_back({"LEFT", 1, 6, 0.0});
domain.boundary_conditions.push_back({"RIGHT", 1, 2, 0.0});
domain.boundary_conditions.push_back({"RIGHT", 4, 6, 0.0});
domain.loads.push_back({"2", 3, -1.0});
domain.loads.push_back({"3", 3, -1.0});
return domain;
}
} // namespace
int main() {
const auto domain = assemblyDomain();
const fesa::DofManager dofs(domain);
const auto pattern = fesa::buildReducedSparsePattern(domain, dofs);
check(pattern.equation_count == dofs.freeDofCount(), "reduced sparse pattern equation count changed");
check(pattern.nonzeroCount() > 0, "reduced sparse pattern should remain non-empty");
const auto assembly = fesa::assembleSystem(domain, dofs);
check(assembly.ok(), "assembly should remain valid");
check(assembly.k_full.rows() == dofs.fullDofCount(), "full stiffness row count changed");
check(assembly.k_full.cols() == dofs.fullDofCount(), "full stiffness column count changed");
check(static_cast<fesa::LocalIndex>(assembly.f_full.size()) == dofs.fullDofCount(), "full load size changed");
checkNear(assembly.f_full[static_cast<std::size_t>(dofs.fullIndex(2, fesa::Dof::UZ))], -1.0, 1.0e-15,
"node 2 UZ load changed");
checkNear(assembly.f_full[static_cast<std::size_t>(dofs.fullIndex(3, fesa::Dof::UZ))], -1.0, 1.0e-15,
"node 3 UZ load changed");
const auto reduced = fesa::projectToReducedSystem(assembly, dofs);
check(reduced.ok(), "reduced system projection should remain valid");
check(reduced.k.rows() == dofs.freeDofCount(), "reduced stiffness row count changed");
check(reduced.k.cols() == dofs.freeDofCount(), "reduced stiffness column count changed");
check(static_cast<fesa::LocalIndex>(reduced.f.size()) == dofs.freeDofCount(), "reduced load size changed");
check(reduced.free_full_indices == dofs.freeFullIndices(), "free full-index map changed");
checkNear(reduced.f[0], -1.0, 1.0e-15, "first reduced load changed");
checkNear(reduced.f[1], -1.0, 1.0e-15, "second reduced load changed");
fesa::DenseMatrix k(3, 3);
k(0, 0) = 4.0;
k(1, 1) = 5.0;
k(2, 2) = 6.0;
const std::vector<fesa::Real> u = {0.5, -0.25, 2.0};
const std::vector<fesa::Real> f = {1.0, 2.0, -3.0};
const auto rf = fesa::recoverFullReaction(k, u, f);
check(rf.size() == 3, "full reaction size changed");
checkNear(rf[0], 1.0, 1.0e-15, "RF component 0 changed");
checkNear(rf[1], -3.25, 1.0e-15, "RF component 1 changed");
checkNear(rf[2], 15.0, 1.0e-15, "RF component 2 changed");
return 0;
}