perf(hex8): use MKL Hessian products and exact zero pair addition

This commit is contained in:
KOKO\Mimi
2026-10-14 16:49:15 +09:00
parent dec6eb3199
commit e18271051c
4 changed files with 116 additions and 34 deletions
+90 -29
View File
@@ -2,6 +2,7 @@
#include <cmath> #include <cmath>
#include <set> #include <set>
#include <stdexcept>
#include <utility> #include <utility>
#include "hex8_geometry.h" #include "hex8_geometry.h"
@@ -26,6 +27,36 @@ std::array<double, 6> Pack(const Tensor& value) {
double Dot(const Triple& a, const Triple& b) { double Dot(const Triple& a, const Triple& b) {
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
} }
/// @brief Forms a scaled 24-component outer product through dense MKL.
Matrix Outer(const std::array<Triple, 8>& values, double coefficient) {
Matrix left{24U, 1U};
Matrix right{1U, 24U};
for (std::size_t a = 0; a < 8; ++a)
for (std::size_t i = 0; i < 3; ++i) {
left(3U * a + i, 0) = coefficient * values[a][i];
right(0, 3U * a + i) = values[a][i];
}
return left.Multiply(right);
}
/// @brief Forms node-gradient Gram and stress congruence through dense MKL.
Matrix GradientProduct(const std::array<Triple, 8>& gradients,
const Tensor* stress = nullptr) {
Matrix left{8U, 3U};
Matrix transpose{3U, 8U};
for (std::size_t a = 0; a < 8; ++a)
for (std::size_t i = 0; i < 3; ++i) {
left(a, i) = gradients[a][i];
transpose(i, a) = gradients[a][i];
}
if (stress != nullptr) {
Matrix stress_transpose{3U, 3U};
for (std::size_t i = 0; i < 3; ++i)
for (std::size_t j = 0; j < 3; ++j)
stress_transpose(i, j) = (*stress)[j][i];
left = left.Multiply(stress_transpose);
}
return left.Multiply(transpose);
}
/// @brief Retains product and cancellation errors in fixed-order contractions. /// @brief Retains product and cancellation errors in fixed-order contractions.
void AccumulateProduct(double left, double right, double& sum, void AccumulateProduct(double left, double right, double& sum,
double& correction) { double& correction) {
@@ -158,30 +189,41 @@ Result<Hex8Element> Hex8Element::Create(EntityIndex index,
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)
integrated_gradients[a][i] += point.weight * gradients[a][i]; integrated_gradients[a][i] += point.weight * gradients[a][i];
Matrix deviatoric{0U, 0U};
Matrix shear{0U, 0U};
Matrix gram{0U, 0U};
try {
deviatoric = Outer(gradients, -(2.0 / 3.0) * candidate.mu_);
shear = Outer(gradients, candidate.mu_);
gram = GradientProduct(gradients);
} catch (const std::invalid_argument&) {
return Result<Hex8Element>::Failure(
candidate.Failure("Hex8 reference stiffness is nonfinite."));
}
for (std::size_t a = 0; a < 8; ++a) for (std::size_t a = 0; a < 8; ++a)
for (std::size_t b = 0; b < 8; ++b) { for (std::size_t b = 0; b < 8; ++b) {
const auto& ga = gradients[a];
const auto& gb = gradients[b];
const double dot = Dot(ga, gb);
for (std::size_t i = 0; i < 3; ++i) for (std::size_t i = 0; i < 3; ++i)
for (std::size_t k = 0; k < 3; ++k) { for (std::size_t k = 0; k < 3; ++k) {
candidate.stiffness_(3U * a + i, 3U * b + k) += candidate.stiffness_(3U * a + i, 3U * b + k) +=
point.weight * (-(2.0 / 3.0) * candidate.mu_ * ga[i] * gb[k] + point.weight * (deviatoric(3U * a + i, 3U * b + k) +
candidate.mu_ * ga[k] * gb[i] + shear(3U * a + k, 3U * b + i) +
(i == k ? candidate.mu_ * dot : 0.0)); (i == k ? candidate.mu_ * gram(a, b) : 0.0));
} }
} }
} }
if (!std::isfinite(volume) || volume <= 0.0) if (!std::isfinite(volume) || volume <= 0.0)
return Result<Hex8Element>::Failure( return Result<Hex8Element>::Failure(
candidate.Failure("Hex8 reference volume is nonfinite.")); candidate.Failure("Hex8 reference volume is nonfinite."));
for (std::size_t a = 0; a < 8; ++a) Matrix bulk{0U, 0U};
for (std::size_t b = 0; b < 8; ++b) try {
for (std::size_t i = 0; i < 3; ++i) bulk = Outer(integrated_gradients, candidate.kappa_ / volume);
for (std::size_t k = 0; k < 3; ++k) } catch (const std::invalid_argument&) {
candidate.stiffness_(3U * a + i, 3U * b + k) += return Result<Hex8Element>::Failure(
(candidate.kappa_ / volume) * integrated_gradients[a][i] * candidate.Failure("Hex8 reference stiffness is nonfinite."));
integrated_gradients[b][k]; }
for (std::size_t i = 0; i < 24; ++i)
for (std::size_t j = 0; j < 24; ++j)
candidate.stiffness_(i, j) += bulk(i, j);
for (std::size_t i = 0; i < 24; ++i) for (std::size_t i = 0; i < 24; ++i)
for (std::size_t j = 0; j < 24; ++j) for (std::size_t j = 0; j < 24; ++j)
if (!std::isfinite(candidate.stiffness_(i, j))) if (!std::isfinite(candidate.stiffness_(i, j)))
@@ -382,6 +424,25 @@ Result<Hex8Response> Hex8Element::Evaluate(const Vector& u,
for (std::size_t i = 0; i < 3; ++i) for (std::size_t i = 0; i < 3; ++i)
integrated_fg[a][i] += point.weight * Dot(f[i], gradients[g][a]); integrated_fg[a][i] += point.weight * Dot(f[i], gradients[g][a]);
} }
Matrix deviatoric{0U, 0U};
Matrix shear{0U, 0U};
Matrix gram{0U, 0U};
Matrix geometric{0U, 0U};
if (nonlinear) {
std::array<Triple, 8> fg{};
for (std::size_t a = 0; a < 8; ++a)
for (std::size_t i = 0; i < 3; ++i)
fg[a][i] = Dot(f[i], gradients[g][a]);
try {
deviatoric = Outer(fg, -(2.0 / 3.0) * mu_);
shear = Outer(fg, mu_);
gram = GradientProduct(gradients[g]);
geometric = GradientProduct(gradients[g], &s);
} catch (const std::invalid_argument&) {
return Result<Hex8Response>::Failure(
Failure("Hex8 trial tangent is nonfinite."));
}
}
for (std::size_t a = 0; a < 8; ++a) { for (std::size_t a = 0; a < 8; ++a) {
const auto& ga = gradients[g][a]; const auto& ga = gradients[g][a];
for (std::size_t i = 0; i < 3; ++i) { for (std::size_t i = 0; i < 3; ++i) {
@@ -399,23 +460,18 @@ Result<Hex8Response> Hex8Element::Evaluate(const Vector& u,
} }
if (nonlinear) if (nonlinear)
for (std::size_t b = 0; b < 8; ++b) { for (std::size_t b = 0; b < 8; ++b) {
const auto& gb = gradients[g][b];
double geometric = 0.0;
for (std::size_t j = 0; j < 3; ++j)
for (std::size_t l = 0; l < 3; ++l)
geometric += ga[j] * s[l][j] * gb[l];
for (std::size_t i = 0; i < 3; ++i) for (std::size_t i = 0; i < 3; ++i)
for (std::size_t k = 0; k < 3; ++k) { for (std::size_t k = 0; k < 3; ++k) {
const auto row = 3U * a + i; const auto row = 3U * a + i;
const auto column = 3U * b + k; const auto column = 3U * b + k;
response.material_tangent(row, column) += response.material_tangent(row, column) +=
point.weight * point.weight *
(-(2.0 / 3.0) * mu_ * Dot(f[i], ga) * Dot(f[k], gb) + (deviatoric(row, column) +
mu_ * Dot(f[i], gb) * Dot(f[k], ga) + shear(3U * b + i, 3U * a + k) +
mu_ * fft[i][k] * Dot(ga, gb)); mu_ * fft[i][k] * gram(a, b));
if (i == k) if (i == k)
response.geometric_tangent(row, column) += response.geometric_tangent(row, column) +=
geometric * point.weight; geometric(a, b) * point.weight;
} }
} }
} }
@@ -429,13 +485,18 @@ Result<Hex8Response> Hex8Element::Evaluate(const Vector& u,
} }
for (std::size_t i = 0; i < 24; ++i) for (std::size_t i = 0; i < 24; ++i)
response.internal_force[i] += force_correction[i]; response.internal_force[i] += force_correction[i];
if (nonlinear) if (nonlinear) {
for (std::size_t a = 0; a < 8; ++a) Matrix bulk{0U, 0U};
for (std::size_t b = 0; b < 8; ++b) try {
for (std::size_t i = 0; i < 3; ++i) bulk = Outer(integrated_fg, kappa_ / volume);
for (std::size_t k = 0; k < 3; ++k) } catch (const std::invalid_argument&) {
response.material_tangent(3 * a + i, 3 * b + k) += return Result<Hex8Response>::Failure(
(kappa_ / volume) * integrated_fg[a][i] * integrated_fg[b][k]; Failure("Hex8 trial tangent is nonfinite."));
}
for (std::size_t i = 0; i < 24; ++i)
for (std::size_t j = 0; j < 24; ++j)
response.material_tangent(i, j) += bulk(i, j);
}
if (!std::isfinite(response.strain_energy)) if (!std::isfinite(response.strain_energy))
return Result<Hex8Response>::Failure( return Result<Hex8Response>::Failure(
Failure("Hex8 trial energy is nonfinite.")); Failure("Hex8 trial energy is nonfinite."));
+3 -1
View File
@@ -14,7 +14,9 @@ using math_internal::HighLow;
using Triple = std::array<HighLow, 3>; using Triple = std::array<HighLow, 3>;
using Tensor = std::array<Triple, 3>; using Tensor = std::array<Triple, 3>;
HighLow operator+(HighLow a, HighLow b) { return math_internal::Add(a, b); } HighLow operator+(HighLow a, HighLow b) {
return hex8_internal::AddPair(a, b);
}
HighLow operator-(HighLow a, HighLow b) { HighLow operator-(HighLow a, HighLow b) {
return math_internal::Subtract(a, b); return math_internal::Subtract(a, b);
} }
+16 -4
View File
@@ -4,6 +4,18 @@
#include "../math/high_low_arithmetic.h" #include "../math/high_low_arithmetic.h"
namespace fesa::hex8_internal { namespace fesa::hex8_internal {
/// @brief Preserves original zero-add normalization without redundant TwoSums.
/// @note Valid nonzero terms retain the original addition association.
inline math_internal::HighLow AddPair(math_internal::HighLow left,
math_internal::HighLow right) {
using namespace math_internal;
if (!IsNormalized(left) || !IsNormalized(right)) return InvalidHighLow();
if (left.high == 0 && left.low == 0)
return {right.high, right.low == 0 ? 0.0 : right.low};
if (right.high == 0 && right.low == 0)
return {left.high, left.low == 0 ? 0.0 : left.low};
return Add(left, right);
}
/// @brief Omits exact-zero cross products without changing nonzero association. /// @brief Omits exact-zero cross products without changing nonzero association.
/// @note A retained zero addition performs the original canonicalization and /// @note A retained zero addition performs the original canonicalization and
/// invalid-result check; its repeated application is idempotent. /// invalid-result check; its repeated application is idempotent.
@@ -14,14 +26,14 @@ inline math_internal::HighLow MultiplyPair(math_internal::HighLow left,
if (left.low != 0 && right.low != 0) return Multiply(left, right); if (left.low != 0 && right.low != 0) return Multiply(left, right);
auto product = TwoProduct(left.high, right.high); auto product = TwoProduct(left.high, right.high);
if (right.low != 0) { if (right.low != 0) {
product = Add(product, TwoProduct(left.high, right.low)); product = AddPair(product, TwoProduct(left.high, right.low));
} else if (left.low != 0) { } else if (left.low != 0) {
// The original zero addition precedes the nonzero low-high term. // The original zero addition precedes the nonzero low-high term.
product = Add(product, HighLow{}); product = AddPair(product, HighLow{});
} }
if (left.low != 0) if (left.low != 0)
product = Add(product, TwoProduct(left.low, right.high)); product = AddPair(product, TwoProduct(left.low, right.high));
return Add(product, HighLow{}); return AddPair(product, HighLow{});
} }
} // namespace fesa::hex8_internal } // namespace fesa::hex8_internal
@@ -31,6 +31,11 @@ void ExpectOriginalProduct(HighLow left, HighLow right) {
{left.high, left.low}, {right.high, right.low}); {left.high, left.low}, {right.high, right.low});
EXPECT_EQ(PairBits(actual.high), PairBits(expected.high)); EXPECT_EQ(PairBits(actual.high), PairBits(expected.high));
EXPECT_EQ(PairBits(actual.low), PairBits(expected.low)); EXPECT_EQ(PairBits(actual.low), PairBits(expected.low));
const auto actual_sum = fesa::hex8_internal::AddPair(left, right);
const auto expected_sum = fesa::high_low_original::Add(
{left.high, left.low}, {right.high, right.low});
EXPECT_EQ(PairBits(actual_sum.high), PairBits(expected_sum.high));
EXPECT_EQ(PairBits(actual_sum.low), PairBits(expected_sum.low));
} }
TEST(Hex8HighLow, ScalarCrossProductShortcutMatchesOriginalBits) { TEST(Hex8HighLow, ScalarCrossProductShortcutMatchesOriginalBits) {
@@ -65,6 +70,8 @@ TEST(Hex8HighLow, ScalarCrossProductShortcutMatchesOriginalBits) {
std::memcpy(&left, &left_bits, sizeof(left)); std::memcpy(&left, &left_bits, sizeof(left));
std::memcpy(&right, &right_bits, sizeof(right)); std::memcpy(&right, &right_bits, sizeof(right));
const auto pair = fesa::math_internal::TwoSum(left, right); const auto pair = fesa::math_internal::TwoSum(left, right);
ExpectOriginalProduct(pair, HighLow{});
ExpectOriginalProduct(HighLow{}, pair);
ExpectOriginalProduct(pair, HighLow{right}); ExpectOriginalProduct(pair, HighLow{right});
ExpectOriginalProduct(HighLow{right}, pair); ExpectOriginalProduct(HighLow{right}, pair);
ExpectOriginalProduct(pair, pair); ExpectOriginalProduct(pair, pair);