feat(hex8): preserve full high-low state in dynamic element responses
This commit is contained in:
@@ -39,6 +39,7 @@ add_library(
|
||||
core/status.cpp
|
||||
elements/element_factory.cpp
|
||||
elements/hex8_element.cpp
|
||||
elements/hex8_high_low.cpp
|
||||
elements/hex8_geometry.cpp
|
||||
elements/euler_beam_3d.cpp
|
||||
elements/finite_rotation.cpp
|
||||
|
||||
@@ -241,6 +241,14 @@ Result<Hex8Response> Hex8Element::EvaluateLinear(const Vector& u) const {
|
||||
Result<Hex8Response> Hex8Element::EvaluateNonlinear(const Vector& u) const {
|
||||
return Evaluate(u, true);
|
||||
}
|
||||
Result<Hex8HighLowResponse> Hex8Element::EvaluateLinearHighLow(
|
||||
const Vector& high, const Vector& low) const {
|
||||
return EvaluateHighLow(high, low, false);
|
||||
}
|
||||
Result<Hex8HighLowResponse> Hex8Element::EvaluateNonlinearHighLow(
|
||||
const Vector& high, const Vector& low) const {
|
||||
return EvaluateHighLow(high, low, true);
|
||||
}
|
||||
Result<Hex8Response> Hex8Element::Evaluate(const Vector& u,
|
||||
bool nonlinear) const {
|
||||
if (u.Size() != 24U)
|
||||
|
||||
@@ -133,7 +133,7 @@ std::pair<Coefficients, Coefficients> Split(const Coefficients& original,
|
||||
} // namespace
|
||||
|
||||
Certificate Certify(const Hex8Element::Coordinates& coordinates,
|
||||
const Vector* displacement) {
|
||||
const Vector* displacement, const Vector* displacement_low) {
|
||||
std::array<std::array<Interval, 3>, 8> x{};
|
||||
for (std::size_t a = 0; a < 8; ++a)
|
||||
for (std::size_t i = 0; i < 3; ++i) {
|
||||
@@ -145,6 +145,11 @@ Certificate Certify(const Hex8Element::Coordinates& coordinates,
|
||||
if (!std::isfinite(u)) return Certificate::kNonfinite;
|
||||
x[a][i] = Add(x[a][i], {u, u});
|
||||
}
|
||||
if (displacement_low) {
|
||||
const auto low = (*displacement_low)[3U * a + i];
|
||||
if (!std::isfinite(low)) return Certificate::kNonfinite;
|
||||
x[a][i] = Add(x[a][i], {low, low});
|
||||
}
|
||||
if (!Finite(x[a][i])) return Certificate::kNonfinite;
|
||||
}
|
||||
Coefficients coefficients{};
|
||||
|
||||
@@ -16,7 +16,8 @@ enum class Certificate { kPositive, kNonpositive, kUncertain, kNonfinite };
|
||||
/// @brief Certifies the closed natural cube with interval Bernstein bounds.
|
||||
/// @note Current addition rounding is enclosed when displacement is supplied.
|
||||
Certificate Certify(const Hex8Element::Coordinates& coordinates,
|
||||
const Vector* displacement = nullptr);
|
||||
const Vector* displacement = nullptr,
|
||||
const Vector* displacement_low = nullptr);
|
||||
/// @brief Evaluates trilinear shapes and natural derivatives in source order.
|
||||
void Shape(const Triple& natural, std::array<double, 8>& values,
|
||||
std::array<Triple, 8>& derivatives);
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
#include "fesa/elements/hex8_element.h"
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <utility>
|
||||
|
||||
#include "../math/high_low_arithmetic.h"
|
||||
#include "hex8_geometry.h"
|
||||
|
||||
namespace fesa {
|
||||
namespace {
|
||||
using math_internal::HighLow;
|
||||
using Triple = std::array<HighLow, 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 math_internal::Subtract(a, b);
|
||||
}
|
||||
HighLow operator*(HighLow a, HighLow b) {
|
||||
return math_internal::Multiply(a, b);
|
||||
}
|
||||
HighLow operator/(HighLow a, HighLow b) {
|
||||
return math_internal::Divide(a, b);
|
||||
}
|
||||
HighLow& operator+=(HighLow& a, HighLow b) { return a = a + b; }
|
||||
|
||||
/// @brief Contracts complete pairs in fixed component order.
|
||||
HighLow Dot(const Triple& a, const Triple& b) {
|
||||
HighLow result;
|
||||
for (std::size_t i = 0; i < 3; ++i) result += a[i] * b[i];
|
||||
return result;
|
||||
}
|
||||
HighLow Determinant(const Tensor& a) {
|
||||
return a[0][0] * (a[1][1] * a[2][2] - a[1][2] * a[2][1]) -
|
||||
a[0][1] * (a[1][0] * a[2][2] - a[1][2] * a[2][0]) +
|
||||
a[0][2] * (a[1][0] * a[2][1] - a[1][1] * a[2][0]);
|
||||
}
|
||||
bool Finite(const Tensor& a) {
|
||||
for (const auto& row : a)
|
||||
for (auto value : row)
|
||||
if (!math_internal::IsNormalized(value)) return false;
|
||||
return true;
|
||||
}
|
||||
std::array<double, 6> Pack(const Tensor& a) {
|
||||
using math_internal::Rounded;
|
||||
return {Rounded(a[0][0]), Rounded(a[1][1]), Rounded(a[2][2]),
|
||||
Rounded(a[0][1]), Rounded(a[0][2]), Rounded(a[1][2])};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Result<Hex8HighLowResponse> Hex8Element::EvaluateHighLow(
|
||||
const Vector& high, const Vector& low, bool nonlinear) const {
|
||||
using math_internal::IsNormalized;
|
||||
if (!math_internal::ArithmeticEnvironmentSupported())
|
||||
return Result<Hex8HighLowResponse>::Failure(Failure(
|
||||
"Hex8 high+low requires nearest rounding and gradual underflow."));
|
||||
if (high.Size() != 24U || low.Size() != 24U)
|
||||
return Result<Hex8HighLowResponse>::Failure(
|
||||
Failure("Hex8 high+low trial requires two 24-component vectors."));
|
||||
std::array<HighLow, 24> relative{};
|
||||
for (std::size_t a = 0; a < 8; ++a)
|
||||
for (std::size_t i = 0; i < 3; ++i) {
|
||||
const auto row = 3U * a + i;
|
||||
const HighLow value{high[row], low[row]};
|
||||
const HighLow origin{high[i], low[i]};
|
||||
if (!IsNormalized(value) || !IsNormalized(origin))
|
||||
return Result<Hex8HighLowResponse>::Failure(
|
||||
Failure("Hex8 high+low translation is not finite normalized."));
|
||||
relative[row] = value - origin;
|
||||
if (!IsNormalized(relative[row]))
|
||||
return Result<Hex8HighLowResponse>::Failure(
|
||||
Failure("Hex8 high+low relative translation is unsupported."));
|
||||
}
|
||||
const auto certificate = hex8_internal::Certify(coordinates_, &high, &low);
|
||||
if (certificate != hex8_internal::Certificate::kPositive) {
|
||||
const char* message = "Hex8 full high+low current geometry is nonpositive.";
|
||||
if (certificate == hex8_internal::Certificate::kUncertain)
|
||||
message = "Hex8 full high+low Jacobian positivity is unresolved at "
|
||||
"depth 10 or cell budget 65536.";
|
||||
if (certificate == hex8_internal::Certificate::kNonfinite)
|
||||
message = "Hex8 full high+low Jacobian interval arithmetic is nonfinite.";
|
||||
return Result<Hex8HighLowResponse>::Failure(Failure(message));
|
||||
}
|
||||
|
||||
Hex8HighLowResponse response;
|
||||
std::array<HighLow, 24> force{};
|
||||
std::array<std::array<HighLow, 24>, 24> material{}, geometric{};
|
||||
HighLow energy;
|
||||
for (std::size_t g = 0; g < 8; ++g) {
|
||||
const auto& point = points_[g];
|
||||
std::array<Triple, 8> gradients{};
|
||||
Triple gradient_sum{};
|
||||
Tensor h{};
|
||||
for (std::size_t a = 1; a < 8; ++a)
|
||||
for (std::size_t j = 0; j < 3; ++j) {
|
||||
gradients[a][j] = HighLow{point.gradients[a][j]};
|
||||
gradient_sum[j] += gradients[a][j];
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
h[i][j] += relative[3U * a + i] * gradients[a][j];
|
||||
}
|
||||
// This is the derivative of the relative displacement expression above.
|
||||
// Existing rounded G0 and cached K0 are not substituted into this operator.
|
||||
for (std::size_t j = 0; j < 3; ++j)
|
||||
gradients[0][j] = math_internal::Negate(gradient_sum[j]);
|
||||
Tensor f{};
|
||||
Tensor strain{}, s{}, p{}, fft{};
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
for (std::size_t j = 0; j < 3; ++j) {
|
||||
f[i][j] = nonlinear ? h[i][j] : HighLow{};
|
||||
if (i == j) f[i][j] += HighLow{1};
|
||||
strain[i][j] = HighLow{0.5} * (h[i][j] + h[j][i]);
|
||||
if (nonlinear)
|
||||
for (std::size_t k = 0; k < 3; ++k)
|
||||
strain[i][j] += HighLow{0.5} * h[k][i] * h[k][j];
|
||||
}
|
||||
const HighLow trace = strain[0][0] + strain[1][1] + strain[2][2];
|
||||
HighLow density_energy = HighLow{0.5} * HighLow{lambda_} * trace * trace;
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
for (std::size_t j = 0; j < 3; ++j) {
|
||||
s[i][j] = HighLow{2} * HighLow{mu_} * strain[i][j];
|
||||
if (i == j) s[i][j] += HighLow{lambda_} * trace;
|
||||
density_energy += HighLow{mu_} * strain[i][j] * strain[i][j];
|
||||
}
|
||||
energy += density_energy * HighLow{point.weight};
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
for (std::size_t j = 0; j < 3; ++j) {
|
||||
for (std::size_t k = 0; k < 3; ++k) {
|
||||
p[i][j] += f[i][k] * s[k][j];
|
||||
fft[i][j] += f[i][k] * f[j][k];
|
||||
}
|
||||
}
|
||||
const HighLow det = Determinant(f);
|
||||
if (!IsNormalized(det) || math_internal::Compare(det, HighLow{}) != 1)
|
||||
return Result<Hex8HighLowResponse>::Failure(Failure(
|
||||
"Hex8 full deformation determinant is not finite positive."));
|
||||
Tensor stress = s;
|
||||
if (nonlinear) {
|
||||
stress = {};
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
for (std::size_t j = 0; j < 3; ++j) {
|
||||
for (std::size_t k = 0; k < 3; ++k)
|
||||
stress[i][j] += p[i][k] * f[j][k];
|
||||
stress[i][j] = stress[i][j] / det;
|
||||
}
|
||||
}
|
||||
if (!Finite(h) || !Finite(f) || !Finite(strain) || !Finite(s) ||
|
||||
!Finite(p) || !Finite(fft) || !Finite(stress))
|
||||
return Result<Hex8HighLowResponse>::Failure(Failure(
|
||||
"Hex8 high+low tensor arithmetic is outside its supported range."));
|
||||
response.high.points[g] = {element_index_, static_cast<int>(g + 1U),
|
||||
point.natural, point.position,
|
||||
Pack(stress), Pack(strain)};
|
||||
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[a]);
|
||||
force[3U * a + i] += Dot(p[i], gradients[a]) * HighLow{point.weight};
|
||||
}
|
||||
for (std::size_t a = 0; a < 8; ++a)
|
||||
for (std::size_t b = 0; b < 8; ++b) {
|
||||
const auto dot = Dot(gradients[a], gradients[b]);
|
||||
HighLow geo;
|
||||
if (nonlinear)
|
||||
for (std::size_t j = 0; j < 3; ++j)
|
||||
for (std::size_t l = 0; l < 3; ++l)
|
||||
geo += gradients[a][j] * s[l][j] * gradients[b][l];
|
||||
for (std::size_t i = 0; i < 3; ++i)
|
||||
for (std::size_t k = 0; k < 3; ++k) {
|
||||
const auto row = 3U * a + i;
|
||||
const auto column = 3U * b + k;
|
||||
const auto mat = HighLow{lambda_} * fg[a][i] * fg[b][k] +
|
||||
HighLow{mu_} * fg[b][i] * fg[a][k] +
|
||||
HighLow{mu_} * fft[i][k] * dot;
|
||||
material[row][column] += mat * HighLow{point.weight};
|
||||
if (i == k)
|
||||
geometric[row][column] += geo * HighLow{point.weight};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!IsNormalized(energy))
|
||||
return Result<Hex8HighLowResponse>::Failure(
|
||||
Failure("Hex8 high+low energy is unsupported or nonfinite."));
|
||||
response.high.strain_energy = energy.high;
|
||||
response.strain_energy_low = energy.low;
|
||||
for (std::size_t i = 0; i < 24; ++i) {
|
||||
if (!IsNormalized(force[i]))
|
||||
return Result<Hex8HighLowResponse>::Failure(
|
||||
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;
|
||||
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]) ||
|
||||
!IsNormalized(tangent))
|
||||
return Result<Hex8HighLowResponse>::Failure(
|
||||
Failure("Hex8 high+low tangent is unsupported or nonfinite."));
|
||||
response.high.material_tangent(i, j) = material[i][j].high;
|
||||
response.high.geometric_tangent(i, j) = geometric[i][j].high;
|
||||
response.high.tangent(i, j) = tangent.high;
|
||||
response.tangent_low(i, j) = tangent.low;
|
||||
}
|
||||
}
|
||||
return Result<Hex8HighLowResponse>::Success(std::move(response));
|
||||
}
|
||||
} // namespace fesa
|
||||
@@ -0,0 +1,162 @@
|
||||
#ifndef FESA_MATH_HIGH_LOW_ARITHMETIC_H_
|
||||
#define FESA_MATH_HIGH_LOW_ARITHMETIC_H_
|
||||
|
||||
#include <cmath>
|
||||
#include <cfenv>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#if defined(_M_X64) || defined(__SSE2__)
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
|
||||
namespace fesa::math_internal {
|
||||
/// @brief Checks the rounding and gradual-underflow premises without mutation.
|
||||
inline bool ArithmeticEnvironmentSupported() {
|
||||
if (std::fegetround() != FE_TONEAREST) return false;
|
||||
#if defined(_M_X64) || defined(__SSE2__)
|
||||
// MXCSR FTZ, DAZ and rounding-control bits must all be clear.
|
||||
if ((_mm_getcsr() & 0xE040U) != 0U) return false;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
/// @brief Two binary64 components of one physical value; raw parts are kept.
|
||||
/// @note Arithmetic requires normalized finite parts and nearest rounding.
|
||||
/// This is an expansion with bounded rounding, not an IEEE quad type.
|
||||
struct HighLow {
|
||||
double high{0};
|
||||
double low{0};
|
||||
HighLow() = default;
|
||||
// Scalar lifting canonicalizes exact signed zero; raw pair inputs do not.
|
||||
HighLow(double value) : high(value == 0 ? 0.0 : value) {}
|
||||
HighLow(double high_part, double low_part)
|
||||
: high(high_part), low(low_part) {}
|
||||
};
|
||||
|
||||
/// @brief Gives an invalid arithmetic result for a checked caller to reject.
|
||||
inline HighLow InvalidHighLow() {
|
||||
return {std::numeric_limits<double>::quiet_NaN(), 0};
|
||||
}
|
||||
/// @brief Checks both physical components, before comparisons or publication.
|
||||
inline bool IsFinite(HighLow value) {
|
||||
return std::isfinite(value.high) && std::isfinite(value.low);
|
||||
}
|
||||
/// @brief Recovers the exact error of supported finite binary64 addition.
|
||||
inline HighLow TwoSum(double left, double right) {
|
||||
const double sum = left + right;
|
||||
const double bridge = sum - left;
|
||||
const double restored_left = sum - bridge;
|
||||
const double error = (left - restored_left) + (right - bridge);
|
||||
if (!std::isfinite(sum) || !std::isfinite(bridge) ||
|
||||
!std::isfinite(restored_left) || !std::isfinite(error))
|
||||
return InvalidHighLow();
|
||||
return sum == 0 && error == 0 ? HighLow{} : HighLow{sum, error};
|
||||
}
|
||||
/// @brief Requires the raw pair to equal its nearest-rounding normalization.
|
||||
inline bool IsNormalized(HighLow value) {
|
||||
if (!IsFinite(value)) return false;
|
||||
if (value.high == 0 && value.low == 0)
|
||||
return !std::signbit(value.high) && !std::signbit(value.low);
|
||||
const auto normalized = TwoSum(value.high, value.low);
|
||||
return IsFinite(normalized) && normalized.high == value.high &&
|
||||
normalized.low == value.low;
|
||||
}
|
||||
/// @brief Rounds the full physical value to one finite output value.
|
||||
inline double Rounded(HighLow value) {
|
||||
return IsNormalized(value) ? value.high + value.low
|
||||
: std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
/// @brief Adds normalized expansions; rounding of discarded tail is bounded.
|
||||
inline HighLow Add(HighLow left, HighLow right) {
|
||||
if (!IsNormalized(left) || !IsNormalized(right)) return InvalidHighLow();
|
||||
const auto head = TwoSum(left.high, right.high);
|
||||
const auto tail = TwoSum(left.low, right.low);
|
||||
const auto middle = TwoSum(head.low, tail.high);
|
||||
const auto joined = TwoSum(head.high, middle.high);
|
||||
const double remainder = (joined.low + middle.low) + tail.low;
|
||||
if (!IsFinite(head) || !IsFinite(tail) || !IsFinite(middle) ||
|
||||
!IsFinite(joined) || !std::isfinite(remainder))
|
||||
return InvalidHighLow();
|
||||
return TwoSum(joined.high, remainder);
|
||||
}
|
||||
/// @brief Negates both components without collapsing the physical value.
|
||||
inline HighLow Negate(HighLow value) {
|
||||
if (!IsNormalized(value)) return InvalidHighLow();
|
||||
return value.high == 0 && value.low == 0
|
||||
? HighLow{}
|
||||
: HighLow{-value.high, -value.low};
|
||||
}
|
||||
/// @brief Subtracts full expansions, including cancellations between highs.
|
||||
inline HighLow Subtract(HighLow left, HighLow right) {
|
||||
return Add(left, Negate(right));
|
||||
}
|
||||
/// @brief Returns the exponent of the least nonzero binary64 significand bit.
|
||||
/// @pre value is finite and nonzero; gradual underflow is enabled.
|
||||
inline int LowestBitExponent(double value) {
|
||||
int exponent = 0;
|
||||
const double fraction = std::frexp(std::abs(value), &exponent);
|
||||
auto significand = static_cast<std::uint64_t>(std::ldexp(fraction, 53));
|
||||
int trailing = 0;
|
||||
while ((significand & 1U) == 0U) {
|
||||
significand >>= 1U;
|
||||
++trailing;
|
||||
}
|
||||
return exponent - 53 + trailing;
|
||||
}
|
||||
/// @brief Gives a product and its FMA residual when the dyadic tail fits.
|
||||
/// @note Rejects underflow-risk exact tails, even if the rounded high is finite.
|
||||
inline HighLow TwoProduct(double left, double right) {
|
||||
if (!std::isfinite(left) || !std::isfinite(right)) return InvalidHighLow();
|
||||
if (left == 0 || right == 0) return {};
|
||||
if (LowestBitExponent(left) + LowestBitExponent(right) < -1074)
|
||||
return InvalidHighLow();
|
||||
const double product = left * right;
|
||||
if (!std::isfinite(product)) return InvalidHighLow();
|
||||
const double error = std::fma(left, right, -product);
|
||||
if (!std::isfinite(error)) return InvalidHighLow();
|
||||
return TwoSum(product, error);
|
||||
}
|
||||
/// @brief Retains all four component products in deterministic order.
|
||||
inline HighLow Multiply(HighLow left, HighLow right) {
|
||||
if (!IsNormalized(left) || !IsNormalized(right)) return InvalidHighLow();
|
||||
auto product = TwoProduct(left.high, right.high);
|
||||
product = Add(product, TwoProduct(left.high, right.low));
|
||||
product = Add(product, TwoProduct(left.low, right.high));
|
||||
return Add(product, TwoProduct(left.low, right.low));
|
||||
}
|
||||
/// @brief Orders canonical values; 2 means unordered/unsupported input.
|
||||
inline int Compare(HighLow left, HighLow right) {
|
||||
if (!IsNormalized(left) || !IsNormalized(right)) return 2;
|
||||
if (left.high < right.high) return -1;
|
||||
if (left.high > right.high) return 1;
|
||||
if (left.low < right.low) return -1;
|
||||
if (left.low > right.low) return 1;
|
||||
return 0;
|
||||
}
|
||||
/// @brief Takes the absolute value using the full physical sign.
|
||||
inline HighLow Abs(HighLow value) {
|
||||
const int sign = Compare(value, HighLow{});
|
||||
return sign == 2 ? InvalidHighLow() : (sign < 0 ? Negate(value) : value);
|
||||
}
|
||||
/// @brief Refines a binary64 quotient twice using the full residual.
|
||||
/// @note Unsupported products/quotient underflow propagate an invalid result.
|
||||
inline HighLow Divide(HighLow numerator, HighLow denominator) {
|
||||
if (!IsNormalized(numerator) || !IsNormalized(denominator) ||
|
||||
Compare(denominator, HighLow{}) == 0)
|
||||
return InvalidHighLow();
|
||||
if (Compare(numerator, HighLow{}) == 0) return {};
|
||||
const double first = numerator.high / denominator.high;
|
||||
if (!std::isfinite(first) || first == 0) return InvalidHighLow();
|
||||
auto quotient = HighLow{first};
|
||||
for (int iteration = 0; iteration < 2; ++iteration) {
|
||||
const auto residual = Subtract(numerator, Multiply(denominator, quotient));
|
||||
if (!IsNormalized(residual)) return InvalidHighLow();
|
||||
const double correction = residual.high / denominator.high;
|
||||
if (!std::isfinite(correction) || (residual.high != 0 && correction == 0))
|
||||
return InvalidHighLow();
|
||||
quotient = Add(quotient, HighLow{correction});
|
||||
}
|
||||
return quotient;
|
||||
}
|
||||
} // namespace fesa::math_internal
|
||||
|
||||
#endif // FESA_MATH_HIGH_LOW_ARITHMETIC_H_
|
||||
Reference in New Issue
Block a user