Integrate Refactor solver sources and dual MSVC CMake builds

This commit is contained in:
KOKO\Mimi
2026-10-18 02:10:51 +09:00
parent 7ee32e5837
commit 12d51480de
218 changed files with 17842 additions and 9689 deletions
@@ -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) {
+191
View File
@@ -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());
}
}