Files
FESADev/src/fesa/analysis/thermal_transient_bounds.h
T

91 lines
3.5 KiB
C++

#ifndef FESA_ANALYSIS_THERMAL_TRANSIENT_BOUNDS_H_
#define FESA_ANALYSIS_THERMAL_TRANSIENT_BOUNDS_H_
#include <algorithm>
#include <cmath>
#include <limits>
#include "elements/thermal_interval.h"
namespace fesa::thermal_transient_internal::bounds {
using I = thermal_internal::Interval;
/// @brief Recognizes proven zero, distinct from an interval containing zero.
inline bool Zero(I x) { return x.lower == 0 && x.upper == 0; }
/// @brief Encloses addition, retaining proven exact sums and zero identities.
inline I Add(I a, I b) {
if (Zero(a)) return b;
if (Zero(b)) return a;
if (a.lower == a.upper && b.lower == b.upper) {
const double sum = a.lower + b.lower;
const double bv = sum - a.lower, av = sum - bv;
const double error = (a.lower - av) + (b.lower - bv);
// TwoSum proves exact additions (including exact cancellation) without
// creating spurious construction uncertainty at prescribed stationary DOFs.
if (std::isfinite(sum) && std::isfinite(bv) && std::isfinite(av) &&
error == 0)
return I{sum};
}
return a + b;
}
/// @brief Recognizes singleton positive powers of two for exact scaling.
inline bool PowerOfTwo(I x, int& exponent) {
if (x.lower != x.upper || x.lower <= 0 || !std::isfinite(x.lower))
return false;
const double fraction = std::frexp(x.lower, &exponent);
--exponent;
return fraction == .5;
}
/// @brief Encloses products, including loss in subnormal power-of-two scaling.
inline I Mul(I a, I b) {
if (Zero(a) || Zero(b)) return I{0};
if (a.lower == a.upper && a.lower < 0) {
const I value = Mul(I{-a.lower}, b);
return {-value.upper, -value.lower};
}
if (b.lower == b.upper && b.lower < 0) {
const I value = Mul(a, I{-b.lower});
return {-value.upper, -value.lower};
}
int exponent = 0;
if (PowerOfTwo(a, exponent)) return thermal_internal::Scale(b, exponent);
if (PowerOfTwo(b, exponent)) return thermal_internal::Scale(a, exponent);
return a * b;
}
/// @brief Encloses division without discarding subnormal quotient uncertainty.
inline I Div(I a, I b) {
if (b.ContainsZero()) return a / b;
if (Zero(a)) return I{0};
int exponent = 0;
if (PowerOfTwo(b, exponent)) return thermal_internal::Scale(a, -exponent);
return a / b;
}
/// @brief Encloses the absolute value of a signed interval.
inline I Abs(I a) {
return {a.ContainsZero() ? 0 : std::min(std::abs(a.lower), std::abs(a.upper)),
std::max(std::abs(a.lower), std::abs(a.upper))};
}
/// @brief Changes interval sign exactly.
inline I Neg(I a) { return {-a.upper, -a.lower}; }
/// @brief Preserves exact cancellation through the addition certificate.
inline I Sub(I a, I b) { return Add(a, Neg(b)); }
/// @brief Lifts an exact binary64 magnitude into an interval.
inline I Magnitude(double a) { return I{std::abs(a)}; }
/// @brief Encloses the approved binary64 gamma operation-count bound.
inline I Gamma(double count) {
const I product = Mul(I{count}, I{0x1p-53});
return Div(product, Sub(I{1}, product));
}
/// @brief Bounds absolute underflow by one minimum subnormal per operation.
/// @note A proven zero expression performs no inexact operation.
inline I Tiny(double count, I scale) {
return Zero(scale)
? I{0}
: Mul(I{count}, I{std::numeric_limits<double>::denorm_min()});
}
/// @brief Combines relative gamma error with absolute underflow error.
inline I Roundoff(double count, I scale) {
return Add(Mul(Gamma(count), scale), Tiny(count, scale));
}
} // namespace fesa::thermal_transient_internal::bounds
#endif // FESA_ANALYSIS_THERMAL_TRANSIENT_BOUNDS_H_