Integrate Refactor solver sources and dual MSVC CMake builds
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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, ¶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);
|
||||
}
|
||||
|
||||
@@ -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(¤t.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) {
|
||||
|
||||
Reference in New Issue
Block a user