feat(linear-static-3d-euler-beam): step 24 - linear-static-cli
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user