631 lines
31 KiB
C++
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, ¶meters); 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, ¶meters); 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, ¶meters); 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, ¶meters); 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);
|
|
}
|