perf(hex8): defer optional paired tangent until a valid linear trial

This commit is contained in:
KOKO\Mimi
2026-10-14 17:38:27 +09:00
parent 774bfbcbdd
commit 6a6c8c33c8
5 changed files with 167 additions and 22 deletions
+4 -8
View File
@@ -7,6 +7,7 @@
#include "../math/dense_blas_internal.h"
#include "hex8_geometry.h"
#include "hex8_linear_tangent_cache.h"
namespace fesa {
namespace {
@@ -116,7 +117,9 @@ Hex8Element::Hex8Element(EntityIndex index, ElementDofLayout layout,
kappa_{kappa},
mu_{mu},
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 {
return Status::Failure(FailureCategory::kModel,
@@ -273,13 +276,6 @@ Result<Hex8Element> Hex8Element::Create(EntityIndex index,
if (backend_inputs_failed)
return Result<Hex8Element>::Failure(candidate.Failure(
"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));
}
+24 -8
View File
@@ -2,10 +2,12 @@
#include <array>
#include <cmath>
#include <mutex>
#include <utility>
#include "../math/high_low_arithmetic.h"
#include "hex8_geometry.h"
#include "hex8_linear_tangent_cache.h"
#include "hex8_pair_arithmetic.h"
namespace fesa {
@@ -53,7 +55,8 @@ std::array<double, 6> Pack(const Tensor& a) {
} // namespace
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;
if (!math_internal::ArithmeticEnvironmentSupported())
return Result<Hex8HighLowResponse>::Failure(Failure(
@@ -85,6 +88,19 @@ Result<Hex8HighLowResponse> Hex8Element::EvaluateHighLow(
message = "Hex8 full high+low Jacobian interval arithmetic is nonfinite.";
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;
std::array<HighLow, 24> force{};
@@ -196,7 +212,7 @@ Result<Hex8HighLowResponse> Hex8Element::EvaluateHighLow(
for (std::size_t i = 0; i < 3; ++i) {
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{};
for (std::size_t a = 0; a < 8; ++a)
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{};
for (std::size_t a = 0; a < 8; ++a)
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."));
response.high.strain_energy = energy.high;
response.strain_energy_low = energy.low;
if (!nonlinear && paired_linear_tangent_ready_) {
response.high.material_tangent = paired_linear_tangent_high_;
response.high.tangent = paired_linear_tangent_high_;
response.tangent_low = paired_linear_tangent_low_;
if (!nonlinear && paired_linear_tangent_->ready) {
response.high.material_tangent = paired_linear_tangent_->high;
response.high.tangent = paired_linear_tangent_->high;
response.tangent_low = paired_linear_tangent_->low;
}
for (std::size_t i = 0; i < 24; ++i) {
if (!IsNormalized(force[i]))
@@ -271,7 +287,7 @@ Result<Hex8HighLowResponse> Hex8Element::EvaluateHighLow(
Failure("Hex8 high+low force is unsupported or nonfinite."));
response.high.internal_force[i] = force[i].high;
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) {
const auto tangent = material[i][j] + 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_