perf(hex8): defer optional paired tangent until a valid linear trial
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
#define FESA_ELEMENTS_HEX8_ELEMENT_H_
|
#define FESA_ELEMENTS_HEX8_ELEMENT_H_
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include "fesa/elements/element.h"
|
#include "fesa/elements/element.h"
|
||||||
@@ -9,6 +10,9 @@
|
|||||||
#include "fesa/results/solid_result_records.h"
|
#include "fesa/results/solid_result_records.h"
|
||||||
|
|
||||||
namespace fesa {
|
namespace fesa {
|
||||||
|
namespace hex8_internal {
|
||||||
|
struct LinearTangentCache;
|
||||||
|
}
|
||||||
|
|
||||||
/// @brief Owns all local quantities from one validated linear or TL trial.
|
/// @brief Owns all local quantities from one validated linear or TL trial.
|
||||||
struct Hex8Response {
|
struct Hex8Response {
|
||||||
@@ -90,7 +94,8 @@ class Hex8Element final : public Element {
|
|||||||
bool nonlinear) const;
|
bool nonlinear) const;
|
||||||
/// @brief Uses one paired gradient for energy, force and analytic Hessian.
|
/// @brief Uses one paired gradient for energy, force and analytic Hessian.
|
||||||
Result<Hex8HighLowResponse> EvaluateHighLow(
|
Result<Hex8HighLowResponse> EvaluateHighLow(
|
||||||
const Vector& high, const Vector& low, bool nonlinear) const;
|
const Vector& high, const Vector& low, bool nonlinear,
|
||||||
|
bool prepare_linear_cache = true) const;
|
||||||
/// @brief Attaches stable source context to a model failure.
|
/// @brief Attaches stable source context to a model failure.
|
||||||
Status Failure(const char* message) const;
|
Status Failure(const char* message) const;
|
||||||
|
|
||||||
@@ -103,11 +108,9 @@ class Hex8Element final : public Element {
|
|||||||
SourceLocation location_;
|
SourceLocation location_;
|
||||||
std::array<ReferencePoint, 8> points_{};
|
std::array<ReferencePoint, 8> points_{};
|
||||||
Matrix stiffness_{24U, 24U};
|
Matrix stiffness_{24U, 24U};
|
||||||
// Optional immutable Hessian from the same paired quadrature graph. Failure
|
// Optional immutable paired Hessian, prepared once on the first valid paired
|
||||||
// to build it does not reject models supported by the original F64 paths.
|
// linear trial. Copies share only their identical owned reference definition.
|
||||||
bool paired_linear_tangent_ready_{false};
|
std::shared_ptr<hex8_internal::LinearTangentCache> paired_linear_tangent_;
|
||||||
Matrix paired_linear_tangent_high_{24U, 24U};
|
|
||||||
Matrix paired_linear_tangent_low_{24U, 24U};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace fesa
|
} // namespace fesa
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "../math/dense_blas_internal.h"
|
#include "../math/dense_blas_internal.h"
|
||||||
#include "hex8_geometry.h"
|
#include "hex8_geometry.h"
|
||||||
|
#include "hex8_linear_tangent_cache.h"
|
||||||
|
|
||||||
namespace fesa {
|
namespace fesa {
|
||||||
namespace {
|
namespace {
|
||||||
@@ -116,7 +117,9 @@ Hex8Element::Hex8Element(EntityIndex index, ElementDofLayout layout,
|
|||||||
kappa_{kappa},
|
kappa_{kappa},
|
||||||
mu_{mu},
|
mu_{mu},
|
||||||
density_{density},
|
density_{density},
|
||||||
location_{std::move(location)} {}
|
location_{std::move(location)},
|
||||||
|
paired_linear_tangent_{
|
||||||
|
std::make_shared<hex8_internal::LinearTangentCache>()} {}
|
||||||
|
|
||||||
Status Hex8Element::Failure(const char* message) const {
|
Status Hex8Element::Failure(const char* message) const {
|
||||||
return Status::Failure(FailureCategory::kModel,
|
return Status::Failure(FailureCategory::kModel,
|
||||||
@@ -273,13 +276,6 @@ Result<Hex8Element> Hex8Element::Create(EntityIndex index,
|
|||||||
if (backend_inputs_failed)
|
if (backend_inputs_failed)
|
||||||
return Result<Hex8Element>::Failure(candidate.Failure(
|
return Result<Hex8Element>::Failure(candidate.Failure(
|
||||||
"Hex8 dense MKL operands failed finite validation."));
|
"Hex8 dense MKL operands failed finite validation."));
|
||||||
const Vector zero{24U};
|
|
||||||
const auto paired = candidate.EvaluateHighLow(zero, zero, false);
|
|
||||||
if (paired.HasValue()) {
|
|
||||||
candidate.paired_linear_tangent_high_ = paired.Value().high.tangent;
|
|
||||||
candidate.paired_linear_tangent_low_ = paired.Value().tangent_low;
|
|
||||||
candidate.paired_linear_tangent_ready_ = true;
|
|
||||||
}
|
|
||||||
return Result<Hex8Element>::Success(std::move(candidate));
|
return Result<Hex8Element>::Success(std::move(candidate));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <mutex>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "../math/high_low_arithmetic.h"
|
#include "../math/high_low_arithmetic.h"
|
||||||
#include "hex8_geometry.h"
|
#include "hex8_geometry.h"
|
||||||
|
#include "hex8_linear_tangent_cache.h"
|
||||||
#include "hex8_pair_arithmetic.h"
|
#include "hex8_pair_arithmetic.h"
|
||||||
|
|
||||||
namespace fesa {
|
namespace fesa {
|
||||||
@@ -53,7 +55,8 @@ std::array<double, 6> Pack(const Tensor& a) {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Result<Hex8HighLowResponse> Hex8Element::EvaluateHighLow(
|
Result<Hex8HighLowResponse> Hex8Element::EvaluateHighLow(
|
||||||
const Vector& high, const Vector& low, bool nonlinear) const {
|
const Vector& high, const Vector& low, bool nonlinear,
|
||||||
|
bool prepare_linear_cache) const {
|
||||||
using math_internal::IsNormalized;
|
using math_internal::IsNormalized;
|
||||||
if (!math_internal::ArithmeticEnvironmentSupported())
|
if (!math_internal::ArithmeticEnvironmentSupported())
|
||||||
return Result<Hex8HighLowResponse>::Failure(Failure(
|
return Result<Hex8HighLowResponse>::Failure(Failure(
|
||||||
@@ -85,6 +88,19 @@ Result<Hex8HighLowResponse> Hex8Element::EvaluateHighLow(
|
|||||||
message = "Hex8 full high+low Jacobian interval arithmetic is nonfinite.";
|
message = "Hex8 full high+low Jacobian interval arithmetic is nonfinite.";
|
||||||
return Result<Hex8HighLowResponse>::Failure(Failure(message));
|
return Result<Hex8HighLowResponse>::Failure(Failure(message));
|
||||||
}
|
}
|
||||||
|
if (!nonlinear && prepare_linear_cache) {
|
||||||
|
// Trial guards above run before optional reference preparation. The nested
|
||||||
|
// zero trial bypasses this once entry and uses the original paired graph.
|
||||||
|
std::call_once(paired_linear_tangent_->preparation, [this] {
|
||||||
|
const Vector zero{24U};
|
||||||
|
const auto paired = EvaluateHighLow(zero, zero, false, false);
|
||||||
|
if (paired.HasValue()) {
|
||||||
|
paired_linear_tangent_->high = paired.Value().high.tangent;
|
||||||
|
paired_linear_tangent_->low = paired.Value().tangent_low;
|
||||||
|
paired_linear_tangent_->ready = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Hex8HighLowResponse response;
|
Hex8HighLowResponse response;
|
||||||
std::array<HighLow, 24> force{};
|
std::array<HighLow, 24> force{};
|
||||||
@@ -196,7 +212,7 @@ Result<Hex8HighLowResponse> Hex8Element::EvaluateHighLow(
|
|||||||
for (std::size_t i = 0; i < 3; ++i) {
|
for (std::size_t i = 0; i < 3; ++i) {
|
||||||
force[3U * a + i] += Dot(p[i], gradients[a]) * HighLow{point.weight};
|
force[3U * a + i] += Dot(p[i], gradients[a]) * HighLow{point.weight};
|
||||||
}
|
}
|
||||||
if (!nonlinear && paired_linear_tangent_ready_) continue;
|
if (!nonlinear && paired_linear_tangent_->ready) continue;
|
||||||
std::array<Triple, 8> fg{};
|
std::array<Triple, 8> fg{};
|
||||||
for (std::size_t a = 0; a < 8; ++a)
|
for (std::size_t a = 0; a < 8; ++a)
|
||||||
for (std::size_t i = 0; i < 3; ++i)
|
for (std::size_t i = 0; i < 3; ++i)
|
||||||
@@ -243,7 +259,7 @@ Result<Hex8HighLowResponse> Hex8Element::EvaluateHighLow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (nonlinear || !paired_linear_tangent_ready_) {
|
if (nonlinear || !paired_linear_tangent_->ready) {
|
||||||
std::array<Triple, 8> bulk_fg{};
|
std::array<Triple, 8> bulk_fg{};
|
||||||
for (std::size_t a = 0; a < 8; ++a)
|
for (std::size_t a = 0; a < 8; ++a)
|
||||||
for (std::size_t i = 0; i < 3; ++i)
|
for (std::size_t i = 0; i < 3; ++i)
|
||||||
@@ -260,10 +276,10 @@ Result<Hex8HighLowResponse> Hex8Element::EvaluateHighLow(
|
|||||||
Failure("Hex8 high+low energy is unsupported or nonfinite."));
|
Failure("Hex8 high+low energy is unsupported or nonfinite."));
|
||||||
response.high.strain_energy = energy.high;
|
response.high.strain_energy = energy.high;
|
||||||
response.strain_energy_low = energy.low;
|
response.strain_energy_low = energy.low;
|
||||||
if (!nonlinear && paired_linear_tangent_ready_) {
|
if (!nonlinear && paired_linear_tangent_->ready) {
|
||||||
response.high.material_tangent = paired_linear_tangent_high_;
|
response.high.material_tangent = paired_linear_tangent_->high;
|
||||||
response.high.tangent = paired_linear_tangent_high_;
|
response.high.tangent = paired_linear_tangent_->high;
|
||||||
response.tangent_low = paired_linear_tangent_low_;
|
response.tangent_low = paired_linear_tangent_->low;
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < 24; ++i) {
|
for (std::size_t i = 0; i < 24; ++i) {
|
||||||
if (!IsNormalized(force[i]))
|
if (!IsNormalized(force[i]))
|
||||||
@@ -271,7 +287,7 @@ Result<Hex8HighLowResponse> Hex8Element::EvaluateHighLow(
|
|||||||
Failure("Hex8 high+low force is unsupported or nonfinite."));
|
Failure("Hex8 high+low force is unsupported or nonfinite."));
|
||||||
response.high.internal_force[i] = force[i].high;
|
response.high.internal_force[i] = force[i].high;
|
||||||
response.internal_force_low[i] = force[i].low;
|
response.internal_force_low[i] = force[i].low;
|
||||||
if (!nonlinear && paired_linear_tangent_ready_) continue;
|
if (!nonlinear && paired_linear_tangent_->ready) continue;
|
||||||
for (std::size_t j = 0; j < 24; ++j) {
|
for (std::size_t j = 0; j < 24; ++j) {
|
||||||
const auto tangent = material[i][j] + geometric[i][j];
|
const auto tangent = material[i][j] + geometric[i][j];
|
||||||
if (!IsNormalized(material[i][j]) || !IsNormalized(geometric[i][j]) ||
|
if (!IsNormalized(material[i][j]) || !IsNormalized(geometric[i][j]) ||
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef FESA_SRC_FESA_ELEMENTS_HEX8_LINEAR_TANGENT_CACHE_H_
|
||||||
|
#define FESA_SRC_FESA_ELEMENTS_HEX8_LINEAR_TANGENT_CACHE_H_
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include "fesa/math/matrix.h"
|
||||||
|
|
||||||
|
namespace fesa::hex8_internal {
|
||||||
|
/// @brief Shares one immutable paired Hessian among identical element copies.
|
||||||
|
/// @note Only call_once preparation writes. Linear readers synchronize through
|
||||||
|
/// the flag; nonlinear trials do not access this optional state. At most two
|
||||||
|
/// 24-by-24 matrices (9216 value bytes) are retained after successful preparation.
|
||||||
|
struct LinearTangentCache {
|
||||||
|
std::once_flag preparation;
|
||||||
|
bool ready{false};
|
||||||
|
Matrix high{0U, 0U};
|
||||||
|
Matrix low{0U, 0U};
|
||||||
|
};
|
||||||
|
} // namespace fesa::hex8_internal
|
||||||
|
|
||||||
|
#endif // FESA_SRC_FESA_ELEMENTS_HEX8_LINEAR_TANGENT_CACHE_H_
|
||||||
@@ -3,10 +3,13 @@
|
|||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <cfenv>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <future>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "fesa/materials/isotropic_linear_elastic_material.h"
|
#include "fesa/materials/isotropic_linear_elastic_material.h"
|
||||||
@@ -93,6 +96,112 @@ fesa::Hex8Element UnitBrick(double offset = 0, double nu = 0) {
|
|||||||
return fesa::Hex8Element::Create(0, layout, coordinates, material, 2).Value();
|
return fesa::Hex8Element::Create(0, layout, coordinates, material, 2).Value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ExpectSamePairResponse(const fesa::Hex8HighLowResponse& actual,
|
||||||
|
const fesa::Hex8HighLowResponse& expected) {
|
||||||
|
EXPECT_EQ(PairBits(actual.high.strain_energy),
|
||||||
|
PairBits(expected.high.strain_energy));
|
||||||
|
EXPECT_EQ(PairBits(actual.strain_energy_low),
|
||||||
|
PairBits(expected.strain_energy_low));
|
||||||
|
ASSERT_EQ(actual.high.tangent.Rows(), 24U);
|
||||||
|
ASSERT_EQ(actual.tangent_low.Rows(), 24U);
|
||||||
|
for (std::size_t i = 0; i < 24; ++i) {
|
||||||
|
EXPECT_EQ(PairBits(actual.high.internal_force[i]),
|
||||||
|
PairBits(expected.high.internal_force[i]));
|
||||||
|
EXPECT_EQ(PairBits(actual.internal_force_low[i]),
|
||||||
|
PairBits(expected.internal_force_low[i]));
|
||||||
|
for (std::size_t j = 0; j < 24; ++j) {
|
||||||
|
EXPECT_EQ(PairBits(actual.high.tangent(i, j)),
|
||||||
|
PairBits(expected.high.tangent(i, j)));
|
||||||
|
EXPECT_EQ(PairBits(actual.tangent_low(i, j)),
|
||||||
|
PairBits(expected.tangent_low(i, j)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (std::size_t point = 0; point < 8; ++point) {
|
||||||
|
EXPECT_EQ(actual.high.points[point].integration_point,
|
||||||
|
expected.high.points[point].integration_point);
|
||||||
|
for (std::size_t i = 0; i < 6; ++i) {
|
||||||
|
EXPECT_EQ(PairBits(actual.high.points[point].cauchy_stress[i]),
|
||||||
|
PairBits(expected.high.points[point].cauchy_stress[i]));
|
||||||
|
EXPECT_EQ(PairBits(actual.high.points[point].strain[i]),
|
||||||
|
PairBits(expected.high.points[point].strain[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Hex8HighLow, ConcurrentFirstLinearTrialsAcrossCopiesAndMoveKeepBits) {
|
||||||
|
const auto element = UnitBrick();
|
||||||
|
auto copied = element;
|
||||||
|
const auto moved = std::move(copied);
|
||||||
|
fesa::Vector high(24), low_a(24), low_b(24);
|
||||||
|
for (std::size_t i = 0; i < 24; ++i) high[i] = 1;
|
||||||
|
for (auto a : {1, 2, 5, 6}) {
|
||||||
|
low_a[3U * a] = std::ldexp(1.0, -60);
|
||||||
|
low_b[3U * a] = -std::ldexp(1.0, -59);
|
||||||
|
}
|
||||||
|
const auto oracle = UnitBrick();
|
||||||
|
const auto expected_a = oracle.EvaluateLinearHighLow(high, low_a);
|
||||||
|
const auto expected_b = oracle.EvaluateLinearHighLow(high, low_b);
|
||||||
|
ASSERT_TRUE(expected_a.HasValue());
|
||||||
|
ASSERT_TRUE(expected_b.HasValue());
|
||||||
|
std::promise<void> start;
|
||||||
|
const auto signal = start.get_future().share();
|
||||||
|
std::array<std::future<fesa::Result<fesa::Hex8HighLowResponse>>, 4> calls;
|
||||||
|
for (std::size_t i = 0; i < calls.size(); ++i)
|
||||||
|
calls[i] = std::async(std::launch::async, [&, i] {
|
||||||
|
signal.wait();
|
||||||
|
return (i % 2U == 0U ? element : moved)
|
||||||
|
.EvaluateLinearHighLow(high, i < 2U ? low_a : low_b);
|
||||||
|
});
|
||||||
|
start.set_value();
|
||||||
|
for (std::size_t i = 0; i < calls.size(); ++i) {
|
||||||
|
const auto response = calls[i].get();
|
||||||
|
ASSERT_TRUE(response.HasValue());
|
||||||
|
ExpectSamePairResponse(response.Value(),
|
||||||
|
i < 2U ? expected_a.Value() : expected_b.Value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Hex8HighLow, FirstLinearPreparationRecoversAfterTrialAndEnvironmentFailure) {
|
||||||
|
ASSERT_TRUE(fesa::math_internal::ArithmeticEnvironmentSupported());
|
||||||
|
const auto element = UnitBrick();
|
||||||
|
fesa::Vector high(24), low(24);
|
||||||
|
EXPECT_FALSE(element.EvaluateLinearHighLow(fesa::Vector(23), low).HasValue());
|
||||||
|
high[0] = std::numeric_limits<double>::infinity();
|
||||||
|
EXPECT_FALSE(element.EvaluateLinearHighLow(high, low).HasValue());
|
||||||
|
high[0] = 0;
|
||||||
|
struct RestoreEnvironment {
|
||||||
|
int rounding = std::fegetround();
|
||||||
|
#if defined(_M_X64) || defined(__SSE2__)
|
||||||
|
unsigned csr = _mm_getcsr();
|
||||||
|
#endif
|
||||||
|
~RestoreEnvironment() {
|
||||||
|
std::fesetround(rounding);
|
||||||
|
#if defined(_M_X64) || defined(__SSE2__)
|
||||||
|
_mm_setcsr(csr);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
} restore;
|
||||||
|
ASSERT_EQ(std::fesetround(FE_DOWNWARD), 0);
|
||||||
|
const auto bad_environment = element.EvaluateLinearHighLow(high, low);
|
||||||
|
ASSERT_FALSE(bad_environment.HasValue());
|
||||||
|
EXPECT_EQ(bad_environment.GetStatus().Diagnostics()[0].message,
|
||||||
|
"Hex8 high+low requires nearest rounding and gradual underflow.");
|
||||||
|
ASSERT_EQ(std::fesetround(FE_TONEAREST), 0);
|
||||||
|
#if defined(_M_X64) || defined(__SSE2__)
|
||||||
|
_mm_setcsr(restore.csr | 0x8040U);
|
||||||
|
EXPECT_FALSE(element.EvaluateLinearHighLow(high, low).HasValue());
|
||||||
|
_mm_setcsr(restore.csr);
|
||||||
|
#endif
|
||||||
|
const auto recovered = element.EvaluateLinearHighLow(high, low);
|
||||||
|
ASSERT_TRUE(recovered.HasValue());
|
||||||
|
high[3] = -2;
|
||||||
|
EXPECT_FALSE(element.EvaluateLinearHighLow(high, low).HasValue());
|
||||||
|
high[3] = 0;
|
||||||
|
const auto after_failure = element.EvaluateLinearHighLow(high, low);
|
||||||
|
ASSERT_TRUE(after_failure.HasValue());
|
||||||
|
ExpectSamePairResponse(after_failure.Value(), recovered.Value());
|
||||||
|
}
|
||||||
|
|
||||||
TEST(Hex8HighLow, LowOnlyAffineStrainSurvivesUniformHighTranslation) {
|
TEST(Hex8HighLow, LowOnlyAffineStrainSurvivesUniformHighTranslation) {
|
||||||
auto brick = UnitBrick();
|
auto brick = UnitBrick();
|
||||||
fesa::Vector high(24), low(24);
|
fesa::Vector high(24), low(24);
|
||||||
|
|||||||
Reference in New Issue
Block a user