feat(linear-static-3d-euler-beam): step 24 - linear-static-cli

This commit is contained in:
KOKO\Mimi
2026-08-09 22:54:59 +09:00
parent 286424bfde
commit a9d93bb206
14 changed files with 3919 additions and 31 deletions
+59
View File
@@ -3,6 +3,8 @@ add_library(
STATIC
analysis/analysis_model.cpp
analysis/analysis_state.cpp
analysis/linear_static_analysis.cpp
app/fesa_application.cpp
assembly/load_assembler.cpp
assembly/parallel_for.cpp
assembly/sparse_assembler.cpp
@@ -43,3 +45,60 @@ target_compile_options(
PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:/W4 /WX>
)
add_executable(fesa_cli app/main.cpp)
set_target_properties(fesa_cli PROPERTIES OUTPUT_NAME fesa)
target_link_libraries(fesa_cli PRIVATE fesa_solver)
target_compile_options(
fesa_cli
PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:/W4 /WX>
)
if(WIN32)
if(NOT TARGET MKL::mkl_intel_thread OR NOT TARGET MKL::mkl_core OR
NOT OMP_DLL_DIR OR NOT OMP_DLLNAME)
message(FATAL_ERROR "The oneMKL runtime files required by fesa.exe were not resolved")
endif()
if(NOT EXISTS "${OMP_DLL_DIR}/libmmd.dll")
message(FATAL_ERROR "The Intel runtime required by the selected HDF5 DLL was not resolved")
endif()
file(GLOB _fesa_cli_mkl_dispatch_dlls "${MKL_ROOT}/bin/mkl_def.*.dll")
if(NOT _fesa_cli_mkl_dispatch_dlls)
message(FATAL_ERROR "The oneMKL default dispatch runtime was not resolved")
endif()
list(SORT _fesa_cli_mkl_dispatch_dlls COMPARE NATURAL ORDER DESCENDING)
list(GET _fesa_cli_mkl_dispatch_dlls 0 _fesa_cli_mkl_dispatch_dll)
# Keep the installed CLI runnable without assuming oneAPI or HDF5 on PATH.
add_custom_command(
TARGET fesa_cli
POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
"$<TARGET_FILE:TBB::tbb>"
"$<TARGET_FILE_DIR:fesa_cli>"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
"$<TARGET_FILE:MKL::mkl_intel_thread>"
"$<TARGET_FILE_DIR:fesa_cli>"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
"$<TARGET_FILE:MKL::mkl_core>"
"$<TARGET_FILE_DIR:fesa_cli>"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
"${OMP_DLL_DIR}/${OMP_DLLNAME}"
"$<TARGET_FILE_DIR:fesa_cli>"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
"${OMP_DLL_DIR}/libmmd.dll"
"$<TARGET_FILE_DIR:fesa_cli>"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
"${_fesa_cli_mkl_dispatch_dll}"
"$<TARGET_FILE_DIR:fesa_cli>"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
"$<TARGET_RUNTIME_DLLS:fesa_cli>"
"$<TARGET_FILE_DIR:fesa_cli>"
COMMAND_EXPAND_LISTS
)
unset(_fesa_cli_mkl_dispatch_dll)
unset(_fesa_cli_mkl_dispatch_dlls)
endif()
@@ -0,0 +1,170 @@
#include "fesa/analysis/linear_static_analysis.hpp"
#include "fesa/assembly/load_assembler.hpp"
#include "fesa/assembly/parallel_for.hpp"
#include "fesa/assembly/sparse_assembler.hpp"
#include "fesa/io/abaqus/domain_mapper.hpp"
#include "fesa/io/abaqus/input_reader.hpp"
#include "fesa/results/result_recovery.hpp"
#include "fesa/results/results_writer.hpp"
#include "fesa/solvers/linear/linear_solver.hpp"
#include <utility>
namespace fesa {
Status Analysis::run(const AnalysisRequest& request) {
Status status = initialize(request);
if (!status.isOk()) {
return status;
}
status = buildAnalysisModel();
if (!status.isOk()) {
return status;
}
status = buildDofMapAndSparsePattern();
if (!status.isOk()) {
return status;
}
status = assembleAndPartitionStiffness();
if (!status.isOk()) {
return status;
}
status = factorize();
if (!status.isOk()) {
return status;
}
status = assembleLoadsAndEffectiveRhs();
if (!status.isOk()) {
return status;
}
status = substituteAndReconstruct();
if (!status.isOk()) {
return status;
}
return recoverAndWriteResults();
}
LinearStaticAnalysis::LinearStaticAnalysis(
const ParallelFor& parallelFor,
LinearSolver& linearSolver,
ResultsWriter& resultsWriter)
: parallelFor_{parallelFor},
linearSolver_{linearSolver},
resultsWriter_{resultsWriter} {}
Status LinearStaticAnalysis::initialize(const AnalysisRequest& request) {
// Clear dependent objects in reverse ownership order so a reused analysis
// never exposes a view into a Domain from an earlier run.
effectiveRhs_.reset();
partitionedStiffness_.reset();
fullStiffness_.reset();
state_.reset();
dofs_.reset();
model_.reset();
domain_.reset();
diagnostics_.clear();
request_ = request;
const auto parsed = AbaqusInputReader{}.read(request_.inputPath);
if (!parsed.hasValue()) {
return parsed.status();
}
auto domain = AbaqusDomainMapper{}.map(parsed.value());
if (!domain.hasValue()) {
return domain.status();
}
domain_ = std::make_unique<Domain>(std::move(domain.value()));
diagnostics_ = domain_->warnings();
sortDiagnostics(diagnostics_);
return Status::ok();
}
Status LinearStaticAnalysis::buildAnalysisModel() {
auto model = AnalysisModel::create(*domain_);
if (!model.hasValue()) {
return model.status();
}
model_ = std::make_unique<AnalysisModel>(std::move(model.value()));
return Status::ok();
}
Status LinearStaticAnalysis::buildDofMapAndSparsePattern() {
auto dofs = DofManager::create(*model_);
if (!dofs.hasValue()) {
return dofs.status();
}
dofs_ = std::make_unique<DofManager>(std::move(dofs.value()));
state_ = std::make_unique<AnalysisState>(
AnalysisState::create(*dofs_, {"Step-1", 0U}));
return Status::ok();
}
Status LinearStaticAnalysis::assembleAndPartitionStiffness() {
auto stiffness = SparseAssembler::assembleStiffness(
*model_, *dofs_, parallelFor_);
if (!stiffness.hasValue()) {
return stiffness.status();
}
fullStiffness_ =
std::make_unique<SparseMatrix>(std::move(stiffness.value()));
auto partitioned = EssentialConstraints::partition(
*fullStiffness_, *dofs_);
if (!partitioned.hasValue()) {
return partitioned.status();
}
partitionedStiffness_ = std::make_unique<PartitionedStiffness>(
std::move(partitioned.value()));
return Status::ok();
}
Status LinearStaticAnalysis::factorize() {
// This call intentionally precedes all load assembly in Analysis::run.
return linearSolver_.factorize(partitionedStiffness_->kff);
}
Status LinearStaticAnalysis::assembleLoadsAndEffectiveRhs() {
auto fullLoad = LoadAssembler::assembleFullNodalLoad(*model_, *dofs_);
if (!fullLoad.hasValue()) {
return fullLoad.status();
}
state_->externalForce() = std::move(fullLoad.value());
auto rhs = LoadAssembler::effectiveFreeRhs(
state_->externalForce(),
partitionedStiffness_->kfc,
dofs_->prescribedValues(),
*dofs_);
if (!rhs.hasValue()) {
return rhs.status();
}
effectiveRhs_ = std::make_unique<Vector>(std::move(rhs.value()));
return Status::ok();
}
Status LinearStaticAnalysis::substituteAndReconstruct() {
Vector freeDisplacement{dofs_->freeDofCount()};
const Status solveStatus =
linearSolver_.solve(*effectiveRhs_, freeDisplacement);
if (!solveStatus.isOk()) {
return solveStatus;
}
state_->displacement() = EssentialConstraints::reconstructFull(
freeDisplacement, dofs_->prescribedValues(), *dofs_);
return Status::ok();
}
Status LinearStaticAnalysis::recoverAndWriteResults() {
const Status recoveryStatus = ResultRecovery::recover(
*model_, *dofs_, *fullStiffness_, *state_);
if (!recoveryStatus.isOk()) {
return recoveryStatus;
}
return resultsWriter_.write(
request_.outputPath, *domain_, *state_, diagnostics_);
}
} // namespace fesa
+112
View File
@@ -0,0 +1,112 @@
#include "fesa/app/fesa_application.hpp"
#include "fesa/analysis/linear_static_analysis.hpp"
#include "fesa/assembly/parallel_for.hpp"
#include "fesa/core/diagnostic.hpp"
#include "fesa/io/hdf5/hdf5_results_writer.hpp"
#include "fesa/solvers/linear/mkl_pardiso_solver.hpp"
#include <filesystem>
#include <iostream>
#include <string>
#include <vector>
namespace fesa {
namespace {
constexpr int kSuccessExitCode = 0;
constexpr int kUsageExitCode = 2;
constexpr int kInputExitCode = 3;
constexpr int kModelExitCode = 4;
constexpr int kSolverExitCode = 5;
constexpr int kOutputExitCode = 6;
bool startsWithOption(const std::string& argument) {
return !argument.empty() && argument.front() == '-';
}
Diagnostic usageDiagnostic() {
return {
Severity::error,
"cli-usage",
{{}, 0U},
"",
"",
"Usage: fesa.exe <model.inp> [--output <results.h5>]."};
}
const char* severityName(const Severity severity) {
return severity == Severity::warning ? "warning" : "error";
}
void writeDiagnostics(std::vector<Diagnostic> diagnostics) {
sortDiagnostics(diagnostics);
for (const auto& diagnostic : diagnostics) {
// Stable field labels and tab separators keep empty source fields
// explicit without depending on locale-specific formatting.
std::cerr
<< "severity=" << severityName(diagnostic.severity)
<< '\t' << "code=" << diagnostic.code
<< '\t' << "file="
<< diagnostic.location.file.generic_u8string()
<< '\t' << "line=" << diagnostic.location.line
<< '\t' << "keyword=" << diagnostic.keyword
<< '\t' << "entity_identity=" << diagnostic.entityIdentity
<< '\t' << "message=" << diagnostic.message
<< '\n';
}
}
int exitCodeFor(const Status& status) {
switch (status.failureCategory().value_or(FailureCategory::input)) {
case FailureCategory::input:
return kInputExitCode;
case FailureCategory::model:
return kModelExitCode;
case FailureCategory::solver:
return kSolverExitCode;
case FailureCategory::output:
return kOutputExitCode;
}
return kInputExitCode;
}
} // namespace
int FesaApplication::run(const std::vector<std::string>& arguments) {
const bool defaultOutputForm =
arguments.size() == 1U &&
!arguments[0U].empty() &&
!startsWithOption(arguments[0U]);
const bool explicitOutputForm =
arguments.size() == 3U &&
!arguments[0U].empty() &&
!startsWithOption(arguments[0U]) &&
arguments[1U] == "--output" &&
!arguments[2U].empty();
if (!defaultOutputForm && !explicitOutputForm) {
writeDiagnostics({usageDiagnostic()});
return kUsageExitCode;
}
AnalysisRequest request;
request.inputPath = arguments[0U];
request.outputPath = explicitOutputForm
? std::filesystem::path{arguments[2U]}
: std::filesystem::current_path() / "results.h5";
TbbParallelFor parallelFor;
MklPardisoSolver linearSolver;
Hdf5ResultsWriter resultsWriter;
LinearStaticAnalysis analysis{
parallelFor, linearSolver, resultsWriter};
const Status status = analysis.run(request);
if (status.isOk()) {
return kSuccessExitCode;
}
writeDiagnostics(status.diagnostics());
return exitCodeFor(status);
}
} // namespace fesa
+17
View File
@@ -0,0 +1,17 @@
#include "fesa/app/fesa_application.hpp"
#include <cstddef>
#include <string>
#include <vector>
int main(const int argc, char* argv[]) {
std::vector<std::string> arguments;
if (argc > 1) {
arguments.reserve(static_cast<std::size_t>(argc - 1));
}
// The application boundary receives only operands and options, not argv[0].
for (int index = 1; index < argc; ++index) {
arguments.emplace_back(argv[index]);
}
return fesa::FesaApplication{}.run(arguments);
}