Merge reviewed MKL shell products and complete-trial reuse

This commit is contained in:
KOKO\Mimi
2026-10-14 21:49:14 +09:00
14 changed files with 1936 additions and 219 deletions
@@ -5,6 +5,8 @@
#include <cmath>
#include <cfenv>
#include <cstddef>
#include <cstring>
#include <functional>
#include <iostream>
#include <memory>
#include <limits>
@@ -19,6 +21,7 @@
#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"
@@ -26,6 +29,71 @@
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;
@@ -394,6 +462,480 @@ 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,
@@ -1141,4 +1683,58 @@ 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
@@ -11,6 +11,131 @@
namespace fesa::dense_blas_internal {
namespace {
TEST(DenseBlasInternal, PolicyQueryPreservesDefaultPositiveAndNestedTls) {
const int previous = mkl_set_num_threads_local(0);
struct Restore {
int previous;
~Restore() { static_cast<void>(mkl_set_num_threads_local(previous)); }
} restore{previous};
const int global = mkl_get_max_threads();
const int domain = mkl_domain_get_max_threads(MKL_DOMAIN_BLAS);
const int dynamic = mkl_get_dynamic();
for (int repeat = 0; repeat < 2; ++repeat) {
const auto policy = ReadScopedLocalDensePolicy();
EXPECT_EQ(policy.blas_max_threads, 1);
EXPECT_EQ(policy.dynamic, dynamic);
EXPECT_EQ(mkl_set_num_threads_local(0), 0);
EXPECT_EQ(mkl_get_max_threads(), global);
EXPECT_EQ(mkl_domain_get_max_threads(MKL_DOMAIN_BLAS), domain);
EXPECT_EQ(mkl_get_dynamic(), dynamic);
}
static_cast<void>(mkl_set_num_threads_local(2));
EXPECT_EQ(ReadScopedLocalDensePolicy().blas_max_threads, 2);
EXPECT_EQ(mkl_set_num_threads_local(2), 2);
static_cast<void>(mkl_set_num_threads_local(0));
try {
const ScopedLocalDenseThreads outer{};
EXPECT_EQ(ReadScopedLocalDensePolicy().blas_max_threads, 1);
EXPECT_EQ(mkl_set_num_threads_local(1), 1);
throw std::runtime_error{"policy-query-unwind"};
} catch (const std::runtime_error&) {
}
EXPECT_EQ(mkl_set_num_threads_local(0), 0);
EXPECT_EQ(mkl_get_dynamic(), dynamic);
}
TEST(DenseBlasInternal,
OuterProductUsesRectangularStorageAndPreservesRejection) {
const std::array<double, 2U> left{2.0, -3.0};
const std::array<double, 3U> right{4.0, -5.0, 0.25};
std::array<double, 6U> product{};
ASSERT_TRUE(OuterProduct(left.data(), left.size(), right.data(), right.size(),
product.data()));
const std::array<double, 6U> expected{8.0, -10.0, 0.5, -12.0, 15.0, -0.75};
EXPECT_EQ(product, expected);
const auto accepted = product;
const std::array<double, 2U> invalid{
1.0, std::numeric_limits<double>::quiet_NaN()};
EXPECT_FALSE(OuterProduct(invalid.data(), invalid.size(), right.data(),
right.size(), product.data()));
EXPECT_EQ(product, accepted);
EXPECT_TRUE(OuterProduct(nullptr, 0U, nullptr, 3U, nullptr));
EXPECT_TRUE(OuterProduct(nullptr, 2U, nullptr, 0U, nullptr));
EXPECT_THROW(OuterProduct(nullptr, 2U, right.data(), 3U, product.data()),
std::invalid_argument);
const auto too_large =
static_cast<std::size_t>((std::numeric_limits<MKL_INT>::max)()) + 1U;
EXPECT_THROW(OuterProduct(nullptr, too_large, nullptr, 1U, nullptr),
std::length_error);
const auto too_many_elements =
std::numeric_limits<std::size_t>::max() / sizeof(double) + 1U;
EXPECT_THROW(OuterProduct(nullptr, too_many_elements, nullptr, 1U, nullptr),
std::length_error);
}
TEST(DenseBlasInternal, LocalDenseScopeRestoresTlsAfterProductsAndUnwind) {
const int previous = mkl_set_num_threads_local(0);
struct Restore {
int previous;
~Restore() { static_cast<void>(mkl_set_num_threads_local(previous)); }
} restore{previous};
const int default_threads = mkl_get_max_threads();
const std::array<double, 2U> left{0.0, 2.0};
const std::array<double, 2U> right{3.0, 4.0};
std::array<double, 4U> product{};
product.fill(std::numeric_limits<double>::quiet_NaN());
{
const ScopedLocalDenseThreads scope{};
EXPECT_EQ(mkl_get_max_threads(), 1);
EXPECT_TRUE(
OuterProduct(left.data(), 2U, right.data(), 2U, product.data()));
EXPECT_DOUBLE_EQ(product[0U], 0.0);
EXPECT_DOUBLE_EQ(product[3U], 8.0);
}
EXPECT_EQ(mkl_get_max_threads(), default_threads);
EXPECT_EQ(mkl_set_num_threads_local(0), 0);
try {
const ScopedLocalDenseThreads scope{};
OuterProduct(nullptr, 2U, right.data(), 2U, product.data());
} catch (const std::invalid_argument&) {
}
EXPECT_EQ(mkl_set_num_threads_local(0), 0);
static_cast<void>(mkl_set_num_threads_local(3));
{
const ScopedLocalDenseThreads scope{};
EXPECT_EQ(mkl_get_max_threads(), 3);
}
EXPECT_EQ(mkl_get_max_threads(), 3);
}
TEST(DenseBlasInternal, OuterProductLeavesFiniteInputOverflowForOwnerGate) {
const std::array<double, 1U> left{std::numeric_limits<double>::max()};
const std::array<double, 1U> right{2.0};
std::array<double, 1U> product{};
EXPECT_TRUE(OuterProduct(left.data(), 1U, right.data(), 1U, product.data()));
EXPECT_TRUE(std::isinf(product[0U]));
}
TEST(DenseBlasInternal, TransposeRightProductUsesInnerAndRectangularStrides) {
const std::array<double, 6U> left{1, 2, 3, 4, 5, 6};
const std::array<double, 12U> right{7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18};
std::array<double, 8U> product;
ASSERT_TRUE(MultiplyTransposeRight(left.data(), 2U, right.data(), 4U, 3U,
product.data()));
const std::array<double, 8U> expected{50, 68, 86, 104, 122, 167, 212, 257};
EXPECT_EQ(product, expected);
EXPECT_TRUE(
MultiplyTransposeRight(nullptr, 2U, nullptr, 4U, 0U, product.data()));
for (const auto value : product) EXPECT_DOUBLE_EQ(value, 0.0);
auto nonfinite = right;
nonfinite[8U] = std::numeric_limits<double>::infinity();
product.fill(31.0);
EXPECT_FALSE(MultiplyTransposeRight(left.data(), 2U, nonfinite.data(), 4U, 3U,
product.data()));
for (const auto value : product) EXPECT_DOUBLE_EQ(value, 31.0);
}
TEST(DenseBlasInternal, CDup003ConvertsZeroNormalAndOverflowLengths) {
const auto zero = ToMklSize(0U);
ASSERT_TRUE(zero.HasValue());
@@ -115,6 +240,30 @@ TEST(DenseBlasInternal, CholeskyAndInverseMatchKnownFactorsAndKeepFailedOutput)
std::invalid_argument);
}
TEST(DenseBlasInternal,
CholeskyFailureReasonPreservesCandidatesAndResetsOnReuse) {
Matrix matrix{2U, 2U};
matrix(0U, 0U) = 4.0;
matrix(1U, 1U) = 9.0;
Matrix lower{0U, 0U};
Matrix inverse{0U, 0U};
auto reason = CholeskyFailure::kInverse;
ASSERT_TRUE(CholeskyLowerAndInverse(matrix, lower, inverse, &reason));
EXPECT_EQ(reason, CholeskyFailure::kNone);
matrix(1U, 1U) = -1.0;
EXPECT_FALSE(CholeskyLowerAndInverse(matrix, lower, inverse, &reason));
EXPECT_EQ(reason, CholeskyFailure::kPivot);
EXPECT_DOUBLE_EQ(lower(1U, 1U), 3.0);
EXPECT_DOUBLE_EQ(inverse(1U, 1U), 1.0 / 3.0);
matrix(1U, 1U) = 9.0;
EXPECT_TRUE(CholeskyLowerAndInverse(matrix, lower, inverse, &reason));
EXPECT_EQ(reason, CholeskyFailure::kNone);
reason = CholeskyFailure::kInverse;
EXPECT_THROW(CholeskyLowerAndInverse(Matrix{2U, 1U}, lower, inverse, &reason),
std::invalid_argument);
EXPECT_EQ(reason, CholeskyFailure::kNone);
}
TEST(DenseBlasInternal, SingleEquationScopeRestoresTlsAndRespectsOverrides) {
const int previous = mkl_set_num_threads_local(0);
struct Restore {