feat(cpp-object-oriented-modular-refactoring): step 20 - analysis-hierarchy
This commit is contained in:
@@ -0,0 +1,30 @@
|
|||||||
|
#ifndef FESA_ANALYSIS_ANALYSIS_H_
|
||||||
|
#define FESA_ANALYSIS_ANALYSIS_H_
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include "fesa/core/status.h"
|
||||||
|
|
||||||
|
namespace fesa {
|
||||||
|
|
||||||
|
/// @brief Carries input and authoritative output paths for one analysis run.
|
||||||
|
struct AnalysisRequest {
|
||||||
|
std::filesystem::path input_path;
|
||||||
|
std::filesystem::path output_path;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Defines the minimal execution contract shared by analysis procedures.
|
||||||
|
class Analysis {
|
||||||
|
public:
|
||||||
|
virtual ~Analysis() = default;
|
||||||
|
|
||||||
|
/// @brief Executes one procedure for the supplied input and output paths.
|
||||||
|
/// @param request Input and authoritative output paths for this run.
|
||||||
|
/// @return The concrete procedure result without changing its failure
|
||||||
|
/// category.
|
||||||
|
virtual Status Run(const AnalysisRequest& request) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace fesa
|
||||||
|
|
||||||
|
#endif // FESA_ANALYSIS_ANALYSIS_H_
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
#ifndef FESA_ANALYSIS_LINEAR_STATIC_ANALYSIS_H_
|
#ifndef FESA_ANALYSIS_LINEAR_STATIC_ANALYSIS_H_
|
||||||
#define FESA_ANALYSIS_LINEAR_STATIC_ANALYSIS_H_
|
#define FESA_ANALYSIS_LINEAR_STATIC_ANALYSIS_H_
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/analysis/analysis.h"
|
||||||
#include "fesa/analysis/analysis_model.h"
|
#include "fesa/analysis/analysis_model.h"
|
||||||
#include "fesa/analysis/analysis_state.h"
|
#include "fesa/analysis/analysis_state.h"
|
||||||
#include "fesa/constraints/essential_constraint_policy.h"
|
#include "fesa/constraints/essential_constraint_policy.h"
|
||||||
#include "fesa/core/status.h"
|
#include "fesa/elements/element.h"
|
||||||
#include "fesa/fem/dof_manager.h"
|
#include "fesa/fem/dof_manager.h"
|
||||||
#include "fesa/math/sparse_matrix.h"
|
#include "fesa/math/sparse_matrix.h"
|
||||||
#include "fesa/math/vector.h"
|
#include "fesa/math/vector.h"
|
||||||
@@ -20,41 +20,6 @@ class LinearSolver;
|
|||||||
class ParallelFor;
|
class ParallelFor;
|
||||||
class ResultsWriter;
|
class ResultsWriter;
|
||||||
|
|
||||||
/// @brief Carries input and authoritative output paths for one analysis run.
|
|
||||||
struct AnalysisRequest {
|
|
||||||
std::filesystem::path input_path;
|
|
||||||
std::filesystem::path output_path;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// @brief Executes the current approved V0 analysis lifecycle.
|
|
||||||
class Analysis {
|
|
||||||
public:
|
|
||||||
virtual ~Analysis() = default;
|
|
||||||
|
|
||||||
/// @brief Runs the eight current lifecycle stages in their approved order.
|
|
||||||
/// @param request Input and output paths for this run.
|
|
||||||
/// @return The first stage failure or successful result finalization.
|
|
||||||
Status Run(const AnalysisRequest& request);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/// @brief Initializes owned input and Domain state for a run.
|
|
||||||
virtual Status Initialize(const AnalysisRequest& request) = 0;
|
|
||||||
/// @brief Builds the non-owning active-model view.
|
|
||||||
virtual Status BuildAnalysisModel() = 0;
|
|
||||||
/// @brief Builds stable DOF numbering and the sparse structural pattern.
|
|
||||||
virtual Status BuildDofMapAndSparsePattern() = 0;
|
|
||||||
/// @brief Assembles full stiffness and stable constraint partitions.
|
|
||||||
virtual Status AssembleAndPartitionStiffness() = 0;
|
|
||||||
/// @brief Factorizes Kff before any load assembly.
|
|
||||||
virtual Status Factorize() = 0;
|
|
||||||
/// @brief Assembles loads and forms Ff-Kfc*dc without solving.
|
|
||||||
virtual Status AssembleLoadsAndEffectiveRhs() = 0;
|
|
||||||
/// @brief Substitutes the retained factorization and reconstructs full d.
|
|
||||||
virtual Status SubstituteAndReconstruct() = 0;
|
|
||||||
/// @brief Recovers a complete candidate and writes authoritative results.
|
|
||||||
virtual Status RecoverAndWriteResults() = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// @brief Orchestrates the single-step linear-static procedure.
|
/// @brief Orchestrates the single-step linear-static procedure.
|
||||||
/// @note Factorization, substitution, recovery, and writing remain separately
|
/// @note Factorization, substitution, recovery, and writing remain separately
|
||||||
/// observable through injected backend boundaries.
|
/// observable through injected backend boundaries.
|
||||||
@@ -66,31 +31,36 @@ class LinearStaticAnalysis final : public Analysis {
|
|||||||
LinearSolver& linear_solver,
|
LinearSolver& linear_solver,
|
||||||
ResultsWriter& results_writer);
|
ResultsWriter& results_writer);
|
||||||
|
|
||||||
protected:
|
/// @brief Runs the approved eight-stage linear-static lifecycle.
|
||||||
/// @copydoc Analysis::Initialize
|
/// @return The first stage failure or successful result finalization.
|
||||||
Status Initialize(const AnalysisRequest& request) override;
|
Status Run(const AnalysisRequest& request) override;
|
||||||
/// @copydoc Analysis::BuildAnalysisModel
|
|
||||||
Status BuildAnalysisModel() override;
|
|
||||||
/// @copydoc Analysis::BuildDofMapAndSparsePattern
|
|
||||||
Status BuildDofMapAndSparsePattern() override;
|
|
||||||
/// @copydoc Analysis::AssembleAndPartitionStiffness
|
|
||||||
Status AssembleAndPartitionStiffness() override;
|
|
||||||
/// @copydoc Analysis::Factorize
|
|
||||||
Status Factorize() override;
|
|
||||||
/// @copydoc Analysis::AssembleLoadsAndEffectiveRhs
|
|
||||||
Status AssembleLoadsAndEffectiveRhs() override;
|
|
||||||
/// @copydoc Analysis::SubstituteAndReconstruct
|
|
||||||
Status SubstituteAndReconstruct() override;
|
|
||||||
/// @copydoc Analysis::RecoverAndWriteResults
|
|
||||||
Status RecoverAndWriteResults() override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// @brief Initializes owned input and Domain state for a run candidate.
|
||||||
|
Status InitializeCandidate(const AnalysisRequest& request);
|
||||||
|
/// @brief Builds the non-owning active-model view.
|
||||||
|
Status BuildAnalysisModel();
|
||||||
|
/// @brief Creates runtime elements, stable DOFs, and the sparse pattern.
|
||||||
|
Status BuildDofMapAndSparsePattern();
|
||||||
|
/// @brief Assembles full stiffness and stable constraint partitions.
|
||||||
|
Status AssembleAndPartitionStiffness();
|
||||||
|
/// @brief Factorizes Kff before any load assembly.
|
||||||
|
Status FactorizeFreeSystem();
|
||||||
|
/// @brief Assembles loads and forms Ff-Kfc*dc without solving.
|
||||||
|
Status AssembleLoadsAndEffectiveRhs();
|
||||||
|
/// @brief Substitutes the retained factorization and reconstructs full d.
|
||||||
|
Status SubstituteAndReconstruct();
|
||||||
|
/// @brief Recovers a complete candidate before writing authoritative output.
|
||||||
|
Status RecoverAndWrite();
|
||||||
|
|
||||||
const ParallelFor& parallel_for_;
|
const ParallelFor& parallel_for_;
|
||||||
LinearSolver& linear_solver_;
|
LinearSolver& linear_solver_;
|
||||||
ResultsWriter& results_writer_;
|
ResultsWriter& results_writer_;
|
||||||
AnalysisRequest request_;
|
AnalysisRequest request_;
|
||||||
std::unique_ptr<Domain> domain_;
|
std::unique_ptr<Domain> domain_;
|
||||||
std::unique_ptr<AnalysisModel> model_;
|
std::unique_ptr<AnalysisModel> model_;
|
||||||
|
std::vector<std::unique_ptr<Element>> elements_;
|
||||||
|
ElementView element_view_;
|
||||||
std::unique_ptr<DofManager> dofs_;
|
std::unique_ptr<DofManager> dofs_;
|
||||||
std::unique_ptr<AnalysisState> state_;
|
std::unique_ptr<AnalysisState> state_;
|
||||||
std::unique_ptr<SparseMatrix> full_stiffness_;
|
std::unique_ptr<SparseMatrix> full_stiffness_;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "fesa/assembly/load_assembler.h"
|
#include "fesa/assembly/load_assembler.h"
|
||||||
#include "fesa/assembly/parallel_for.h"
|
#include "fesa/assembly/parallel_for.h"
|
||||||
#include "fesa/assembly/sparse_assembler.h"
|
#include "fesa/assembly/sparse_assembler.h"
|
||||||
|
#include "fesa/elements/element_factory.h"
|
||||||
#include "fesa/io/abaqus/domain_mapper.h"
|
#include "fesa/io/abaqus/domain_mapper.h"
|
||||||
#include "fesa/io/abaqus/input_reader.h"
|
#include "fesa/io/abaqus/input_reader.h"
|
||||||
#include "fesa/results/result_recovery.h"
|
#include "fesa/results/result_recovery.h"
|
||||||
@@ -13,8 +14,8 @@
|
|||||||
|
|
||||||
namespace fesa {
|
namespace fesa {
|
||||||
|
|
||||||
Status Analysis::Run(const AnalysisRequest& request) {
|
Status LinearStaticAnalysis::Run(const AnalysisRequest& request) {
|
||||||
Status status = Initialize(request);
|
Status status = InitializeCandidate(request);
|
||||||
if (!status.IsOk()) {
|
if (!status.IsOk()) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
@@ -30,7 +31,7 @@ Status Analysis::Run(const AnalysisRequest& request) {
|
|||||||
if (!status.IsOk()) {
|
if (!status.IsOk()) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
status = Factorize();
|
status = FactorizeFreeSystem();
|
||||||
if (!status.IsOk()) {
|
if (!status.IsOk()) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
@@ -42,7 +43,7 @@ Status Analysis::Run(const AnalysisRequest& request) {
|
|||||||
if (!status.IsOk()) {
|
if (!status.IsOk()) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
return RecoverAndWriteResults();
|
return RecoverAndWrite();
|
||||||
}
|
}
|
||||||
|
|
||||||
LinearStaticAnalysis::LinearStaticAnalysis(const ParallelFor& parallel_for,
|
LinearStaticAnalysis::LinearStaticAnalysis(const ParallelFor& parallel_for,
|
||||||
@@ -52,7 +53,8 @@ LinearStaticAnalysis::LinearStaticAnalysis(const ParallelFor& parallel_for,
|
|||||||
linear_solver_{linear_solver},
|
linear_solver_{linear_solver},
|
||||||
results_writer_{results_writer} {}
|
results_writer_{results_writer} {}
|
||||||
|
|
||||||
Status LinearStaticAnalysis::Initialize(const AnalysisRequest& request) {
|
Status LinearStaticAnalysis::InitializeCandidate(
|
||||||
|
const AnalysisRequest& request) {
|
||||||
// Clear dependent objects in reverse ownership order so a reused analysis
|
// Clear dependent objects in reverse ownership order so a reused analysis
|
||||||
// never exposes a view into a Domain from an earlier run.
|
// never exposes a view into a Domain from an earlier run.
|
||||||
effective_rhs_.reset();
|
effective_rhs_.reset();
|
||||||
@@ -60,6 +62,8 @@ Status LinearStaticAnalysis::Initialize(const AnalysisRequest& request) {
|
|||||||
full_stiffness_.reset();
|
full_stiffness_.reset();
|
||||||
state_.reset();
|
state_.reset();
|
||||||
dofs_.reset();
|
dofs_.reset();
|
||||||
|
element_view_.clear();
|
||||||
|
elements_.clear();
|
||||||
model_.reset();
|
model_.reset();
|
||||||
domain_.reset();
|
domain_.reset();
|
||||||
diagnostics_.clear();
|
diagnostics_.clear();
|
||||||
@@ -90,11 +94,29 @@ Status LinearStaticAnalysis::BuildAnalysisModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Status LinearStaticAnalysis::BuildDofMapAndSparsePattern() {
|
Status LinearStaticAnalysis::BuildDofMapAndSparsePattern() {
|
||||||
auto dofs = DofManager::Create(*model_);
|
std::vector<std::unique_ptr<Element>> elements;
|
||||||
if (!dofs.HasValue()) {
|
ElementView element_view;
|
||||||
return dofs.GetStatus();
|
elements.reserve(model_->ActiveElements().size());
|
||||||
|
element_view.reserve(model_->ActiveElements().size());
|
||||||
|
const ElementFactory factory;
|
||||||
|
for (const EntityIndex element_index : model_->ActiveElements()) {
|
||||||
|
auto element = factory.Create(domain_->Elements()[element_index], *domain_);
|
||||||
|
if (!element.HasValue()) {
|
||||||
|
return element.GetStatus();
|
||||||
|
}
|
||||||
|
element_view.emplace_back(*element.Value());
|
||||||
|
elements.push_back(std::move(element.Value()));
|
||||||
}
|
}
|
||||||
dofs_ = std::make_unique<DofManager>(std::move(dofs.Value()));
|
|
||||||
|
DofManager dofs;
|
||||||
|
const Status dof_status = dofs.Build(*model_, element_view);
|
||||||
|
if (!dof_status.IsOk()) {
|
||||||
|
return dof_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
elements_ = std::move(elements);
|
||||||
|
element_view_ = std::move(element_view);
|
||||||
|
dofs_ = std::make_unique<DofManager>(std::move(dofs));
|
||||||
state_ = std::make_unique<AnalysisState>(
|
state_ = std::make_unique<AnalysisState>(
|
||||||
AnalysisState::Create(*dofs_, {"Step-1", 0U}));
|
AnalysisState::Create(*dofs_, {"Step-1", 0U}));
|
||||||
return Status::Ok();
|
return Status::Ok();
|
||||||
@@ -102,7 +124,7 @@ Status LinearStaticAnalysis::BuildDofMapAndSparsePattern() {
|
|||||||
|
|
||||||
Status LinearStaticAnalysis::AssembleAndPartitionStiffness() {
|
Status LinearStaticAnalysis::AssembleAndPartitionStiffness() {
|
||||||
auto stiffness =
|
auto stiffness =
|
||||||
SparseAssembler::AssembleStiffness(*model_, *dofs_, parallel_for_);
|
SparseAssembler::Assemble(element_view_, *dofs_, parallel_for_);
|
||||||
if (!stiffness.HasValue()) {
|
if (!stiffness.HasValue()) {
|
||||||
return stiffness.GetStatus();
|
return stiffness.GetStatus();
|
||||||
}
|
}
|
||||||
@@ -119,8 +141,8 @@ Status LinearStaticAnalysis::AssembleAndPartitionStiffness() {
|
|||||||
return Status::Ok();
|
return Status::Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Status LinearStaticAnalysis::Factorize() {
|
Status LinearStaticAnalysis::FactorizeFreeSystem() {
|
||||||
// This call intentionally precedes all load assembly in Analysis::Run.
|
// This call intentionally precedes all load assembly in Run.
|
||||||
return linear_solver_.Factorize(partitioned_stiffness_->k_ff);
|
return linear_solver_.Factorize(partitioned_stiffness_->k_ff);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,9 +176,10 @@ Status LinearStaticAnalysis::SubstituteAndReconstruct() {
|
|||||||
return Status::Ok();
|
return Status::Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Status LinearStaticAnalysis::RecoverAndWriteResults() {
|
Status LinearStaticAnalysis::RecoverAndWrite() {
|
||||||
const Status recovery_status =
|
const Status recovery_status = ResultRecovery::Recover(
|
||||||
ResultRecovery::Recover(*model_, *dofs_, *full_stiffness_, *state_);
|
*model_, element_view_, *dofs_, *full_stiffness_, state_->Displacement(),
|
||||||
|
state_->ExternalForce(), *state_);
|
||||||
if (!recovery_status.IsOk()) {
|
if (!recovery_status.IsOk()) {
|
||||||
return recovery_status;
|
return recovery_status;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/analysis/analysis.h"
|
||||||
#include "fesa/analysis/linear_static_analysis.h"
|
#include "fesa/analysis/linear_static_analysis.h"
|
||||||
#include "fesa/assembly/parallel_for.h"
|
#include "fesa/assembly/parallel_for.h"
|
||||||
#include "fesa/core/diagnostic.h"
|
#include "fesa/core/diagnostic.h"
|
||||||
@@ -91,8 +93,9 @@ int FesaApplication::Run(const std::vector<std::string>& arguments) {
|
|||||||
TbbParallelFor parallel_for;
|
TbbParallelFor parallel_for;
|
||||||
MklPardisoSolver linear_solver;
|
MklPardisoSolver linear_solver;
|
||||||
Hdf5ResultsWriter results_writer;
|
Hdf5ResultsWriter results_writer;
|
||||||
LinearStaticAnalysis analysis{parallel_for, linear_solver, results_writer};
|
std::unique_ptr<Analysis> analysis = std::make_unique<LinearStaticAnalysis>(
|
||||||
const Status status = analysis.Run(request);
|
parallel_for, linear_solver, results_writer);
|
||||||
|
const Status status = analysis->Run(request);
|
||||||
if (status.IsOk()) {
|
if (status.IsOk()) {
|
||||||
return kSuccessExitCode;
|
return kSuccessExitCode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,13 +57,6 @@ Result<SparseMatrix> ExtractBlock(const SparseMatrix& full,
|
|||||||
std::move(contributions), pattern);
|
std::move(contributions), pattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RequireDofOrder(const DofManager& dof_manager) {
|
|
||||||
if (!dof_manager.ValidateInvariants().IsOk()) {
|
|
||||||
throw std::invalid_argument{
|
|
||||||
"DofManager constraint dimensions or order are invalid."};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Result<PartitionedStiffness> EssentialConstraintPolicy::Partition(
|
Result<PartitionedStiffness> EssentialConstraintPolicy::Partition(
|
||||||
@@ -114,7 +107,6 @@ Result<PartitionedStiffness> EssentialConstraintPolicy::Partition(
|
|||||||
|
|
||||||
Vector EssentialConstraintPolicy::GatherFree(
|
Vector EssentialConstraintPolicy::GatherFree(
|
||||||
const Vector& full_values, const DofManager& dof_manager) const {
|
const Vector& full_values, const DofManager& dof_manager) const {
|
||||||
RequireDofOrder(dof_manager);
|
|
||||||
if (full_values.Size() != dof_manager.FullDofCount()) {
|
if (full_values.Size() != dof_manager.FullDofCount()) {
|
||||||
throw std::invalid_argument{
|
throw std::invalid_argument{
|
||||||
"Full vector size must match the DofManager full dimension."};
|
"Full vector size must match the DofManager full dimension."};
|
||||||
@@ -129,7 +121,6 @@ Vector EssentialConstraintPolicy::GatherFree(
|
|||||||
|
|
||||||
Vector EssentialConstraintPolicy::GatherConstrained(
|
Vector EssentialConstraintPolicy::GatherConstrained(
|
||||||
const Vector& full_values, const DofManager& dof_manager) const {
|
const Vector& full_values, const DofManager& dof_manager) const {
|
||||||
RequireDofOrder(dof_manager);
|
|
||||||
if (full_values.Size() != dof_manager.FullDofCount()) {
|
if (full_values.Size() != dof_manager.FullDofCount()) {
|
||||||
throw std::invalid_argument{
|
throw std::invalid_argument{
|
||||||
"Full vector size must match the DofManager full dimension."};
|
"Full vector size must match the DofManager full dimension."};
|
||||||
@@ -145,7 +136,6 @@ Vector EssentialConstraintPolicy::GatherConstrained(
|
|||||||
Vector EssentialConstraintPolicy::ReconstructFull(
|
Vector EssentialConstraintPolicy::ReconstructFull(
|
||||||
const Vector& free_values, const Vector& constrained_values,
|
const Vector& free_values, const Vector& constrained_values,
|
||||||
const DofManager& dof_manager) const {
|
const DofManager& dof_manager) const {
|
||||||
RequireDofOrder(dof_manager);
|
|
||||||
if (free_values.Size() != dof_manager.FreeDofCount() ||
|
if (free_values.Size() != dof_manager.FreeDofCount() ||
|
||||||
constrained_values.Size() != dof_manager.ConstrainedDofCount()) {
|
constrained_values.Size() != dof_manager.ConstrainedDofCount()) {
|
||||||
throw std::invalid_argument{
|
throw std::invalid_argument{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ include(GoogleTest)
|
|||||||
add_executable(
|
add_executable(
|
||||||
fesa_unit_tests
|
fesa_unit_tests
|
||||||
unit/build_info_test.cpp
|
unit/build_info_test.cpp
|
||||||
|
unit/analysis/analysis_test.cpp
|
||||||
unit/analysis/analysis_model_test.cpp
|
unit/analysis/analysis_model_test.cpp
|
||||||
unit/analysis/analysis_state_test.cpp
|
unit/analysis/analysis_state_test.cpp
|
||||||
unit/assembly/parallel_for_test.cpp
|
unit/assembly/parallel_for_test.cpp
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "fesa/analysis/analysis.h"
|
||||||
#include "fesa/assembly/parallel_for.h"
|
#include "fesa/assembly/parallel_for.h"
|
||||||
#include "fesa/results/results_writer.h"
|
#include "fesa/results/results_writer.h"
|
||||||
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
|
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
|
||||||
@@ -152,45 +153,6 @@ N4, 1, 6
|
|||||||
)inp");
|
)inp");
|
||||||
}
|
}
|
||||||
|
|
||||||
// The pure Template Method spy makes the eight public lifecycle hooks
|
|
||||||
// observable without coupling the ordering assertion to any solver backend.
|
|
||||||
class SpyAnalysis final : public fesa::Analysis {
|
|
||||||
public:
|
|
||||||
const std::vector<std::string>& Events() const noexcept { return events_; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
fesa::Status Initialize(const fesa::AnalysisRequest&) override {
|
|
||||||
return Record("initialize");
|
|
||||||
}
|
|
||||||
fesa::Status BuildAnalysisModel() override {
|
|
||||||
return Record("build-analysis-model");
|
|
||||||
}
|
|
||||||
fesa::Status BuildDofMapAndSparsePattern() override {
|
|
||||||
return Record("build-dof-map-and-sparse-pattern");
|
|
||||||
}
|
|
||||||
fesa::Status AssembleAndPartitionStiffness() override {
|
|
||||||
return Record("assemble-and-partition-stiffness");
|
|
||||||
}
|
|
||||||
fesa::Status Factorize() override { return Record("factorize"); }
|
|
||||||
fesa::Status AssembleLoadsAndEffectiveRhs() override {
|
|
||||||
return Record("assemble-loads-and-effective-rhs");
|
|
||||||
}
|
|
||||||
fesa::Status SubstituteAndReconstruct() override {
|
|
||||||
return Record("substitute-and-reconstruct");
|
|
||||||
}
|
|
||||||
fesa::Status RecoverAndWriteResults() override {
|
|
||||||
return Record("recover-and-write-results");
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
fesa::Status Record(const char* event) {
|
|
||||||
events_.emplace_back(event);
|
|
||||||
return fesa::Status::Ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string> events_;
|
|
||||||
};
|
|
||||||
|
|
||||||
// The solver spy records only the adapter-boundary operations. In particular,
|
// The solver spy records only the adapter-boundary operations. In particular,
|
||||||
// solve() cannot conceal a second factorization call.
|
// solve() cannot conceal a second factorization call.
|
||||||
class SpyLinearSolver final : public fesa::LinearSolver {
|
class SpyLinearSolver final : public fesa::LinearSolver {
|
||||||
@@ -342,17 +304,6 @@ class CapturingResultsWriter final : public fesa::ResultsWriter {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST(LinearStaticCli, FactorizesBeforeLoadAndSolvesWithoutRefactorization) {
|
TEST(LinearStaticCli, FactorizesBeforeLoadAndSolvesWithoutRefactorization) {
|
||||||
SpyAnalysis lifecycle;
|
|
||||||
const fesa::AnalysisRequest empty_request{};
|
|
||||||
ASSERT_TRUE(lifecycle.Run(empty_request).IsOk());
|
|
||||||
EXPECT_EQ(lifecycle.Events(),
|
|
||||||
(std::vector<std::string>{
|
|
||||||
"initialize", "build-analysis-model",
|
|
||||||
"build-dof-map-and-sparse-pattern",
|
|
||||||
"assemble-and-partition-stiffness", "factorize",
|
|
||||||
"assemble-loads-and-effective-rhs",
|
|
||||||
"substitute-and-reconstruct", "recover-and-write-results"}));
|
|
||||||
|
|
||||||
TempDirectory directory{"order"};
|
TempDirectory directory{"order"};
|
||||||
const auto input = directory.Path() / "order.inp";
|
const auto input = directory.Path() / "order.inp";
|
||||||
const auto output = directory.Path() / "results.h5";
|
const auto output = directory.Path() / "results.h5";
|
||||||
@@ -362,9 +313,10 @@ TEST(LinearStaticCli, FactorizesBeforeLoadAndSolvesWithoutRefactorization) {
|
|||||||
fesa::SerialParallelFor serial;
|
fesa::SerialParallelFor serial;
|
||||||
SpyLinearSolver solver{adapter_events};
|
SpyLinearSolver solver{adapter_events};
|
||||||
SpyResultsWriter writer{adapter_events};
|
SpyResultsWriter writer{adapter_events};
|
||||||
fesa::LinearStaticAnalysis analysis{serial, solver, writer};
|
std::unique_ptr<fesa::Analysis> analysis =
|
||||||
|
std::make_unique<fesa::LinearStaticAnalysis>(serial, solver, writer);
|
||||||
|
|
||||||
ASSERT_TRUE(analysis.Run({input, output}).IsOk());
|
ASSERT_TRUE(analysis->Run({input, output}).IsOk());
|
||||||
EXPECT_EQ(solver.FactorizeCalls(), 1);
|
EXPECT_EQ(solver.FactorizeCalls(), 1);
|
||||||
EXPECT_EQ(solver.SolveCalls(), 1);
|
EXPECT_EQ(solver.SolveCalls(), 1);
|
||||||
EXPECT_EQ(writer.WriteCalls(), 1);
|
EXPECT_EQ(writer.WriteCalls(), 1);
|
||||||
@@ -373,6 +325,30 @@ TEST(LinearStaticCli, FactorizesBeforeLoadAndSolvesWithoutRefactorization) {
|
|||||||
"writer-write"}));
|
"writer-write"}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(LinearStaticCli, FactorizesBeforeRejectingAnInvalidLoad) {
|
||||||
|
TempDirectory directory{"factorize-before-load"};
|
||||||
|
const auto input = directory.Path() / "invalid-load-shell.inp";
|
||||||
|
const auto output = directory.Path() / "must-not-exist.h5";
|
||||||
|
WriteText(input, ShellDeck("*Boundary\nAll, 1, 6\n", "*Cload\nN1, 6, 1.0\n"));
|
||||||
|
|
||||||
|
std::vector<std::string> adapter_events;
|
||||||
|
fesa::SerialParallelFor serial;
|
||||||
|
SpyLinearSolver solver{adapter_events};
|
||||||
|
SpyResultsWriter writer{adapter_events};
|
||||||
|
std::unique_ptr<fesa::Analysis> analysis =
|
||||||
|
std::make_unique<fesa::LinearStaticAnalysis>(serial, solver, writer);
|
||||||
|
|
||||||
|
const fesa::Status status = analysis->Run({input, output});
|
||||||
|
ASSERT_FALSE(status.IsOk());
|
||||||
|
EXPECT_EQ(status.Category(), fesa::FailureCategory::kModel);
|
||||||
|
ASSERT_EQ(status.Diagnostics().size(), 1U);
|
||||||
|
EXPECT_EQ(status.Diagnostics()[0U].code, "unsupported-drilling-load");
|
||||||
|
EXPECT_EQ(solver.FactorizeCalls(), 1);
|
||||||
|
EXPECT_EQ(solver.SolveCalls(), 0);
|
||||||
|
EXPECT_EQ(writer.WriteCalls(), 0);
|
||||||
|
EXPECT_EQ(adapter_events, (std::vector<std::string>{"solver-factorize"}));
|
||||||
|
}
|
||||||
|
|
||||||
TEST(LinearStaticCli, RealPipelineHandlesAnalyticalAndNonzeroPrescription) {
|
TEST(LinearStaticCli, RealPipelineHandlesAnalyticalAndNonzeroPrescription) {
|
||||||
TempDirectory directory{"analytical"};
|
TempDirectory directory{"analytical"};
|
||||||
const auto input = directory.Path() / "prescribed-axial.inp";
|
const auto input = directory.Path() / "prescribed-axial.inp";
|
||||||
@@ -382,9 +358,10 @@ TEST(LinearStaticCli, RealPipelineHandlesAnalyticalAndNonzeroPrescription) {
|
|||||||
fesa::SerialParallelFor serial;
|
fesa::SerialParallelFor serial;
|
||||||
fesa::MklPardisoSolver solver;
|
fesa::MklPardisoSolver solver;
|
||||||
CapturingResultsWriter writer;
|
CapturingResultsWriter writer;
|
||||||
fesa::LinearStaticAnalysis analysis{serial, solver, writer};
|
std::unique_ptr<fesa::Analysis> analysis =
|
||||||
|
std::make_unique<fesa::LinearStaticAnalysis>(serial, solver, writer);
|
||||||
|
|
||||||
const auto status = analysis.Run({input, output});
|
const auto status = analysis->Run({input, output});
|
||||||
ASSERT_TRUE(status.IsOk());
|
ASSERT_TRUE(status.IsOk());
|
||||||
EXPECT_EQ(writer.OutputPath(), output);
|
EXPECT_EQ(writer.OutputPath(), output);
|
||||||
EXPECT_EQ(writer.NodeCount(), 2U);
|
EXPECT_EQ(writer.NodeCount(), 2U);
|
||||||
@@ -419,9 +396,10 @@ TEST(Mitc4ShellCli, UsesExistingLifecycleAndExactlyOneFactorization) {
|
|||||||
fesa::SerialParallelFor serial;
|
fesa::SerialParallelFor serial;
|
||||||
SpyLinearSolver solver{adapter_events};
|
SpyLinearSolver solver{adapter_events};
|
||||||
SpyResultsWriter writer{adapter_events};
|
SpyResultsWriter writer{adapter_events};
|
||||||
fesa::LinearStaticAnalysis analysis{serial, solver, writer};
|
std::unique_ptr<fesa::Analysis> analysis =
|
||||||
|
std::make_unique<fesa::LinearStaticAnalysis>(serial, solver, writer);
|
||||||
|
|
||||||
ASSERT_TRUE(analysis.Run({input, output}).IsOk());
|
ASSERT_TRUE(analysis->Run({input, output}).IsOk());
|
||||||
EXPECT_EQ(solver.FactorizeCalls(), 1);
|
EXPECT_EQ(solver.FactorizeCalls(), 1);
|
||||||
EXPECT_EQ(solver.SolveCalls(), 1);
|
EXPECT_EQ(solver.SolveCalls(), 1);
|
||||||
EXPECT_EQ(writer.WriteCalls(), 1);
|
EXPECT_EQ(writer.WriteCalls(), 1);
|
||||||
@@ -440,9 +418,10 @@ TEST(Mitc4ShellCli, AppliesKfcForNonzeroPrescribedDisplacement) {
|
|||||||
fesa::SerialParallelFor serial;
|
fesa::SerialParallelFor serial;
|
||||||
RecordingMklSolver solver;
|
RecordingMklSolver solver;
|
||||||
CapturingResultsWriter writer;
|
CapturingResultsWriter writer;
|
||||||
fesa::LinearStaticAnalysis analysis{serial, solver, writer};
|
std::unique_ptr<fesa::Analysis> analysis =
|
||||||
|
std::make_unique<fesa::LinearStaticAnalysis>(serial, solver, writer);
|
||||||
|
|
||||||
const auto status = analysis.Run({input, output});
|
const auto status = analysis->Run({input, output});
|
||||||
for (const auto& diagnostic : status.Diagnostics()) {
|
for (const auto& diagnostic : status.Diagnostics()) {
|
||||||
EXPECT_TRUE(status.IsOk()) << diagnostic.code << ": " << diagnostic.message;
|
EXPECT_TRUE(status.IsOk()) << diagnostic.code << ": " << diagnostic.message;
|
||||||
}
|
}
|
||||||
@@ -479,19 +458,21 @@ TEST(Mitc4ShellCli, RejectsSingularAndAcceptsZeroByZeroFreeSystem) {
|
|||||||
fesa::MklPardisoSolver singular_solver;
|
fesa::MklPardisoSolver singular_solver;
|
||||||
std::vector<std::string> singular_events;
|
std::vector<std::string> singular_events;
|
||||||
SpyResultsWriter singular_writer{singular_events};
|
SpyResultsWriter singular_writer{singular_events};
|
||||||
fesa::LinearStaticAnalysis singular_analysis{serial, singular_solver,
|
std::unique_ptr<fesa::Analysis> singular_analysis =
|
||||||
singular_writer};
|
std::make_unique<fesa::LinearStaticAnalysis>(serial, singular_solver,
|
||||||
const auto singular =
|
singular_writer);
|
||||||
singular_analysis.Run({singular_input, directory.Path() / "singular.h5"});
|
const auto singular = singular_analysis->Run(
|
||||||
|
{singular_input, directory.Path() / "singular.h5"});
|
||||||
ASSERT_FALSE(singular.IsOk());
|
ASSERT_FALSE(singular.IsOk());
|
||||||
EXPECT_EQ(singular.Category(), fesa::FailureCategory::kSolver);
|
EXPECT_EQ(singular.Category(), fesa::FailureCategory::kSolver);
|
||||||
EXPECT_EQ(singular_writer.WriteCalls(), 0);
|
EXPECT_EQ(singular_writer.WriteCalls(), 0);
|
||||||
|
|
||||||
RecordingMklSolver constrained_solver;
|
RecordingMklSolver constrained_solver;
|
||||||
CapturingResultsWriter constrained_writer;
|
CapturingResultsWriter constrained_writer;
|
||||||
fesa::LinearStaticAnalysis constrained_analysis{serial, constrained_solver,
|
std::unique_ptr<fesa::Analysis> constrained_analysis =
|
||||||
constrained_writer};
|
std::make_unique<fesa::LinearStaticAnalysis>(serial, constrained_solver,
|
||||||
const auto constrained = constrained_analysis.Run(
|
constrained_writer);
|
||||||
|
const auto constrained = constrained_analysis->Run(
|
||||||
{constrained_input, directory.Path() / "constrained.h5"});
|
{constrained_input, directory.Path() / "constrained.h5"});
|
||||||
ASSERT_TRUE(constrained.IsOk());
|
ASSERT_TRUE(constrained.IsOk());
|
||||||
EXPECT_EQ(constrained_solver.FactorizeCalls(), 1);
|
EXPECT_EQ(constrained_solver.FactorizeCalls(), 1);
|
||||||
@@ -511,9 +492,10 @@ TEST(Mitc4ShellCli, DoesNotWriteAnInvalidRecoveryCandidate) {
|
|||||||
NonfiniteLinearSolver solver;
|
NonfiniteLinearSolver solver;
|
||||||
std::vector<std::string> adapter_events;
|
std::vector<std::string> adapter_events;
|
||||||
SpyResultsWriter writer{adapter_events};
|
SpyResultsWriter writer{adapter_events};
|
||||||
fesa::LinearStaticAnalysis analysis{serial, solver, writer};
|
std::unique_ptr<fesa::Analysis> analysis =
|
||||||
|
std::make_unique<fesa::LinearStaticAnalysis>(serial, solver, writer);
|
||||||
const auto status =
|
const auto status =
|
||||||
analysis.Run({input, directory.Path() / "must-not-exist.h5"});
|
analysis->Run({input, directory.Path() / "must-not-exist.h5"});
|
||||||
|
|
||||||
ASSERT_FALSE(status.IsOk());
|
ASSERT_FALSE(status.IsOk());
|
||||||
EXPECT_EQ(status.Category(), fesa::FailureCategory::kModel);
|
EXPECT_EQ(status.Category(), fesa::FailureCategory::kModel);
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#include "fesa/analysis/analysis.h"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using RunSignature =
|
||||||
|
fesa::Status (fesa::Analysis::*)(const fesa::AnalysisRequest&);
|
||||||
|
|
||||||
|
static_assert(std::has_virtual_destructor_v<fesa::Analysis>);
|
||||||
|
static_assert(std::is_abstract_v<fesa::Analysis>);
|
||||||
|
static_assert(std::is_same_v<decltype(&fesa::Analysis::Run), RunSignature>);
|
||||||
|
|
||||||
|
class StatusAnalysis final : public fesa::Analysis {
|
||||||
|
public:
|
||||||
|
StatusAnalysis(bool& destroyed, fesa::Status result)
|
||||||
|
: destroyed_{destroyed}, result_{std::move(result)} {}
|
||||||
|
|
||||||
|
~StatusAnalysis() override { destroyed_ = true; }
|
||||||
|
|
||||||
|
fesa::Status Run(const fesa::AnalysisRequest& request) override {
|
||||||
|
request_ = request;
|
||||||
|
return result_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fesa::AnalysisRequest& Request() const noexcept { return request_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool& destroyed_;
|
||||||
|
fesa::Status result_;
|
||||||
|
fesa::AnalysisRequest request_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST(Analysis, PropagatesStatusAndDestroysThroughMinimalAnalysis) {
|
||||||
|
bool destroyed = false;
|
||||||
|
const fesa::AnalysisRequest request{"model.inp", "results.h5"};
|
||||||
|
const fesa::Status failure = fesa::Status::Failure(
|
||||||
|
fesa::FailureCategory::kSolver,
|
||||||
|
{{fesa::Severity::kError,
|
||||||
|
"analysis-test-failure",
|
||||||
|
{},
|
||||||
|
"ANALYSIS",
|
||||||
|
"",
|
||||||
|
"The concrete procedure failure must propagate unchanged."}});
|
||||||
|
|
||||||
|
auto concrete = std::make_unique<StatusAnalysis>(destroyed, failure);
|
||||||
|
StatusAnalysis* const concrete_observer = concrete.get();
|
||||||
|
std::unique_ptr<fesa::Analysis> analysis = std::move(concrete);
|
||||||
|
|
||||||
|
const fesa::Status status = analysis->Run(request);
|
||||||
|
ASSERT_FALSE(status.IsOk());
|
||||||
|
EXPECT_EQ(status.Category(), fesa::FailureCategory::kSolver);
|
||||||
|
ASSERT_EQ(status.Diagnostics().size(), 1U);
|
||||||
|
EXPECT_EQ(status.Diagnostics()[0U].code, "analysis-test-failure");
|
||||||
|
EXPECT_EQ(concrete_observer->Request().input_path, request.input_path);
|
||||||
|
EXPECT_EQ(concrete_observer->Request().output_path, request.output_path);
|
||||||
|
|
||||||
|
analysis.reset();
|
||||||
|
EXPECT_TRUE(destroyed);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user