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
@@ -7,6 +7,9 @@
#include <type_traits>
#include <utility>
#include "fesa/analysis/analysis_model.h"
#include "fesa/fem/dof_manager.h"
namespace {
template <class T, class = void>
+563
View File
@@ -1,9 +1,20 @@
#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>
@@ -65,3 +76,555 @@ TEST(Analysis, PropagatesStatusAndDestroysThroughMinimalAnalysis) {
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);
}
+67 -33
View File
@@ -6,7 +6,6 @@
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <utility>
#include <vector>
@@ -93,38 +92,6 @@ TEST(EigenReduction, RemovesOnlyActualFreeDirectorNull) {
.HasValue());
}
TEST(EigenReduction, RejectsNonfiniteUsedBasisAndPreservesPriorCandidate) {
const fesa::ShellNodeInitialFrame frame{0U, {0, 0, 1}, {1, 0, 0}, {0, 1, 0}};
const auto mass = NodalMass(frame.director);
fesa::Matrix stiffness(6U, 6U);
for (std::size_t i = 0; i < 5U; ++i) stiffness(i, i) = mass(i, i) * 7.0;
stiffness(5U, 5U) = .25;
const auto matrix = Sparse(stiffness);
const std::vector<std::uint8_t> mask(6U, 0U);
const auto prior = fesa::eigen_internal::BuildEigenReduction(
matrix, {mass}, {frame}, mask, {.25}, 2.0);
ASSERT_TRUE(prior.HasValue());
const auto prior_values = prior.Value().whitened_operator.Values();
for (const double invalid : {std::numeric_limits<double>::quiet_NaN(),
std::numeric_limits<double>::infinity()}) {
for (const bool first_tangent : {false, true}) {
auto bad_frame = frame;
(first_tangent ? bad_frame.tangent_a : bad_frame.tangent_b)[0] = invalid;
const auto rejected = fesa::eigen_internal::BuildEigenReduction(
matrix, {mass}, {bad_frame}, mask, {.25}, 2.0);
ASSERT_FALSE(rejected.HasValue());
EXPECT_EQ(rejected.GetStatus().Category(), fesa::FailureCategory::kSolver);
EXPECT_EQ(rejected.GetStatus().Diagnostics()[0].code,
"invalid-eigen-mass-space");
EXPECT_EQ(prior.Value().whitened_operator.Values(), prior_values);
}
}
const auto rerun = fesa::eigen_internal::BuildEigenReduction(
matrix, {mass}, {frame}, mask, {.25}, 2.0);
ASSERT_TRUE(rerun.HasValue());
EXPECT_EQ(rerun.Value().whitened_operator.Values(), prior_values);
}
TEST(EigenReduction, RejectsPositiveButUnresolvableMassWithoutDeletingDofs) {
const double p = 1e-8;
const std::array<double, 3> d{2 * p / (1 + p * p), 0,
@@ -215,3 +182,70 @@ TEST(EigenReduction, HandlesTwoFreeRotationsAndRejectsEmptyFiniteSpace) {
EXPECT_EQ(empty.GetStatus().Diagnostics()[0].code, "empty-eigenproblem");
}
}
TEST(EigenReduction, PureMassWhiteningRetainsInclusiveInfinityConditionGate) {
const fesa::ShellNodeInitialFrame frame{0U, {0, 0, 1}, {1, 0, 0}, {0, 1, 0}};
for (const double diagonal : {0x1p-31, 0x1p-32, 0x1p-33}) {
auto mass = NodalMass(frame.director);
mass(0, 0) = 1.0;
mass(1, 1) = diagonal;
fesa::Matrix stiffness{6U, 6U};
stiffness(0, 0) = 3.0;
stiffness(1, 1) = 3.0 * diagonal;
const auto reduced = fesa::eigen_internal::BuildEigenReduction(
Sparse(stiffness), {mass}, {frame}, {0, 0, 1, 1, 1, 1}, {0.25}, 1.0);
EXPECT_EQ(reduced.HasValue(), diagonal >= 0x1p-32);
if (reduced.HasValue()) {
EXPECT_DOUBLE_EQ(reduced.Value().nodes[0].record.rcond_inf, diagonal);
fesa::Vector one{2U, 1.0};
const auto applied = reduced.Value().whitened_operator.Multiply(one);
EXPECT_NEAR(applied[0], 3.0, 1e-12);
EXPECT_NEAR(applied[1], 3.0, 1e-12);
} else {
EXPECT_EQ(reduced.GetStatus().Diagnostics()[0].code,
"eigen-mass-conditioning");
}
}
}
TEST(EigenReduction, PureOffDiagonalWhiteningMatchesAnalyticGramAndPencil) {
const fesa::ShellNodeInitialFrame frame{0U, {0, 0, 1}, {1, 0, 0}, {0, 1, 0}};
auto mass = NodalMass(frame.director);
mass(0, 1) = mass(1, 0) = 1.0;
fesa::Matrix stiffness{6U, 6U};
stiffness(0, 0) = stiffness(1, 1) = 6.0;
stiffness(0, 1) = stiffness(1, 0) = 4.0;
// For M=[[2,1],[1,2]], physical whitening columns are analytically
// (1/sqrt(2),0) and (-1/sqrt(6),2/sqrt(6)). No eigensolver is invoked.
for (const double length : {0.25, 3.0, 16.0}) {
const auto reduced = fesa::eigen_internal::BuildEigenReduction(
Sparse(stiffness), {mass}, {frame}, {0, 0, 1, 1, 1, 1}, {0.25}, length);
ASSERT_TRUE(reduced.HasValue());
fesa::Matrix canonical{2U, 2U};
canonical(0, 0) = canonical(1, 1) = 1.0;
std::array<fesa::Vector, 2> physical{fesa::Vector{6U}, fesa::Vector{6U}};
for (std::size_t column = 0; column < 2U; ++column) {
physical[column] = fesa::eigen_internal::RestoreScaledMode(
reduced.Value(), canonical, column);
for (std::size_t i = 0; i < 3U; ++i) physical[column][i] *= length;
}
EXPECT_NEAR(physical[0][0], 1.0 / std::sqrt(2.0), 1e-12);
EXPECT_NEAR(physical[0][1], 0.0, 1e-12);
EXPECT_NEAR(physical[1][0], -1.0 / std::sqrt(6.0), 1e-12);
EXPECT_NEAR(physical[1][1], 2.0 / std::sqrt(6.0), 1e-12);
const double expected[2][2]{{3.0, 1.0 / std::sqrt(3.0)},
{1.0 / std::sqrt(3.0), 7.0 / 3.0}};
for (std::size_t column = 0; column < 2U; ++column) {
fesa::Vector coordinate{2U};
coordinate[column] = 1.0;
const auto action = reduced.Value().whitened_operator.Multiply(coordinate);
const auto mq = mass.Multiply(physical[column]);
const auto kq = stiffness.Multiply(physical[column]);
for (std::size_t row = 0; row < 2U; ++row) {
EXPECT_NEAR(physical[row].Dot(mq), row == column ? 1.0 : 0.0, 1e-12);
EXPECT_NEAR(action[row], expected[row][column], 1e-12);
EXPECT_NEAR(action[row], physical[row].Dot(kq), 1e-12);
}
}
}
}
@@ -5,8 +5,6 @@
#include <cmath>
#include <cfenv>
#include <cstddef>
#include <cstring>
#include <functional>
#include <iostream>
#include <memory>
#include <limits>
@@ -21,7 +19,6 @@
#endif
#include "analysis/hht_reduced_internal.h"
#include "math/dense_blas_internal.h"
#include "fesa/analysis/analysis_model.h"
#include "fesa/analysis/hht_time_integration.h"
#include "fesa/assembly/parallel_for.h"
@@ -29,71 +26,6 @@
namespace {
class RestoreTrialEnvironment {
public:
RestoreTrialEnvironment()
: rounding_(std::fegetround()),
dynamic_(mkl_get_dynamic()),
tls_(mkl_set_num_threads_local(0)) {
#if defined(_M_X64)
mxcsr_ = _mm_getcsr();
_mm_setcsr((_mm_getcsr() | 0x1F80U) & ~0x8040U);
#endif
static_cast<void>(std::fesetround(FE_TONEAREST));
mkl_set_dynamic(0);
}
~RestoreTrialEnvironment() {
mkl_set_dynamic(dynamic_);
static_cast<void>(mkl_set_num_threads_local(tls_));
static_cast<void>(std::fesetround(rounding_));
#if defined(_M_X64)
_mm_setcsr(mxcsr_);
#endif
}
private:
int rounding_;
int dynamic_;
int tls_;
#if defined(_M_X64)
unsigned int mxcsr_;
#endif
};
void ExpectSameBits(const double left, const double right) {
EXPECT_EQ(std::memcmp(&left, &right, sizeof(double)), 0);
}
void ExpectSameOperators(
const fesa::hht_reduced_internal::ConstraintOperators& left,
const fesa::hht_reduced_internal::ConstraintOperators& right) {
ASSERT_EQ(left.rows.size(), right.rows.size());
for (std::size_t row = 0U; row < left.rows.size(); ++row) {
const auto& a = left.rows[row];
const auto& b = right.rows[row];
ExpectSameBits(a.value, b.value);
ExpectSameBits(a.value_scale, b.value_scale);
ExpectSameBits(a.time_first, b.time_first);
ExpectSameBits(a.time_second, b.time_second);
for (const auto& pair : {std::make_pair(&a.gradient, &b.gradient),
std::make_pair(&a.time_gradient, &b.time_gradient),
std::make_pair(&a.third_directional_gradient,
&b.third_directional_gradient)}) {
ASSERT_EQ(pair.first->size(), pair.second->size());
for (std::size_t i = 0U; i < pair.first->size(); ++i) {
EXPECT_EQ((*pair.first)[i].dof, (*pair.second)[i].dof);
ExpectSameBits((*pair.first)[i].value, (*pair.second)[i].value);
}
}
ASSERT_EQ(a.hessian.size(), b.hessian.size());
for (std::size_t i = 0U; i < a.hessian.size(); ++i) {
EXPECT_EQ(a.hessian[i].row, b.hessian[i].row);
EXPECT_EQ(a.hessian[i].column, b.hessian[i].column);
ExpectSameBits(a.hessian[i].value, b.hessian[i].value);
}
}
}
struct Fixture {
std::unique_ptr<fesa::Domain> domain;
fesa::DofManager dofs;
@@ -462,480 +394,6 @@ TEST(HhtReducedChart, InvertibleGraphDoesNotCertifySelectorTransversality) {
assembly, chart.Value(), solver).HasValue());
}
TEST(HhtReducedOperators,
CompleteTrialConstraintDataMatchesFreshAndOwnsCopies) {
RestoreTrialEnvironment restore;
const auto fixture = MakeFixture(false, true);
const auto shells =
fesa::hht_internal::BuildRuntimeShells(*fixture.domain, fixture.dofs);
ASSERT_TRUE(shells.HasValue());
const fesa::Vector zero(fixture.dofs.FullDofCount());
auto q = zero;
auto v = zero;
auto force = zero;
auto rate = zero;
auto acceleration = zero;
for (std::size_t i = 0U; i < zero.Size(); ++i) {
q[i] = 0.001 * static_cast<double>((i % 5U) + 1U);
v[i] = 0.002 * static_cast<double>((i % 7U) + 1U);
if (i % 6U >= 3U) {
force[i] = 0.03;
rate[i] = -0.02;
acceleration[i] = 0.01;
}
}
auto assembled = Assemble(fixture, shells.Value(), q, v, zero, force, true);
ASSERT_TRUE(assembled.HasValue());
ASSERT_TRUE(assembled.Value().trial_constraints.has_value());
ASSERT_EQ(assembled.Value().trial_constraints->shells.size(),
shells.Value().size());
const auto chart = fesa::hht_reduced_internal::BuildFixedChart(
fixture.dofs, assembled.Value().rotations);
ASSERT_TRUE(chart.HasValue());
for (std::size_t e = 0U; e < shells.Value().size(); ++e) {
const auto& saved = assembled.Value().trial_constraints->shells[e];
ASSERT_TRUE(saved.directional_internal);
fesa::NonlinearMitc4TrialDofs trial{};
fesa::Vector direction{24U};
for (std::size_t node = 0U; node < 4U; ++node) {
const auto first = 6U * node;
trial.displacement[node] =
fesa::Vector3{saved.q[first], saved.q[first + 1U], saved.q[first + 2U]};
trial.rotation_vector[node] = fesa::Vector3{
saved.q[first + 3U], saved.q[first + 4U], saved.q[first + 5U]};
}
for (std::size_t i = 0U; i < 24U; ++i) direction[i] = saved.v[i];
const auto response = fesa::EvaluateInternalResidualAndTangent(
shells.Value()[e].internal, trial, &direction);
ASSERT_TRUE(response.HasValue());
for (std::size_t node = 0U; node < 4U; ++node) {
ExpectSameBits(saved.drilling_constraint[node],
response.Value().drilling_constraint[node]);
for (std::size_t i = 0U; i < 24U; ++i) {
ExpectSameBits(saved.drilling_constraint_gradient(node, i),
response.Value().drilling_constraint_gradient(node, i));
ExpectSameBits(
saved.drilling_constraint_third_directional_gradient[node][i],
response.Value()
.drilling_constraint_third_directional_gradient[node][i]);
for (std::size_t j = 0U; j < 24U; ++j)
ExpectSameBits(
saved.drilling_constraint_hessian[node](i, j),
response.Value().drilling_constraint_hessian[node](i, j));
}
}
}
const auto evaluate = [&](const fesa::hht_internal::DynamicAssembly& value) {
return fesa::hht_reduced_internal::EvaluateConstraintOperators(
*fixture.domain, fixture.dofs, shells.Value(), value, chart.Value(),
rate, acceleration);
};
auto fresh = assembled.Value();
fresh.trial_constraints.reset();
const auto reference = evaluate(fresh);
const auto reused = evaluate(assembled.Value());
ASSERT_TRUE(reference.HasValue());
ASSERT_TRUE(reused.HasValue());
ExpectSameOperators(reused.Value(), reference.Value());
auto copy = assembled.Value();
copy.trial_constraints->shells[0U].drilling_constraint[0U] += 7.0;
// Test-only finite marker proves the selected route actually reads its owner.
const auto marker = evaluate(copy);
ASSERT_TRUE(marker.HasValue());
EXPECT_NE(marker.Value().rows[0U].value, reused.Value().rows[0U].value);
EXPECT_NE(
copy.trial_constraints->shells[0U].drilling_constraint[0U],
assembled.Value().trial_constraints->shells[0U].drilling_constraint[0U]);
auto moved = std::move(assembled.Value());
const auto moved_result = evaluate(moved);
ASSERT_TRUE(moved_result.HasValue());
ExpectSameOperators(moved_result.Value(), reference.Value());
fesa::DynamicAcceptedFrameState frame{};
frame.generalized_coordinates = q;
frame.velocity = v;
frame.acceleration = zero;
frame.algorithmic_acceleration = zero;
frame.external_force = force;
const auto parameters = fesa::MakeHhtParameters(-0.05);
ASSERT_TRUE(parameters.HasValue());
fesa::TbbParallelFor parallel;
const auto tbb = fesa::hht_internal::EvaluateAssembly(
*fixture.domain, fixture.dofs, shells.Value(), frame, zero, 0.1,
parameters.Value(), parallel, true);
ASSERT_TRUE(tbb.HasValue());
const auto tbb_result = evaluate(tbb.Value());
ASSERT_TRUE(tbb_result.HasValue());
ExpectSameOperators(tbb_result.Value(), reference.Value());
}
TEST(HhtReducedOperators,
ChangedTrialConstraintContextFallsBackInOriginalPosition) {
RestoreTrialEnvironment restore;
const auto fixture = MakeFixture(false);
const auto shells =
fesa::hht_internal::BuildRuntimeShells(*fixture.domain, fixture.dofs);
ASSERT_TRUE(shells.HasValue());
const fesa::Vector zero(fixture.dofs.FullDofCount());
const auto assembled =
Assemble(fixture, shells.Value(), zero, zero, zero, zero, true);
ASSERT_TRUE(assembled.HasValue());
ASSERT_TRUE(assembled.Value().trial_constraints.has_value());
ASSERT_TRUE(
assembled.Value().trial_constraints->shells[0U].directional_internal);
const auto chart = fesa::hht_reduced_internal::BuildFixedChart(
fixture.dofs, assembled.Value().rotations);
ASSERT_TRUE(chart.HasValue());
const auto evaluate = [&](const fesa::hht_internal::DynamicAssembly& value) {
return fesa::hht_reduced_internal::EvaluateConstraintOperators(
*fixture.domain, fixture.dofs, shells.Value(), value, chart.Value(),
zero, zero);
};
using Assembly = fesa::hht_internal::DynamicAssembly;
std::vector<std::function<void(Assembly&)>> changes{
[](Assembly& a) { a.frame.generalized_coordinates[2U] = 0.001; },
[](Assembly& a) { a.frame.velocity[3U] = 0.003; },
[](Assembly& a) { a.frame.time = 0.1; },
[](Assembly& a) { ++a.frame.frame_index; },
[](Assembly& a) { ++a.frame.increment_index; },
[](Assembly& a) { ++a.frame.iteration_count; },
[](Assembly& a) { a.frame.is_initial = true; },
[](Assembly& a) { a.trial_constraints->source_path = "different.inp"; },
[](Assembly& a) { ++a.trial_constraints->shells[0U].element_index; },
[](Assembly& a) { ++a.trial_constraints->shells[0U].scatter[0U]; },
[](Assembly& a) {
a.trial_constraints->shells[0U].directional_internal = false;
},
[](Assembly& a) {
a.trial_constraints->shells[0U].node_source_ids[0U].instance_name +=
"x";
},
[](Assembly& a) {
++a.trial_constraints->shells[0U].node_source_ids[0U].source_label;
},
[](Assembly& a) {
a.trial_constraints->shells[0U].node_source_ids[0U].source_label_text +=
"x";
},
[](Assembly& a) {
++a.trial_constraints->shells[0U].configuration.source.line;
},
[](Assembly& a) {
a.trial_constraints->shells[0U].configuration.source.file =
"different.inp";
},
[](Assembly& a) {
a.trial_constraints->shells[0U].configuration.identity += "x";
},
[](Assembly& a) { a.trial_constraints->shells[0U].q[0U] = -0.0; },
[](Assembly& a) { a.trial_constraints->shells[0U].v[0U] = -0.0; },
[](Assembly& a) {
a.trial_constraints->shells[0U].drilling_constraint_gradient =
fesa::Matrix{0U, 0U};
},
[](Assembly& a) {
a.trial_constraints->shells[0U].drilling_constraint_hessian[2U] =
fesa::Matrix{0U, 0U};
},
[](Assembly& a) {
a.trial_constraints->shells[0U]
.drilling_constraint_third_directional_gradient[1U] =
fesa::Vector{0U};
},
[](Assembly& a) {
a.trial_constraints->shells[0U].drilling_constraint_hessian[0U](
0U, 0U) = std::numeric_limits<double>::infinity();
},
[](Assembly& a) {
a.trial_constraints->shells[0U]
.evaluation_context.dense_policy.blas_max_threads = 2;
},
[](Assembly& a) {
a.trial_constraints->shells[0U]
.evaluation_context.dense_policy.dynamic = 1;
},
[](Assembly& a) {
a.trial_constraints->shells[0U]
.evaluation_context.dense_policy.dynamic = -1;
},
[](Assembly& a) {
a.trial_constraints->shells[0U].evaluation_context.mxcsr_control ^=
0x8000U;
},
[](Assembly& a) {
a.trial_constraints->shells[0U].evaluation_context.supported = false;
}};
for (std::size_t i = 0U; i < 64U; ++i)
changes.push_back([i](Assembly& a) {
a.trial_constraints->shells[0U].configuration.values[i] += 0.125;
});
for (const double invalid : {std::numeric_limits<double>::quiet_NaN(),
std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity()})
changes.push_back([invalid](Assembly& a) {
a.trial_constraints->shells[0U].configuration.values[60U] = invalid;
});
for (std::size_t i = 0U; i < changes.size(); ++i) {
SCOPED_TRACE(i);
auto candidate = assembled.Value();
candidate.trial_constraints->shells[0U].drilling_constraint[0U] = 17.0;
changes[i](candidate);
auto fresh = candidate;
fresh.trial_constraints.reset();
const auto reference = evaluate(fresh);
const auto actual = evaluate(candidate);
ASSERT_TRUE(reference.HasValue());
ASSERT_TRUE(actual.HasValue());
ExpectSameOperators(actual.Value(), reference.Value());
}
for (std::size_t kind = 0U; kind < 2U; ++kind) {
auto candidate = assembled.Value();
if (kind == 0U) candidate.trial_constraints->shells.clear();
if (kind == 1U) candidate.trial_constraints.reset();
auto fresh = assembled.Value();
fresh.trial_constraints.reset();
const auto reference = evaluate(fresh);
const auto actual = evaluate(candidate);
ASSERT_TRUE(reference.HasValue());
ASSERT_TRUE(actual.HasValue());
ExpectSameOperators(actual.Value(), reference.Value());
}
// Change the actual consumer environment, preserving the original fallback.
for (std::size_t mode = 0U; mode < 5U; ++mode) {
SCOPED_TRACE(mode);
auto candidate = assembled.Value();
candidate.trial_constraints->shells[0U].drilling_constraint[0U] = 17.0;
auto fresh = candidate;
fresh.trial_constraints.reset();
#if defined(_M_X64)
const auto csr = _mm_getcsr();
if (mode == 0U) _mm_setcsr(csr ^ 0x8000U);
if (mode == 1U) _mm_setcsr(csr ^ 0x0040U);
#endif
const int rounding = std::fegetround();
if (mode == 2U) static_cast<void>(std::fesetround(FE_DOWNWARD));
if (mode == 3U) static_cast<void>(mkl_set_num_threads_local(2));
if (mode == 4U) mkl_set_dynamic(1);
const auto reference = evaluate(fresh);
const auto actual = evaluate(candidate);
if (mode == 4U) mkl_set_dynamic(0);
if (mode == 3U) EXPECT_EQ(mkl_set_num_threads_local(0), 2);
static_cast<void>(std::fesetround(rounding));
#if defined(_M_X64)
_mm_setcsr(csr);
#endif
ASSERT_TRUE(reference.HasValue());
ASSERT_TRUE(actual.HasValue());
ExpectSameOperators(actual.Value(), reference.Value());
}
auto failed_trial = assembled.Value();
failed_trial.frame.generalized_coordinates[0U] =
std::numeric_limits<double>::infinity();
auto fresh_failure = failed_trial;
fresh_failure.trial_constraints.reset();
const auto failed = evaluate(failed_trial);
const auto original = evaluate(fresh_failure);
ASSERT_FALSE(failed.HasValue());
ASSERT_FALSE(original.HasValue());
ASSERT_EQ(failed.GetStatus().Diagnostics().size(),
original.GetStatus().Diagnostics().size());
EXPECT_EQ(failed.GetStatus().Diagnostics()[0U].code,
original.GetStatus().Diagnostics()[0U].code);
EXPECT_EQ(failed.GetStatus().Diagnostics()[0U].message,
original.GetStatus().Diagnostics()[0U].message);
const auto rerun = evaluate(assembled.Value());
auto fresh = assembled.Value();
fresh.trial_constraints.reset();
const auto reference = evaluate(fresh);
ASSERT_TRUE(rerun.HasValue());
ASSERT_TRUE(reference.HasValue());
ExpectSameOperators(rerun.Value(), reference.Value());
}
TEST(HhtReducedOperators, TrialContextControlsAndInitialFalseFailureLifetime) {
RestoreTrialEnvironment restore;
const auto context = fesa::hht_internal::ReadTrialEvaluationContext();
ASSERT_TRUE(fesa::hht_internal::ReusableTrialContext(context, context));
for (const unsigned int bit : {0x0040U, 0x8000U, 0x2000U, 0x0080U}) {
auto changed = context;
changed.mxcsr_control ^= bit;
EXPECT_FALSE(fesa::hht_internal::ReusableTrialContext(context, changed));
}
auto unmasked = context;
unmasked.mxcsr_control &= ~0x0080U;
EXPECT_FALSE(fesa::hht_internal::ReusableTrialContext(unmasked, unmasked));
static_cast<void>(mkl_set_num_threads_local(2));
const auto positive = fesa::hht_internal::ReadTrialEvaluationContext();
EXPECT_FALSE(fesa::hht_internal::ReusableTrialContext(positive, positive));
EXPECT_EQ(mkl_set_num_threads_local(0), 2);
mkl_set_dynamic(1);
const auto dynamic = fesa::hht_internal::ReadTrialEvaluationContext();
EXPECT_FALSE(fesa::hht_internal::ReusableTrialContext(dynamic, dynamic));
mkl_set_dynamic(0);
const auto fixture = MakeFixture(false);
const auto shells =
fesa::hht_internal::BuildRuntimeShells(*fixture.domain, fixture.dofs);
ASSERT_TRUE(shells.HasValue());
const fesa::Vector zero(fixture.dofs.FullDofCount());
const auto position =
Assemble(fixture, shells.Value(), zero, zero, zero, zero, false);
ASSERT_TRUE(position.HasValue());
EXPECT_FALSE(position.Value().trial_constraints.has_value());
mkl_set_dynamic(1);
const auto dynamic_trial =
Assemble(fixture, shells.Value(), zero, zero, zero, zero, true);
mkl_set_dynamic(0);
ASSERT_TRUE(dynamic_trial.HasValue());
ASSERT_TRUE(dynamic_trial.Value().trial_constraints.has_value());
EXPECT_FALSE(
dynamic_trial.Value().trial_constraints->shells[0U].directional_internal);
static_cast<void>(mkl_set_num_threads_local(2));
const auto positive_trial =
Assemble(fixture, shells.Value(), zero, zero, zero, zero, true);
EXPECT_EQ(mkl_set_num_threads_local(0), 2);
ASSERT_TRUE(positive_trial.HasValue());
ASSERT_TRUE(positive_trial.Value().trial_constraints.has_value());
EXPECT_FALSE(positive_trial.Value()
.trial_constraints->shells[0U]
.directional_internal);
fesa::DynamicAcceptedFrameState frame{};
frame.is_initial = true;
for (auto* vector :
{&frame.generalized_coordinates, &frame.velocity, &frame.acceleration,
&frame.algorithmic_acceleration, &frame.external_force})
*vector = zero;
fesa::SerialParallelFor parallel;
const auto parameters = fesa::MakeHhtParameters(-0.05);
ASSERT_TRUE(parameters.HasValue());
const auto initial = fesa::hht_internal::EvaluateAssembly(
*fixture.domain, fixture.dofs, shells.Value(), frame, zero, 0.1,
parameters.Value(), parallel, true);
ASSERT_TRUE(initial.HasValue());
EXPECT_FALSE(initial.Value().trial_constraints.has_value());
auto invalid = zero;
invalid[0U] = std::numeric_limits<double>::quiet_NaN();
const auto failure =
Assemble(fixture, shells.Value(), invalid, zero, zero, zero, true);
EXPECT_FALSE(failure.HasValue());
const auto current =
Assemble(fixture, shells.Value(), zero, zero, zero, zero, true);
const auto next =
Assemble(fixture, shells.Value(), zero, zero, zero, zero, true);
ASSERT_TRUE(current.HasValue());
ASSERT_TRUE(next.HasValue());
ASSERT_EQ(current.Value().trial_constraints->shells.size(),
shells.Value().size());
ASSERT_EQ(next.Value().trial_constraints->shells.size(),
shells.Value().size());
EXPECT_NE(&current.Value().trial_constraints->shells[0U],
&next.Value().trial_constraints->shells[0U]);
}
TEST(HhtReducedOperators, NonfiniteMatchingTrialKeysPreserveOriginalFailure) {
RestoreTrialEnvironment restore;
const auto fixture = MakeFixture(false);
const auto shells =
fesa::hht_internal::BuildRuntimeShells(*fixture.domain, fixture.dofs);
ASSERT_TRUE(shells.HasValue());
const fesa::Vector zero(fixture.dofs.FullDofCount());
const auto assembled =
Assemble(fixture, shells.Value(), zero, zero, zero, zero, true);
ASSERT_TRUE(assembled.HasValue());
ASSERT_TRUE(assembled.Value().trial_constraints.has_value());
ASSERT_TRUE(
assembled.Value().trial_constraints->shells[0U].directional_internal);
const auto chart = fesa::hht_reduced_internal::BuildFixedChart(
fixture.dofs, assembled.Value().rotations);
ASSERT_TRUE(chart.HasValue());
const auto evaluate = [&](const fesa::hht_internal::DynamicAssembly& value) {
return fesa::hht_reduced_internal::EvaluateConstraintOperators(
*fixture.domain, fixture.dofs, shells.Value(), value, chart.Value(),
zero, zero);
};
const std::array<double, 3> invalid{std::numeric_limits<double>::quiet_NaN(),
std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity()};
for (std::size_t channel = 0U; channel < 2U; ++channel)
for (std::size_t i = 0U; i < invalid.size(); ++i) {
SCOPED_TRACE(channel);
SCOPED_TRACE(i);
auto candidate = assembled.Value();
auto& saved = candidate.trial_constraints->shells[0U];
if (channel == 0U) {
candidate.frame.generalized_coordinates[0U] = invalid[i];
saved.q[0U] = invalid[i];
} else {
candidate.frame.velocity[0U] = invalid[i];
saved.v[0U] = invalid[i];
}
auto fresh = candidate;
fresh.trial_constraints.reset();
const auto original = evaluate(fresh);
const auto actual = evaluate(candidate);
ASSERT_FALSE(original.HasValue());
ASSERT_FALSE(actual.HasValue());
EXPECT_EQ(actual.GetStatus().Category(), original.GetStatus().Category());
const auto& a = actual.GetStatus().Diagnostics();
const auto& b = original.GetStatus().Diagnostics();
ASSERT_EQ(a.size(), b.size());
for (std::size_t j = 0U; j < a.size(); ++j) {
EXPECT_EQ(a[j].severity, b[j].severity);
EXPECT_EQ(a[j].code, b[j].code);
EXPECT_EQ(a[j].location.file, b[j].location.file);
EXPECT_EQ(a[j].location.line, b[j].location.line);
EXPECT_EQ(a[j].keyword, b[j].keyword);
EXPECT_EQ(a[j].entity_identity, b[j].entity_identity);
EXPECT_EQ(a[j].message, b[j].message);
}
}
}
TEST(HhtReducedOperators, NonfiniteMatchingTimeKeyMissPreservesFreshSuccess) {
RestoreTrialEnvironment restore;
const auto fixture = MakeFixture(false);
const auto shells =
fesa::hht_internal::BuildRuntimeShells(*fixture.domain, fixture.dofs);
ASSERT_TRUE(shells.HasValue());
const fesa::Vector zero(fixture.dofs.FullDofCount());
const auto assembled =
Assemble(fixture, shells.Value(), zero, zero, zero, zero, true);
ASSERT_TRUE(assembled.HasValue());
ASSERT_TRUE(assembled.Value().trial_constraints.has_value());
const auto chart = fesa::hht_reduced_internal::BuildFixedChart(
fixture.dofs, assembled.Value().rotations);
ASSERT_TRUE(chart.HasValue());
const auto evaluate = [&](const fesa::hht_internal::DynamicAssembly& value) {
return fesa::hht_reduced_internal::EvaluateConstraintOperators(
*fixture.domain, fixture.dofs, shells.Value(), value, chart.Value(),
zero, zero);
};
const auto parameters = fesa::MakeHhtParameters(-0.05);
ASSERT_TRUE(parameters.HasValue());
fesa::SerialParallelFor parallel;
for (const double bad_time : {std::numeric_limits<double>::quiet_NaN(),
std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity()}) {
auto candidate = assembled.Value();
candidate.frame.time = bad_time;
candidate.trial_constraints->time = bad_time;
candidate.trial_constraints->shells[0U].drilling_constraint[0U] = 17.0;
auto fresh = candidate;
fresh.trial_constraints.reset();
const auto reference = evaluate(fresh);
const auto actual = evaluate(candidate);
ASSERT_TRUE(reference.HasValue());
ASSERT_TRUE(actual.HasValue());
ExpectSameOperators(actual.Value(), reference.Value());
const auto producer = fesa::hht_internal::EvaluateAssembly(
*fixture.domain, fixture.dofs, shells.Value(), candidate.frame, zero,
0.1, parameters.Value(), parallel, true);
ASSERT_TRUE(producer.HasValue());
EXPECT_FALSE(producer.Value().trial_constraints.has_value());
const auto producer_result = evaluate(producer.Value());
ASSERT_TRUE(producer_result.HasValue());
ExpectSameOperators(producer_result.Value(), reference.Value());
}
}
TEST(HhtReducedOperators, ScatterShellDerivativesByConstraintRow) {
const auto fixture = MakeFixture(false);
const auto shells = fesa::hht_internal::BuildRuntimeShells(*fixture.domain,
@@ -1683,58 +1141,4 @@ TEST(HhtReducedChannelNewton, RestrictedSingularityIsNotFullInvertibility) {
EXPECT_FALSE(solver.FactorizeFresh(restricted.Value().tangent).IsOk());
}
TEST(HhtReducedOperators, NullMetricKeepsFiniteLeftAssociatedProducts) {
const auto fixture = MakeFixture(false);
const auto shells = fesa::hht_internal::BuildRuntimeShells(*fixture.domain,
fixture.dofs);
ASSERT_TRUE(shells.HasValue());
const fesa::Vector zero(fixture.dofs.FullDofCount());
const auto assembled = Assemble(fixture, shells.Value(), zero, zero, zero,
zero, false);
ASSERT_TRUE(assembled.HasValue());
auto assembly = assembled.Value();
const double maximum = (std::numeric_limits<double>::max)();
const double epsilon = std::ldexp(1.0, -26);
const double t = std::sqrt(1.0 - epsilon * epsilon);
const std::array<double, 3U> n{epsilon, 0.6 * t, 0.8 * t};
const std::array<double, 9U> block{0.0, maximum, maximum,
maximum, 0.0, 0.0,
maximum, 0.0, 0.0};
double original_metric = 0.0;
for (std::size_t i = 0U; i < 3U; ++i)
for (std::size_t j = 0U; j < 3U; ++j)
original_metric += (n[i] * block[3U * i + j]) * n[j];
ASSERT_TRUE(std::isfinite(original_metric));
ASSERT_GT(original_metric, 0.0);
ASSERT_FALSE(std::isfinite(block[1U] * n[1U] + block[2U] * n[2U]));
std::vector<fesa::CooContribution> entries;
for (std::size_t node = 0U; node < assembly.rotations.size(); ++node) {
assembly.rotations[node].drill_null = fesa::Vector3(n);
for (std::size_t i = 0U; i < 3U; ++i)
for (std::size_t j = 0U; j < 3U; ++j)
entries.push_back({6U * node + 3U + i, 6U * node + 3U + j,
block[3U * i + j], node, 3U * i + j});
}
const auto metric = fesa::hht_internal::MakeSparse(
fixture.dofs.FullDofCount(), fixture.dofs.FullDofCount(),
std::move(entries));
ASSERT_TRUE(metric.HasValue());
assembly.drilling_metric = metric.Value();
const auto chart = fesa::hht_reduced_internal::BuildFixedChart(
fixture.dofs, assembly.rotations);
ASSERT_TRUE(chart.HasValue());
fesa::hht_reduced_internal::ConstraintOperators operators;
operators.rows.resize(chart.Value().null_nodes.size());
for (std::size_t row = 0U; row < operators.rows.size(); ++row)
for (std::size_t component = 0U; component < 3U; ++component)
operators.rows[row].gradient.push_back(
{6U * chart.Value().null_nodes[row] + 3U + component,
original_metric * n[component]});
fesa::MklPardisoTangentSolver solver;
const auto checked = fesa::hht_reduced_internal::CheckGraphRegularity(
assembly, chart.Value(), operators, solver);
ASSERT_TRUE(checked.HasValue());
EXPECT_GE(checked.Value(), std::ldexp(1.0, -32));
}
} // namespace
@@ -573,8 +573,7 @@ TEST(HhtShellPhysics, ExternalDrillingHessianBalancesGradientAcceleration) {
const fesa::Vector3 velocity{1.0, 0.0, 1.0};
const fesa::Vector3 acceleration{0.0, -1.0 / 3.0, 0.0};
const fesa::Vector3 force{1.0, 0.0, 0.0};
const auto external = fixture.shells[0U].internal
.EvaluateExternalDrillingConstraint(
const auto external = fixture.shells[0U].internal->EvaluateExternalDrillingConstraint(
0U, phi, velocity, force, {}, {});
ASSERT_TRUE(external.HasValue());
double gradient_acceleration = 0.0;
@@ -586,14 +585,11 @@ TEST(HhtShellPhysics, ExternalDrillingHessianBalancesGradientAcceleration) {
EXPECT_NEAR(external.Value().directional_second + gradient_acceleration,
0.0, 1.0e-14);
constexpr double step = 1.0e-4;
const auto plus = fixture.shells[0U].internal
.EvaluateExternalDrillingConstraint(
const auto plus = fixture.shells[0U].internal->EvaluateExternalDrillingConstraint(
0U, {step, 0.0, step}, {}, force, {}, {});
const auto middle = fixture.shells[0U].internal
.EvaluateExternalDrillingConstraint(
const auto middle = fixture.shells[0U].internal->EvaluateExternalDrillingConstraint(
0U, phi, {}, force, {}, {});
const auto minus = fixture.shells[0U].internal
.EvaluateExternalDrillingConstraint(
const auto minus = fixture.shells[0U].internal->EvaluateExternalDrillingConstraint(
0U, {-step, 0.0, -step}, {}, force, {}, {});
ASSERT_TRUE(plus.HasValue());
ASSERT_TRUE(middle.HasValue());
@@ -21,6 +21,8 @@
#include "fesa/solvers/linear/mkl_pardiso_tangent_solver.h"
#include "solid_hht_test_fixture.h"
#include "fesa/analysis/analysis_model.h"
namespace {
fesa::Result<fesa::SolidDynamicExecutionResult> RunDynamics(
fesa::ModelDefinition d) {
@@ -1,4 +1,7 @@
#include "fesa/analysis/solid_static_procedure.h"
#include "analysis/solid_static_core.h"
#include "analysis/equilibrium_algorithm.h"
#include "fesa/core/source_identity.h"
#include <gtest/gtest.h>
@@ -7,6 +10,7 @@
#include <fstream>
#include <iostream>
#include <limits>
#include <memory>
#include <sstream>
#include <string>
@@ -824,3 +828,116 @@ TEST(SolidStaticProcedure,
<< " energy=" << frame.physical_strain_energy << '\n';
}
}
namespace {
fesa::Status StaticPolicyFailure() {
return fesa::Status::Failure(fesa::FailureCategory::kSolver,
{{fesa::Severity::kError, "injected-static-algorithm", {}, "STATIC",
"original-owner", "Original algorithm failure."}});
}
class UnusedStaticSolvers final : public fesa::LinearSolver,
public fesa::TangentSolver {
public:
mutable int calls = 0;
fesa::Status Factorize(const fesa::SparseMatrix&) override {
++calls; return StaticPolicyFailure();
}
fesa::Status Solve(const fesa::Vector&, fesa::Vector&) const override {
++calls; return StaticPolicyFailure();
}
fesa::Status FactorizeFresh(const fesa::SparseMatrix&) override {
++calls; return StaticPolicyFailure();
}
fesa::Status SolveCorrection(const fesa::Vector&, fesa::Vector&) const override {
++calls; return StaticPolicyFailure();
}
};
class RefusingStaticAlgorithm final : public fesa::EquilibriumAlgorithm {
public:
RefusingStaticAlgorithm(const fesa::SolidStaticState& state, bool& destroyed)
: state_(state), destroyed_(destroyed) {}
~RefusingStaticAlgorithm() override { destroyed_ = true; }
int calls = 0;
fesa::SolidStaticFrame baseline;
fesa::Status SolveCurrentStep(std::uint32_t first, std::uint32_t last,
const Evaluation&) override {
++calls;
EXPECT_EQ(first, 1U); EXPECT_EQ(last, 100U);
EXPECT_TRUE(state_.AcceptedFrames().empty());
EXPECT_TRUE(state_.IterationRecords().empty());
baseline = state_.CommittedState();
return StaticPolicyFailure();
}
private:
const fesa::SolidStaticState& state_;
bool& destroyed_;
};
} // namespace
TEST(SolidStaticProcedure, InjectedAlgorithmFailurePreservesInitializedBaseline) {
auto domain = Map(Deck(Supports(), Tension(1.0), true));
ASSERT_TRUE(domain.HasValue()) << Message(domain.GetStatus());
fesa::SerialParallelFor serial;
UnusedStaticSolvers solvers;
fesa::SolidStaticState state;
bool destroyed = false;
auto owned = std::make_unique<RefusingStaticAlgorithm>(state, destroyed);
auto* selected = owned.get();
std::unique_ptr<fesa::EquilibriumAlgorithm> algorithm = std::move(owned);
{
fesa::SolidStaticCore core(serial, solvers, solvers, *algorithm);
const auto status = core.ExecuteInto(domain.Value(), state);
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-static-algorithm");
EXPECT_EQ(diagnostic.keyword, "STATIC");
EXPECT_EQ(diagnostic.entity_identity, "original-owner");
EXPECT_EQ(diagnostic.message, "Original algorithm failure.");
}
EXPECT_EQ(selected->calls, 1); EXPECT_EQ(solvers.calls, 0);
EXPECT_TRUE(state.AcceptedFrames().empty());
EXPECT_TRUE(state.IterationRecords().empty());
// Core initialization legitimately installs the zero-factor baseline before
// iteration. Compare to that exact baseline at algorithm entry, not to the
// initially uninitialized observer.
const auto& expected = selected->baseline;
const auto& actual = state.CommittedState();
EXPECT_EQ(actual.frame_index, expected.frame_index);
EXPECT_EQ(actual.increment_index, expected.increment_index);
EXPECT_DOUBLE_EQ(actual.load_factor, expected.load_factor);
EXPECT_EQ(actual.iteration_count, expected.iteration_count);
EXPECT_EQ(actual.is_factor_one, expected.is_factor_one);
EXPECT_EQ(actual.nodal_internal_order, expected.nodal_internal_order);
ASSERT_EQ(actual.nodal_source_ids.size(), expected.nodal_source_ids.size());
for (std::size_t i = 0; i < actual.nodal_source_ids.size(); ++i)
EXPECT_TRUE(fesa::SourceIdentityEquals(actual.nodal_source_ids[i], expected.nodal_source_ids[i]));
const auto same_vector = [](const fesa::Vector& a, const fesa::Vector& b) {
ASSERT_EQ(a.Size(), b.Size());
for (std::size_t i = 0; i < a.Size(); ++i) EXPECT_DOUBLE_EQ(a[i], b[i]);
};
same_vector(actual.displacement, expected.displacement);
same_vector(actual.internal_force, expected.internal_force);
same_vector(actual.external_force, expected.external_force);
same_vector(actual.residual, expected.residual); same_vector(actual.reaction, expected.reaction);
EXPECT_DOUBLE_EQ(actual.physical_strain_energy, expected.physical_strain_energy);
EXPECT_EQ(actual.global_equilibrium, expected.global_equilibrium);
EXPECT_EQ(actual.balance.internal_force_sum, expected.balance.internal_force_sum);
EXPECT_EQ(actual.balance.reference_internal_moment, expected.balance.reference_internal_moment);
EXPECT_EQ(actual.balance.current_internal_moment, expected.balance.current_internal_moment);
EXPECT_EQ(actual.balance.free_residual_moment, expected.balance.free_residual_moment);
EXPECT_EQ(actual.balance.linearization_moment, expected.balance.linearization_moment);
EXPECT_EQ(actual.balance.internal_force_scale, expected.balance.internal_force_scale);
EXPECT_EQ(actual.balance.reference_moment_scale, expected.balance.reference_moment_scale);
EXPECT_EQ(actual.balance.current_moment_scale, expected.balance.current_moment_scale);
ASSERT_EQ(actual.solid_results.size(), expected.solid_results.size());
for (std::size_t i = 0; i < actual.solid_results.size(); ++i) {
const auto& a = actual.solid_results[i]; const auto& b = expected.solid_results[i];
EXPECT_EQ(a.element, b.element); EXPECT_EQ(a.integration_point, b.integration_point);
EXPECT_EQ(a.natural_coordinates, b.natural_coordinates);
EXPECT_EQ(a.reference_coordinates, b.reference_coordinates);
EXPECT_EQ(a.cauchy_stress, b.cauchy_stress); EXPECT_EQ(a.strain, b.strain);
}
EXPECT_FALSE(destroyed); algorithm.reset(); EXPECT_TRUE(destroyed);
}
@@ -11,6 +11,8 @@
#include <utility>
#include <vector>
#include "fesa/assembly/thermal_assembler.h"
namespace {
fesa::ModelDefinition Definition(bool constrained = true, double period = .9,
double increment = .3,
@@ -12,6 +12,8 @@
#include "analysis/thermal_transient_bounds.h"
#include "fesa/solvers/linear/mkl_pardiso_solver.h"
#include "fesa/assembly/thermal_assembler.h"
namespace {
namespace ti = fesa::thermal_transient_internal;
std::string Messages(const fesa::Status& status) {