Integrate Refactor solver sources and dual MSVC CMake builds
This commit is contained in:
@@ -9,6 +9,9 @@
|
||||
|
||||
#include "fesa/analysis/analysis_state.h"
|
||||
|
||||
#include "fesa/analysis/analysis_model.h"
|
||||
#include "fesa/fem/dof_manager.h"
|
||||
|
||||
namespace {
|
||||
|
||||
fesa::DofManager MakeEmptyDofs() {
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <filesystem>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -354,41 +353,6 @@ TEST(ResultRecoveryComponents,
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ResultRecoveryComponents, PartitionedScaleKeepsCancelledInternalTerms) {
|
||||
auto definition = MakeShellDefinition();
|
||||
definition.node_sets = {{"Fixed", std::optional<std::string>{"Shell-1"},
|
||||
{0U}, {definition.source_path, 60U}}};
|
||||
definition.steps[0].boundaries = {
|
||||
{"Fixed", 1, 1, 1.0, {definition.source_path, 61U}}};
|
||||
const auto fixture = MakeShellFixture(std::move(definition));
|
||||
const auto count = fixture.dofs->FullDofCount();
|
||||
fesa::SparsePattern pattern;
|
||||
pattern.row_offsets = {0U, 1U, 3U};
|
||||
pattern.row_offsets.resize(count + 1U, 3U);
|
||||
pattern.column_indices = {0U, 0U, 1U};
|
||||
auto matrix = fesa::SparseMatrix::FromCoo(
|
||||
count, count,
|
||||
{{0U, 0U, 1.0, 0U, 0U}, {1U, 0U, -1.0, 0U, 1U},
|
||||
{1U, 1U, 1.0, 0U, 2U}}, pattern);
|
||||
ASSERT_TRUE(matrix.HasValue());
|
||||
fesa::Vector displacement{count};
|
||||
displacement[0U] = 1.0;
|
||||
displacement[1U] = 1.0;
|
||||
fesa::Vector external{count};
|
||||
external[1U] = 1.0e-12;
|
||||
const auto result = fesa::results_internal::GlobalEquilibriumRecovery(
|
||||
*fixture.domain, *fixture.dofs, matrix.Value(), displacement, external);
|
||||
ASSERT_TRUE(result.HasValue());
|
||||
EXPECT_DOUBLE_EQ(result.Value().internal_force[1U], 0.0);
|
||||
EXPECT_DOUBLE_EQ(result.Value().normalized_free_residual, 1.0e-12);
|
||||
displacement[1U] = std::numeric_limits<double>::quiet_NaN();
|
||||
const auto rejected = fesa::results_internal::GlobalEquilibriumRecovery(
|
||||
*fixture.domain, *fixture.dofs, matrix.Value(), displacement, external);
|
||||
ASSERT_FALSE(rejected.HasValue());
|
||||
ExpectStatusCode(rejected.GetStatus(), "nonfinite-recovery-value");
|
||||
EXPECT_DOUBLE_EQ(result.Value().reaction[1U], -1.0e-12);
|
||||
}
|
||||
|
||||
// C-MODULE-002
|
||||
TEST(ResultRecoveryComponents,
|
||||
BeamResultRecoveryKeepsEndpointGaussAndStressIdentities) {
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
#include "fesa/results/results_writer.h"
|
||||
|
||||
#include "fesa/results/prepared_results_writer.h"
|
||||
#include "fesa/results/dynamic_results_writer.h"
|
||||
#include "fesa/results/eigen_results_writer.h"
|
||||
#include "fesa/results/nonlinear_results_writer.h"
|
||||
#include "fesa/results/solid_results_writer.h"
|
||||
#include "fesa/results/thermal_results_writer.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "fesa/analysis/analysis_model.h"
|
||||
#include "fesa/analysis/analysis_state.h"
|
||||
#include "fesa/core/diagnostic.h"
|
||||
#include "fesa/core/status.h"
|
||||
@@ -21,3 +34,181 @@ static_assert(
|
||||
std::is_same_v<decltype(&fesa::ResultsWriter::Write), WriteSignature>);
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
|
||||
// These adapters only observe borrowed values; no procedure, kernel, or file I/O.
|
||||
struct WriterCalls {
|
||||
std::vector<const std::filesystem::path*> paths;
|
||||
std::vector<const fesa::Domain*> domains;
|
||||
std::vector<const std::vector<fesa::Diagnostic>*> diagnostics;
|
||||
std::vector<unsigned> families;
|
||||
|
||||
fesa::Status Record(unsigned family, const std::filesystem::path& path,
|
||||
const fesa::Domain& domain,
|
||||
const std::vector<fesa::Diagnostic>& values) {
|
||||
paths.push_back(&path);
|
||||
domains.push_back(&domain);
|
||||
diagnostics.push_back(&values);
|
||||
families.push_back(family);
|
||||
return fesa::Status::Failure(
|
||||
fesa::FailureCategory::kOutput,
|
||||
{{fesa::Severity::kError, "candidate-close-failure", {"source.inp", 7U},
|
||||
"WRITER", "0007", "Preserved writer failure."}});
|
||||
}
|
||||
};
|
||||
|
||||
template <class Writer, class State, unsigned Family>
|
||||
class RecordingWriter final : public Writer {
|
||||
public:
|
||||
explicit RecordingWriter(WriterCalls& calls) : calls_{calls} {}
|
||||
fesa::Status Write(const std::filesystem::path& path,
|
||||
const fesa::Domain& domain, const State& state,
|
||||
const std::vector<fesa::Diagnostic>& diagnostics) override {
|
||||
received = &state;
|
||||
return calls_.Record(Family, path, domain, diagnostics);
|
||||
}
|
||||
const State* received{nullptr};
|
||||
|
||||
private:
|
||||
WriterCalls& calls_;
|
||||
};
|
||||
|
||||
class RecordingSolidWriter final : public fesa::SolidResultsWriter {
|
||||
public:
|
||||
explicit RecordingSolidWriter(WriterCalls& calls) : calls_{calls} {}
|
||||
fesa::Status Write(const std::filesystem::path& p, const fesa::Domain& d,
|
||||
const fesa::SolidStaticState& s,
|
||||
const std::vector<fesa::Diagnostic>& v) override {
|
||||
static_state = &s;
|
||||
return calls_.Record(5U, p, d, v);
|
||||
}
|
||||
fesa::Status Write(const std::filesystem::path& p, const fesa::Domain& d,
|
||||
const fesa::SolidFrequencyState& s,
|
||||
const std::vector<fesa::Diagnostic>& v) override {
|
||||
frequency_state = &s;
|
||||
return calls_.Record(6U, p, d, v);
|
||||
}
|
||||
fesa::Status Write(const std::filesystem::path& p, const fesa::Domain& d,
|
||||
const fesa::SolidDynamicAnalysisState& s,
|
||||
const std::vector<fesa::Diagnostic>& v) override {
|
||||
dynamic_state = &s;
|
||||
return calls_.Record(7U, p, d, v);
|
||||
}
|
||||
const fesa::SolidStaticState* static_state{nullptr};
|
||||
const fesa::SolidFrequencyState* frequency_state{nullptr};
|
||||
const fesa::SolidDynamicAnalysisState* dynamic_state{nullptr};
|
||||
|
||||
private:
|
||||
WriterCalls& calls_;
|
||||
};
|
||||
|
||||
// Empty values intentionally bypass physics: dispatch must preserve their type.
|
||||
std::vector<fesa::PreparedAnalysisOutcome> EmptyOutcomes() {
|
||||
std::vector<fesa::PreparedAnalysisOutcome> values;
|
||||
values.emplace_back(fesa::LinearStaticExecutionResult{
|
||||
fesa::AnalysisState::Create(fesa::DofManager{}, {"source/step", 9U}), {}});
|
||||
fesa::ModelDefinition definition{};
|
||||
definition.steps = {{"value-only", {}, {}, 0.1, 1.0, 0.01, 1.0, {}}};
|
||||
auto domain = fesa::Domain::Create(std::move(definition));
|
||||
auto model = fesa::AnalysisModel::Create(domain.Value());
|
||||
auto dofs = fesa::DofManager::Create(model.Value());
|
||||
auto nonlinear = fesa::NonlinearAnalysisState::Create(
|
||||
domain.Value(), dofs.Value(), {});
|
||||
if (!nonlinear.HasValue())
|
||||
throw std::runtime_error{"Empty nonlinear value construction failed."};
|
||||
values.emplace_back(
|
||||
fesa::NonlinearStaticExecutionResult{std::move(nonlinear.Value()), {}});
|
||||
values.emplace_back(fesa::EigenAnalysisState{
|
||||
fesa::FeastEigenResult{0U, {}, fesa::Matrix{0U, 0U}}});
|
||||
values.emplace_back(fesa::DynamicExecutionResult{});
|
||||
values.emplace_back(fesa::SolidStaticExecutionResult{});
|
||||
values.emplace_back(fesa::SolidFrequencyState{
|
||||
fesa::FeastEigenResult{0U, {}, fesa::Matrix{0U, 0U}}});
|
||||
values.emplace_back(fesa::SolidDynamicExecutionResult{});
|
||||
return values;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(PreparedResultsWriter, RoutesValueOutcomesAndPreservesWriterFailure) {
|
||||
auto domain = fesa::Domain::Create({});
|
||||
ASSERT_TRUE(domain.HasValue());
|
||||
const std::filesystem::path output{"relative/custom-result.h5"};
|
||||
const std::vector<fesa::Diagnostic> diagnostics{
|
||||
{fesa::Severity::kWarning, "original-warning", {"source.inp", 3U},
|
||||
"NODE", "0003", "Original diagnostic."}};
|
||||
WriterCalls calls;
|
||||
RecordingWriter<fesa::ResultsWriter, fesa::AnalysisState, 0U> linear{calls};
|
||||
RecordingWriter<fesa::NonlinearResultsWriter, fesa::NonlinearAnalysisState, 1U>
|
||||
nonlinear{calls};
|
||||
RecordingWriter<fesa::EigenResultsWriter, fesa::EigenAnalysisState, 2U>
|
||||
eigen{calls};
|
||||
RecordingWriter<fesa::DynamicResultsWriter, fesa::DynamicAnalysisState, 3U>
|
||||
dynamic{calls};
|
||||
RecordingWriter<fesa::ThermalResultsWriter, fesa::ThermalAnalysisState, 4U>
|
||||
thermal{calls};
|
||||
RecordingSolidWriter solid{calls};
|
||||
auto outcomes = EmptyOutcomes();
|
||||
const std::array<unsigned, 7> families{0U, 1U, 2U, 3U, 5U, 6U, 7U};
|
||||
ASSERT_EQ(outcomes.size(), families.size());
|
||||
for (std::size_t index = 0U; index < outcomes.size(); ++index) {
|
||||
const auto status = fesa::WritePreparedAnalysisResult(
|
||||
output, domain.Value(), outcomes[index], diagnostics, linear,
|
||||
nonlinear, eigen, &dynamic, &thermal, &solid);
|
||||
ASSERT_EQ(calls.families.size(), index + 1U);
|
||||
EXPECT_EQ(calls.families.back(), families[index]);
|
||||
EXPECT_EQ(calls.paths.back(), &output);
|
||||
EXPECT_EQ(calls.domains.back(), &domain.Value());
|
||||
EXPECT_EQ(calls.diagnostics.back(), &diagnostics);
|
||||
EXPECT_EQ(status.Category(), fesa::FailureCategory::kOutput);
|
||||
ASSERT_EQ(status.Diagnostics().size(), 1U);
|
||||
EXPECT_EQ(status.Diagnostics()[0U].code, "candidate-close-failure");
|
||||
EXPECT_EQ(status.Diagnostics()[0U].location.file, "source.inp");
|
||||
EXPECT_EQ(status.Diagnostics()[0U].location.line, 7U);
|
||||
EXPECT_EQ(status.Diagnostics()[0U].keyword, "WRITER");
|
||||
EXPECT_EQ(status.Diagnostics()[0U].entity_identity, "0007");
|
||||
EXPECT_EQ(status.Diagnostics()[0U].message, "Preserved writer failure.");
|
||||
}
|
||||
EXPECT_EQ(linear.received,
|
||||
&std::get<fesa::LinearStaticExecutionResult>(outcomes[0U]).state);
|
||||
EXPECT_EQ(nonlinear.received,
|
||||
&std::get<fesa::NonlinearStaticExecutionResult>(outcomes[1U]).state);
|
||||
EXPECT_EQ(eigen.received, &std::get<fesa::EigenAnalysisState>(outcomes[2U]));
|
||||
EXPECT_EQ(dynamic.received,
|
||||
&std::get<fesa::DynamicExecutionResult>(outcomes[3U]).state);
|
||||
EXPECT_EQ(thermal.received, nullptr);
|
||||
EXPECT_EQ(solid.static_state,
|
||||
&std::get<fesa::SolidStaticExecutionResult>(outcomes[4U]).state);
|
||||
EXPECT_EQ(solid.frequency_state,
|
||||
&std::get<fesa::SolidFrequencyState>(outcomes[5U]));
|
||||
EXPECT_EQ(solid.dynamic_state,
|
||||
&std::get<fesa::SolidDynamicExecutionResult>(outcomes[6U]).state);
|
||||
}
|
||||
|
||||
TEST(PreparedResultsWriter, MissingOptionalAdapterInvokesNoOtherWriter) {
|
||||
auto domain = fesa::Domain::Create({});
|
||||
ASSERT_TRUE(domain.HasValue());
|
||||
WriterCalls calls;
|
||||
RecordingWriter<fesa::ResultsWriter, fesa::AnalysisState, 0U> linear{calls};
|
||||
RecordingWriter<fesa::NonlinearResultsWriter, fesa::NonlinearAnalysisState, 1U>
|
||||
nonlinear{calls};
|
||||
RecordingWriter<fesa::EigenResultsWriter, fesa::EigenAnalysisState, 2U>
|
||||
eigen{calls};
|
||||
auto outcomes = EmptyOutcomes();
|
||||
const std::array<const char*, 4> codes{
|
||||
"missing-dynamic-writer", "missing-solid-writer",
|
||||
"missing-solid-writer", "missing-solid-writer"};
|
||||
const std::array<const char*, 4> keywords{"DYNAMIC", "", "", ""};
|
||||
for (std::size_t index = 3U; index < outcomes.size(); ++index) {
|
||||
const auto status = fesa::WritePreparedAnalysisResult(
|
||||
"explicit.h5", domain.Value(), outcomes[index], {}, linear, nonlinear,
|
||||
eigen);
|
||||
EXPECT_FALSE(status.IsOk());
|
||||
EXPECT_EQ(status.Category(), fesa::FailureCategory::kOutput);
|
||||
ASSERT_EQ(status.Diagnostics().size(), 1U);
|
||||
EXPECT_EQ(status.Diagnostics()[0U].code, codes[index - 3U]);
|
||||
EXPECT_EQ(status.Diagnostics()[0U].keyword, keywords[index - 3U]);
|
||||
EXPECT_TRUE(calls.families.empty());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user