Files
FESADev/tests/unit/analysis/analysis_test.cpp
T

631 lines
31 KiB
C++

#include "fesa/analysis/analysis.h"
#include "fesa/analysis/analysis_procedure.h"
#include "fesa/analysis/prepared_analysis.h"
#include "analysis/equilibrium_algorithm.h"
#include "analysis/hht_reduced_integrator.h"
#include "analysis/hht_reduced_procedure.h"
#include "analysis/solid_hht_integrator.h"
#include "analysis/solid_hht_core.h"
#include "analysis/thermal_transient_algebra.h"
#include "fesa/solvers/linear/tangent_solver.h"
#include <gtest/gtest.h>
#include <filesystem>
#include <memory>
#include <optional>
#include <vector>
#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);
}
namespace {
using ProcedureSignature = fesa::Result<fesa::PreparedAnalysisOutcome> (
fesa::AnalysisProcedure::*)(const fesa::Domain &);
static_assert(std::has_virtual_destructor_v<fesa::AnalysisProcedure>);
static_assert(std::is_abstract_v<fesa::AnalysisProcedure>);
static_assert(std::is_same_v<decltype(&fesa::AnalysisProcedure::Execute),
ProcedureSignature>);
class ObservedProcedure final : public fesa::AnalysisProcedure {
public:
ObservedProcedure(const fesa::Domain *&observed, bool &destroyed)
: observed_(observed), destroyed_(destroyed) {}
~ObservedProcedure() override { destroyed_ = true; }
fesa::Result<fesa::PreparedAnalysisOutcome>
Execute(const fesa::Domain &domain) override {
observed_ = &domain;
return fesa::Result<fesa::PreparedAnalysisOutcome>::Failure(
fesa::Status::Failure(fesa::FailureCategory::kSolver,
{{fesa::Severity::kError,
"procedure-original-failure",
{},
"ORIGINAL",
"source-identity",
"Original failure message."}}));
}
private:
const fesa::Domain *&observed_;
bool &destroyed_;
};
} // namespace
TEST(AnalysisProcedure, PreparedConsumerPreservesDomainFailureAndLifetime) {
const auto domain_result = fesa::Domain::Create({});
ASSERT_TRUE(domain_result.HasValue());
const auto &domain = domain_result.Value();
const fesa::Domain *observed = nullptr;
bool destroyed = false;
std::unique_ptr<fesa::AnalysisProcedure> procedure =
std::make_unique<ObservedProcedure>(observed, destroyed);
auto outcome = fesa::ExecutePreparedAnalysis(domain, *procedure);
EXPECT_EQ(observed, &domain);
ASSERT_FALSE(outcome.HasValue());
EXPECT_EQ(outcome.GetStatus().Category(), fesa::FailureCategory::kSolver);
ASSERT_EQ(outcome.GetStatus().Diagnostics().size(), 1U);
const auto &diagnostic = outcome.GetStatus().Diagnostics()[0];
EXPECT_EQ(diagnostic.code, "procedure-original-failure");
EXPECT_EQ(diagnostic.keyword, "ORIGINAL");
EXPECT_EQ(diagnostic.entity_identity, "source-identity");
EXPECT_EQ(diagnostic.message, "Original failure message.");
EXPECT_FALSE(destroyed);
procedure.reset();
EXPECT_TRUE(destroyed);
}
TEST(EquilibriumAlgorithm, RetainsMoveOnlyCandidateUntilProcedureTakesIt) {
static_assert(std::has_virtual_destructor_v<fesa::EquilibriumAlgorithm>);
static_assert(std::is_abstract_v<fesa::EquilibriumAlgorithm>);
using Candidate = std::unique_ptr<int>;
using EvaluationResult = fesa::Result<std::optional<Candidate>>;
std::vector<std::uint32_t> observed;
fesa::EquilibriumStep<Candidate, 1U> step(
[&](std::uint32_t iteration) {
observed.push_back(iteration);
if (iteration == 3U)
return EvaluationResult::Success(std::make_unique<int>(42));
return EvaluationResult::Success(std::nullopt);
}, 100U);
fesa::NewtonStepAlgorithm algorithm;
fesa::EquilibriumAlgorithm& base = algorithm;
EXPECT_FALSE(step.HasCandidate());
ASSERT_TRUE(step.Solve(base).IsOk());
EXPECT_EQ(observed, (std::vector<std::uint32_t>{1U, 2U, 3U}));
ASSERT_TRUE(step.HasCandidate());
Candidate accepted = step.TakeCandidate();
ASSERT_NE(accepted, nullptr);
EXPECT_EQ(*accepted, 42);
}
TEST(EquilibriumAlgorithm, FirstFailedEvaluationLeavesNoAcceptedCandidate) {
using EvaluationResult = fesa::Result<std::optional<int>>;
std::vector<std::uint32_t> observed;
fesa::EquilibriumStep<int, 1U> step(
[&](std::uint32_t iteration) {
observed.push_back(iteration);
return EvaluationResult::Failure(fesa::Status::Failure(
fesa::FailureCategory::kSolver,
{{fesa::Severity::kError, "first-evaluation-failure", {},
"STATIC", "original-owner", "Original evaluation failure."}}));
}, 100U);
fesa::NewtonStepAlgorithm algorithm;
fesa::EquilibriumAlgorithm& base = algorithm;
const auto status = step.Solve(base);
EXPECT_EQ(observed, (std::vector<std::uint32_t>{1U}));
EXPECT_FALSE(step.HasCandidate());
ASSERT_FALSE(status.IsOk());
ASSERT_EQ(status.Diagnostics().size(), 1U);
EXPECT_EQ(status.Diagnostics()[0].code, "first-evaluation-failure");
EXPECT_EQ(status.Diagnostics()[0].entity_identity, "original-owner");
EXPECT_EQ(status.Diagnostics()[0].message, "Original evaluation failure.");
}
namespace {
// These helpers route supplied values only. They never call a concrete time
// integrator, Domain/assembly/time-grid builder, analysis or numerical solver.
fesa::Status InjectionFailure() {
return fesa::Status::Failure(fesa::FailureCategory::kSolver,
{{fesa::Severity::kError, "injected-policy-failure", {}, "MOCK",
"caller-owned", "Original injected failure."}});
}
void ExpectInjectionFailure(const fesa::Status& status) {
ASSERT_FALSE(status.IsOk());
EXPECT_EQ(status.Category(), fesa::FailureCategory::kSolver);
ASSERT_EQ(status.Diagnostics().size(), 1U);
const auto& diagnostic = status.Diagnostics()[0];
EXPECT_EQ(diagnostic.code, "injected-policy-failure");
EXPECT_EQ(diagnostic.keyword, "MOCK");
EXPECT_EQ(diagnostic.entity_identity, "caller-owned");
EXPECT_EQ(diagnostic.message, "Original injected failure.");
}
void ExpectValueVector(const fesa::Vector& value, double expected) {
ASSERT_EQ(value.Size(), 2U);
for (std::size_t i = 0; i < value.Size(); ++i)
EXPECT_DOUBLE_EQ(value[i], expected);
}
class NeverCalledLinear final : public fesa::LinearSolver {
public:
fesa::Status Factorize(const fesa::SparseMatrix&) override {
ADD_FAILURE() << "Mock dispatch must not factorize.";
return InjectionFailure();
}
fesa::Status Solve(const fesa::Vector&, fesa::Vector&) const override {
ADD_FAILURE() << "Mock dispatch must not solve.";
return InjectionFailure();
}
};
class NeverCalledTangent final : public fesa::TangentSolver {
public:
fesa::Status FactorizeFresh(const fesa::SparseMatrix&) override {
ADD_FAILURE() << "Mock dispatch must not factorize.";
return InjectionFailure();
}
fesa::Status SolveCorrection(const fesa::Vector&, fesa::Vector&) const override {
ADD_FAILURE() << "Mock dispatch must not solve.";
return InjectionFailure();
}
};
class SelectedOrderAlgorithm final : public fesa::EquilibriumAlgorithm {
public:
explicit SelectedOrderAlgorithm(bool& destroyed) : destroyed_(destroyed) {}
~SelectedOrderAlgorithm() override { destroyed_ = true; }
bool fail = false;
fesa::Status SolveCurrentStep(std::uint32_t first, std::uint32_t last,
const Evaluation& evaluate) override {
EXPECT_EQ(first, 0U);
EXPECT_EQ(last, 3U);
if (fail) return InjectionFailure();
auto first_result = evaluate(first);
if (!first_result.HasValue()) return first_result.GetStatus();
EXPECT_FALSE(first_result.Value());
auto last_result = evaluate(last);
if (!last_result.HasValue()) return last_result.GetStatus();
EXPECT_TRUE(last_result.Value());
return fesa::Status::Ok();
}
private:
bool& destroyed_;
};
namespace shell_value = fesa::hht_reduced_internal;
class ValueShellIntegrator final : public shell_value::ShellHhtIntegrator {
public:
ValueShellIntegrator(bool& destroyed, std::vector<std::string>& events)
: destroyed_(destroyed), events_(events) {}
~ValueShellIntegrator() override { destroyed_ = true; }
std::function<shell_value::ReducedShellPredictor(
const shell_value::ReducedHistory&, double, const fesa::HhtParameters&)> predict;
std::function<void(const shell_value::ReducedShellRateRequest&,
fesa::Vector&, fesa::Vector&)> rates;
std::function<fesa::Result<shell_value::ReconstructedRates>(
const shell_value::FixedChart&, const shell_value::EmbeddingRoot&,
const fesa::Vector&, const fesa::Vector&, fesa::TangentSolver&)> reconstruct;
shell_value::ReducedShellPredictor Predict(
const shell_value::ReducedHistory& history, double h,
const fesa::HhtParameters& parameters) const override {
events_.push_back("predict");
return predict(history, h, parameters);
}
void EvaluateRates(const shell_value::ReducedShellRateRequest& request,
fesa::Vector& velocity, fesa::Vector& acceleration) const override {
events_.push_back("rates");
rates(request, velocity, acceleration);
}
fesa::Result<shell_value::ReconstructedRates> ReconstructPhysicalRates(
const shell_value::FixedChart& chart, const shell_value::EmbeddingRoot& root,
const fesa::Vector& velocity, const fesa::Vector& acceleration,
fesa::TangentSolver& solver) const override {
events_.push_back("reconstruct");
return reconstruct(chart, root, velocity, acceleration, solver);
}
private:
bool& destroyed_;
std::vector<std::string>& events_;
};
class ValueSolidIntegrator final : public fesa::detail::SolidHhtIntegrator {
public:
ValueSolidIntegrator(bool& destroyed, std::vector<std::string>& events)
: destroyed_(destroyed), events_(events) {}
~ValueSolidIntegrator() override { destroyed_ = true; }
std::function<fesa::Result<fesa::detail::SolidHhtPairedPredictor>(
const fesa::SolidDynamicAcceptedFrameState&, double,
const fesa::HhtParameters&)> predict;
std::function<fesa::Result<fesa::detail::SolidHhtPairedRates>(
const fesa::Vector&, const fesa::Vector&,
const fesa::detail::SolidHhtPairedPredictor&, const fesa::Vector&,
const fesa::Vector&, double, const fesa::HhtParameters&)> rates;
fesa::Result<fesa::detail::SolidHhtPairedPredictor> Predict(
const fesa::SolidDynamicAcceptedFrameState& old, double h,
const fesa::HhtParameters& parameters) const override {
events_.push_back("predict");
return predict(old, h, parameters);
}
fesa::Result<fesa::detail::SolidHhtPairedRates> EvaluateRates(
const fesa::Vector& high, const fesa::Vector& low,
const fesa::detail::SolidHhtPairedPredictor& predictor,
const fesa::Vector& old_acceleration,
const fesa::Vector& old_acceleration_low, double h,
const fesa::HhtParameters& parameters) const override {
events_.push_back("rates");
return rates(high, low, predictor, old_acceleration, old_acceleration_low,
h, parameters);
}
private:
bool& destroyed_;
std::vector<std::string>& events_;
};
namespace heat_value = fesa::thermal_transient_internal;
class ValueHeatIntegrator final : public heat_value::HeatStepIntegrator {
public:
ValueHeatIntegrator(bool& destroyed, std::vector<std::string>& events)
: destroyed_(destroyed), events_(events) {}
~ValueHeatIntegrator() override { destroyed_ = true; }
std::function<fesa::Result<heat_value::ThermalStepCandidate>(
const heat_value::ThermalSystem&, const heat_value::ThermalPartition&,
const fesa::Vector&, double, fesa::LinearSolver&,
heat_value::ThermalFactorization&)> solve;
fesa::Result<heat_value::ThermalStepCandidate> SolveIncrement(
const heat_value::ThermalSystem& system,
const heat_value::ThermalPartition& partition,
const fesa::Vector& old, double delta, fesa::LinearSolver& solver,
heat_value::ThermalFactorization& cache) const override {
events_.push_back("solve");
return solve(system, partition, old, delta, solver, cache);
}
private:
bool& destroyed_;
std::vector<std::string>& events_;
};
} // namespace
TEST(EquilibriumAlgorithm, InjectsAlternativeOrderAndPreservesTypedPendingOnFailure) {
bool destroyed = false;
auto owned = std::make_unique<SelectedOrderAlgorithm>(destroyed);
auto* selected = owned.get();
std::unique_ptr<fesa::EquilibriumAlgorithm> policy = std::move(owned);
std::vector<std::uint32_t> observed;
using Candidate = std::unique_ptr<int>;
using EvaluationResult = fesa::Result<std::optional<Candidate>>;
fesa::EquilibriumStep<Candidate, 0U> step(
[&](std::uint32_t iteration) {
observed.push_back(iteration);
return iteration == 3U
? EvaluationResult::Success(std::make_unique<int>(42))
: EvaluationResult::Success(std::nullopt);
}, 3U);
ASSERT_TRUE(step.Solve(*policy).IsOk());
EXPECT_EQ(observed, (std::vector<std::uint32_t>{0U, 3U}));
selected->fail = true;
ExpectInjectionFailure(step.Solve(*policy));
EXPECT_EQ(observed, (std::vector<std::uint32_t>{0U, 3U}));
ASSERT_TRUE(step.HasCandidate());
EXPECT_EQ(*step.TakeCandidate(), 42);
EXPECT_FALSE(destroyed);
policy.reset();
EXPECT_TRUE(destroyed);
// The default algorithm still evaluates zero through the inclusive maximum.
std::vector<std::uint32_t> zero_observed;
fesa::NewtonStepAlgorithm zero_algorithm;
fesa::EquilibriumStep<int, 0U> zero_step([&](std::uint32_t iteration) {
zero_observed.push_back(iteration);
return iteration == 2U ? fesa::Result<std::optional<int>>::Success(7)
: fesa::Result<std::optional<int>>::Success(std::nullopt);
}, 2U);
ASSERT_TRUE(zero_step.Solve(zero_algorithm).IsOk());
EXPECT_EQ(zero_observed, (std::vector<std::uint32_t>{0U, 1U, 2U}));
ASSERT_TRUE(zero_step.HasCandidate());
EXPECT_EQ(zero_step.TakeCandidate(), 7);
}
TEST(ProcedureIntegratorInjection, ShellValuesUseProductionStepAndPreserveHistory) {
bool destroyed = false;
std::vector<std::string> events;
auto owned = std::make_unique<ValueShellIntegrator>(destroyed, events);
auto* fake = owned.get();
std::unique_ptr<shell_value::ShellHhtIntegrator> integrator = std::move(owned);
shell_value::ReducedHistory old{fesa::Vector(2, 1), fesa::Vector(2, 2),
fesa::Vector(2, 3), fesa::Vector(2, 4), fesa::Vector(2, 5)};
shell_value::FixedChart chart;
shell_value::EmbeddingRoot root;
fesa::Vector algorithmic(2, 6), retained_v(2, 7), retained_a(2, 8);
const fesa::HhtParameters parameters{11, 12, 13};
NeverCalledTangent solver;
bool fail_reconstruction = false, fail_commit = false;
fake->predict = [&](const auto& history, double h, const auto& p) {
EXPECT_EQ(&history, &old); EXPECT_EQ(&p, &parameters); EXPECT_DOUBLE_EQ(h, 17);
return shell_value::ReducedShellPredictor{fesa::Vector(2, 21), fesa::Vector(2, 22),
fesa::Vector(2, 23), fesa::Vector(2, 24), fesa::Vector(2, 25), fesa::Vector(2, 26)};
};
fake->rates = [&](const auto& request, auto& velocity, auto& acceleration) {
EXPECT_EQ(&request.history, &old); EXPECT_EQ(&request.chart, &chart);
EXPECT_EQ(&request.trial_algorithmic, &algorithmic);
EXPECT_EQ(&request.retained_velocity, &retained_v);
EXPECT_EQ(&request.retained_acceleration, &retained_a);
EXPECT_EQ(&request.parameters, &parameters); EXPECT_DOUBLE_EQ(request.h, 17);
EXPECT_FALSE(request.copy_translation_rates); ExpectValueVector(request.vpp, 22);
EXPECT_NE(velocity.Data(), acceleration.Data());
EXPECT_NE(velocity.Data(), request.vpp.Data());
EXPECT_NE(acceleration.Data(), request.trial_algorithmic.Data());
ExpectValueVector(velocity, 0); ExpectValueVector(acceleration, 0);
for (std::size_t i = 0; i < 2; ++i) { velocity[i] = 31; acceleration[i] = 32; }
};
fake->reconstruct = [&](const auto& c, const auto& r, const auto& v,
const auto& a, auto& backend) {
EXPECT_EQ(&c, &chart); EXPECT_EQ(&r, &root); EXPECT_EQ(&backend, &solver);
ExpectValueVector(v, 31); ExpectValueVector(a, 32);
if (fail_reconstruction)
return fesa::Result<shell_value::ReconstructedRates>::Failure(InjectionFailure());
return fesa::Result<shell_value::ReconstructedRates>::Success({
fesa::Vector(2, 41), fesa::Vector(2, 42), fesa::Vector(2, 43), fesa::Vector(2, 44)});
};
std::vector<shell_value::ReconstructedRates> history;
const auto evaluate = [&](const auto& selected, auto predictor) {
ExpectValueVector(predictor.pp, 21); ExpectValueVector(predictor.vpp, 22);
ExpectValueVector(predictor.coordinate_low, 23);
ExpectValueVector(predictor.increment_trace, 24);
ExpectValueVector(predictor.increment_trace_compensation, 25);
ExpectValueVector(predictor.increment_trace_absolute_sum, 26);
shell_value::ReducedShellTrialRates rates{fesa::Vector(2), fesa::Vector(2)};
auto* v_data = rates.velocity.Data(); auto* a_data = rates.acceleration.Data();
selected.EvaluateRates({chart, old, algorithmic, predictor.vpp, false,
retained_v, retained_a, 17, parameters}, rates.velocity, rates.acceleration);
EXPECT_EQ(v_data, rates.velocity.Data()); EXPECT_EQ(a_data, rates.acceleration.Data());
return selected.ReconstructPhysicalRates(chart, root, rates.velocity,
rates.acceleration, solver);
};
const auto commit = [&](shell_value::ReconstructedRates&& pending) {
events.push_back("commit");
ExpectValueVector(pending.velocity, 41); ExpectValueVector(pending.acceleration, 42);
ExpectValueVector(pending.z_velocity, 43); ExpectValueVector(pending.z_acceleration, 44);
if (fail_commit) return InjectionFailure();
history.push_back(std::move(pending));
return fesa::Status::Ok();
};
ASSERT_TRUE(shell_value::RunReducedHhtStep(*integrator, old, 17, parameters,
evaluate, commit).IsOk());
EXPECT_EQ(events, (std::vector<std::string>{"predict", "rates", "reconstruct", "commit"}));
ASSERT_EQ(history.size(), 1U);
events.clear(); fail_reconstruction = true;
ExpectInjectionFailure(shell_value::RunReducedHhtStep(*integrator, old, 17,
parameters, evaluate, commit));
EXPECT_EQ(events, (std::vector<std::string>{"predict", "rates", "reconstruct"}));
EXPECT_EQ(history.size(), 1U);
events.clear(); fail_reconstruction = false; fail_commit = true;
ExpectInjectionFailure(shell_value::RunReducedHhtStep(*integrator, old, 17,
parameters, evaluate, commit));
EXPECT_EQ(events, (std::vector<std::string>{"predict", "rates", "reconstruct", "commit"}));
EXPECT_EQ(history.size(), 1U);
ExpectValueVector(old.p, 1); ExpectValueVector(old.velocity, 2);
ExpectValueVector(old.acceleration, 3); ExpectValueVector(old.algorithmic_acceleration, 4);
ExpectValueVector(old.z, 5); ExpectValueVector(history[0].velocity, 41);
EXPECT_FALSE(destroyed); integrator.reset(); EXPECT_TRUE(destroyed);
}
TEST(ProcedureIntegratorInjection, SolidPairedValuesUseProductionStepAndPreserveHistory) {
bool destroyed = false;
std::vector<std::string> events;
auto owned = std::make_unique<ValueSolidIntegrator>(destroyed, events);
auto* fake = owned.get();
std::unique_ptr<fesa::detail::SolidHhtIntegrator> integrator = std::move(owned);
fesa::SolidDynamicAcceptedFrameState old;
old.displacement = fesa::Vector(2, 1); old.low.displacement = fesa::Vector(2, 2);
old.velocity = fesa::Vector(2, 3); old.low.velocity = fesa::Vector(2, 4);
old.acceleration = fesa::Vector(2, 5); old.low.acceleration = fesa::Vector(2, 6);
old.algorithmic_acceleration = fesa::Vector(2, 7);
old.low.algorithmic_acceleration = fesa::Vector(2, 8);
const fesa::HhtParameters parameters{11, 12, 13};
bool fail_predict = false, fail_rates = false, fail_commit = false;
fake->predict = [&](const auto& frame, double h, const auto& p) {
EXPECT_EQ(&frame, &old); EXPECT_EQ(&p, &parameters); EXPECT_DOUBLE_EQ(h, 17);
if (fail_predict)
return fesa::Result<fesa::detail::SolidHhtPairedPredictor>::Failure(InjectionFailure());
return fesa::Result<fesa::detail::SolidHhtPairedPredictor>::Success({
fesa::Vector(2, 21), fesa::Vector(2, 22), fesa::Vector(2, 23), fesa::Vector(2, 24)});
};
fake->rates = [&](const auto& high, const auto& low, const auto& predictor,
const auto& old_a, const auto& old_a_low, double h, const auto& p) {
ExpectValueVector(high, 1); ExpectValueVector(low, 2);
ExpectValueVector(predictor.displacement, 21); ExpectValueVector(predictor.displacement_low, 22);
ExpectValueVector(predictor.velocity, 23); ExpectValueVector(predictor.velocity_low, 24);
EXPECT_EQ(&old_a, &old.acceleration); EXPECT_EQ(&old_a_low, &old.low.acceleration);
EXPECT_EQ(&p, &parameters); EXPECT_DOUBLE_EQ(h, 17);
if (fail_rates)
return fesa::Result<fesa::detail::SolidHhtPairedRates>::Failure(InjectionFailure());
return fesa::Result<fesa::detail::SolidHhtPairedRates>::Success({
fesa::Vector(2, 31), fesa::Vector(2, 32), fesa::Vector(2, 33),
fesa::Vector(2, 34), fesa::Vector(2, 35), fesa::Vector(2, 36)});
};
std::vector<fesa::SolidDynamicAcceptedFrameState> history;
const auto failure = [&](const fesa::Status& status) {
events.push_back("predict-failure"); return status;
};
const auto evaluate = [&](const auto& selected, const auto& predictor) {
auto pending = old;
const auto status = fesa::detail::SetSolidHhtRates(selected, old, predictor,
17, parameters, &pending);
if (!status.IsOk()) {
ExpectValueVector(pending.velocity, 3); ExpectValueVector(pending.low.velocity, 4);
ExpectValueVector(pending.acceleration, 5); ExpectValueVector(pending.low.acceleration, 6);
ExpectValueVector(pending.algorithmic_acceleration, 7);
ExpectValueVector(pending.low.algorithmic_acceleration, 8);
return fesa::Result<fesa::SolidDynamicAcceptedFrameState>::Failure(status);
}
return fesa::Result<fesa::SolidDynamicAcceptedFrameState>::Success(std::move(pending));
};
const auto commit = [&](fesa::SolidDynamicAcceptedFrameState&& pending) {
events.push_back("commit");
ExpectValueVector(pending.displacement, 1); ExpectValueVector(pending.low.displacement, 2);
ExpectValueVector(pending.velocity, 31); ExpectValueVector(pending.low.velocity, 32);
ExpectValueVector(pending.acceleration, 33); ExpectValueVector(pending.low.acceleration, 34);
ExpectValueVector(pending.algorithmic_acceleration, 35);
ExpectValueVector(pending.low.algorithmic_acceleration, 36);
if (fail_commit) return InjectionFailure();
history.push_back(std::move(pending)); return fesa::Status::Ok();
};
ASSERT_TRUE(fesa::detail::RunSolidHhtStep(*integrator, old, 17, parameters,
failure, evaluate, commit).IsOk());
EXPECT_EQ(events, (std::vector<std::string>{"predict", "rates", "commit"}));
ASSERT_EQ(history.size(), 1U);
events.clear(); fail_predict = true;
ExpectInjectionFailure(fesa::detail::RunSolidHhtStep(*integrator, old, 17,
parameters, failure, evaluate, commit));
EXPECT_EQ(events, (std::vector<std::string>{"predict", "predict-failure"}));
EXPECT_EQ(history.size(), 1U);
events.clear(); fail_predict = false; fail_rates = true;
ExpectInjectionFailure(fesa::detail::RunSolidHhtStep(*integrator, old, 17,
parameters, failure, evaluate, commit));
EXPECT_EQ(events, (std::vector<std::string>{"predict", "rates"}));
EXPECT_EQ(history.size(), 1U);
events.clear(); fail_rates = false; fail_commit = true;
ExpectInjectionFailure(fesa::detail::RunSolidHhtStep(*integrator, old, 17,
parameters, failure, evaluate, commit));
EXPECT_EQ(events, (std::vector<std::string>{"predict", "rates", "commit"}));
EXPECT_EQ(history.size(), 1U);
ExpectValueVector(old.displacement, 1); ExpectValueVector(old.low.displacement, 2);
ExpectValueVector(old.velocity, 3); ExpectValueVector(old.low.velocity, 4);
ExpectValueVector(old.acceleration, 5); ExpectValueVector(old.low.acceleration, 6);
ExpectValueVector(old.algorithmic_acceleration, 7); ExpectValueVector(old.low.algorithmic_acceleration, 8);
ExpectValueVector(history[0].low.algorithmic_acceleration, 36);
EXPECT_FALSE(destroyed); integrator.reset(); EXPECT_TRUE(destroyed);
}
TEST(ProcedureIntegratorInjection, HeatValuesUseProductionRecoveryCommitAndPreserveHistory) {
bool destroyed = false;
std::vector<std::string> events;
auto owned = std::make_unique<ValueHeatIntegrator>(destroyed, events);
auto* fake = owned.get();
std::unique_ptr<heat_value::HeatStepIntegrator> integrator = std::move(owned);
auto matrix = fesa::SparseMatrix::FromCoo(
2, 2, {}, fesa::SparsePattern{{0, 0, 0}, {}});
ASSERT_TRUE(matrix.HasValue());
const auto& k = matrix.Value();
const fesa::SparseMatrix c{k};
const fesa::Vector q(2, 2), old(2, 1);
const heat_value::ThermalSystem system{k, c, q};
const heat_value::ThermalPartition partition{{0, 1}, {}, fesa::Vector(0)};
heat_value::ThermalFactorization cache;
NeverCalledLinear solver;
bool fail_solve = false, fail_recovery = false, fail_commit = false;
fake->solve = [&](const auto& supplied, const auto& p, const auto& previous,
double delta, auto& backend, auto& workspace) {
EXPECT_EQ(&supplied, &system); EXPECT_EQ(&p, &partition); EXPECT_EQ(&previous, &old);
EXPECT_EQ(&backend, &solver); EXPECT_EQ(&workspace, &cache); EXPECT_DOUBLE_EQ(delta, 17);
EXPECT_EQ(&supplied.k, &k); EXPECT_EQ(&supplied.c, &c); EXPECT_EQ(&supplied.q, &q);
EXPECT_EQ(supplied.k_error, nullptr); EXPECT_EQ(supplied.c_error, nullptr);
EXPECT_EQ(supplied.q_error, nullptr); EXPECT_EQ(p.free, (std::vector<std::size_t>{0, 1}));
EXPECT_TRUE(p.constrained.empty()); EXPECT_EQ(p.prescribed.Size(), 0U);
if (fail_solve)
return fesa::Result<heat_value::ThermalStepCandidate>::Failure(InjectionFailure());
heat_value::ThermalStepCandidate pending{fesa::Vector(2, 21), fesa::Vector(2, 22),
fesa::Vector(2, 23), fesa::Vector(2, 24),
{31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, true, 42, 43}};
return fesa::Result<heat_value::ThermalStepCandidate>::Success(std::move(pending));
};
const heat_value::ThermalRecovery recovery = [&](const fesa::Vector& temperature) {
events.push_back("recovery"); ExpectValueVector(temperature, 21);
if (fail_recovery)
return fesa::Result<std::vector<fesa::ThermalElementFlux>>::Failure(InjectionFailure());
std::vector<fesa::ThermalElementFlux> flux(1);
for (auto& value : flux[0]) value = fesa::Vector3{51, 52, 53};
return fesa::Result<std::vector<fesa::ThermalElementFlux>>::Success(std::move(flux));
};
std::vector<heat_value::ThermalStepCandidate> history;
const auto commit = [&](heat_value::ThermalStepCandidate&& pending,
std::vector<fesa::ThermalElementFlux>&& flux) {
events.push_back("commit");
ExpectValueVector(pending.temperature, 21); ExpectValueVector(pending.residual, 22);
ExpectValueVector(pending.reaction, 23); ExpectValueVector(pending.certified_residual, 24);
const auto& e = pending.evidence;
EXPECT_DOUBLE_EQ(e.beta_cert, 31); EXPECT_DOUBLE_EQ(e.beta_limit, 32);
EXPECT_DOUBLE_EQ(e.free_residual_cert, 33); EXPECT_DOUBLE_EQ(e.full_free_residual_cert, 34);
EXPECT_DOUBLE_EQ(e.full_beta_cert, 35); EXPECT_DOUBLE_EQ(e.operator_construction_error, 36);
EXPECT_DOUBLE_EQ(e.rhs_construction_error, 37); EXPECT_DOUBLE_EQ(e.construction_identity_bound, 38);
EXPECT_DOUBLE_EQ(e.energy_change, 39); EXPECT_DOUBLE_EQ(e.energy_defect, 40);
EXPECT_DOUBLE_EQ(e.energy_bound, 41); EXPECT_TRUE(e.dissipation_checked);
EXPECT_DOUBLE_EQ(e.dissipation_defect, 42); EXPECT_DOUBLE_EQ(e.dissipation_bound, 43);
EXPECT_EQ(flux.size(), 1U);
for (const auto& value : flux[0]) EXPECT_EQ(value.Components(), (std::array<double, 3>{51, 52, 53}));
if (fail_commit) return InjectionFailure();
history.push_back(std::move(pending)); return fesa::Status::Ok();
};
ASSERT_TRUE(heat_value::RunThermalStep(*integrator, system, partition, old,
17, solver, cache, recovery, commit).IsOk());
EXPECT_EQ(events, (std::vector<std::string>{"solve", "recovery", "commit"}));
ASSERT_EQ(history.size(), 1U);
events.clear(); fail_solve = true;
ExpectInjectionFailure(heat_value::RunThermalStep(*integrator, system, partition,
old, 17, solver, cache, recovery, commit));
EXPECT_EQ(events, (std::vector<std::string>{"solve"})); EXPECT_EQ(history.size(), 1U);
events.clear(); fail_solve = false; fail_recovery = true;
ExpectInjectionFailure(heat_value::RunThermalStep(*integrator, system, partition,
old, 17, solver, cache, recovery, commit));
EXPECT_EQ(events, (std::vector<std::string>{"solve", "recovery"})); EXPECT_EQ(history.size(), 1U);
events.clear(); fail_recovery = false; fail_commit = true;
ExpectInjectionFailure(heat_value::RunThermalStep(*integrator, system, partition,
old, 17, solver, cache, recovery, commit));
EXPECT_EQ(events, (std::vector<std::string>{"solve", "recovery", "commit"}));
EXPECT_EQ(history.size(), 1U); ExpectValueVector(old, 1);
ExpectValueVector(history[0].temperature, 21); EXPECT_FALSE(cache.matrix.has_value());
EXPECT_FALSE(destroyed); integrator.reset(); EXPECT_TRUE(destroyed);
}