Integrate Refactor solver sources and dual MSVC CMake builds
This commit is contained in:
@@ -1,15 +1,10 @@
|
||||
#include "../../../src/fesa/math/high_low_arithmetic.h"
|
||||
#include "elements/hex8_pair_simd.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#include "high_low_arithmetic_original.h"
|
||||
|
||||
namespace {
|
||||
using namespace fesa::math_internal;
|
||||
@@ -131,109 +126,81 @@ TEST(HighLowArithmetic, RejectsChangedRoundingAndFlushToZeroEnvironment) {
|
||||
EXPECT_FALSE(ArithmeticEnvironmentSupported());
|
||||
#endif
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::uint64_t Bits(const double value) {
|
||||
std::uint64_t result = 0;
|
||||
std::memcpy(&result, &value, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
double FromBits(const std::uint64_t bits) {
|
||||
double result = 0;
|
||||
std::memcpy(&result, &bits, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
TEST(HighLowArithmetic, FiniteClassificationMatchesBinary64Boundaries) {
|
||||
const auto check = [](const double value) {
|
||||
const bool expected = std::isfinite(value);
|
||||
EXPECT_EQ(IsFinite(HighLow{value, 0.0}), expected);
|
||||
EXPECT_EQ(IsFinite(HighLow{1.0, value}), expected);
|
||||
};
|
||||
for (std::uint64_t exponent = 0; exponent < 2048U; ++exponent) {
|
||||
for (const auto fraction : {0ULL, 1ULL, 1ULL << 51U,
|
||||
(1ULL << 52U) - 1ULL}) {
|
||||
for (const auto sign : {0ULL, 1ULL << 63U})
|
||||
check(FromBits(sign | (exponent << 52U) | fraction));
|
||||
}
|
||||
}
|
||||
std::mt19937_64 random{0x5800f17U};
|
||||
for (int sample = 0; sample < 10000; ++sample) check(FromBits(random()));
|
||||
}
|
||||
|
||||
void ExpectSamePair(const HighLow actual,
|
||||
const fesa::high_low_original::HighLow expected) {
|
||||
EXPECT_EQ(Bits(actual.high), Bits(expected.high));
|
||||
EXPECT_EQ(Bits(actual.low), Bits(expected.low));
|
||||
}
|
||||
|
||||
TEST(HighLowArithmetic, BitExponentMatchesFrozenOracleAcrossEveryExponent) {
|
||||
ASSERT_TRUE(ArithmeticEnvironmentSupported());
|
||||
// Every normal exponent, every possible trailing significand-bit count,
|
||||
// both signs; then every subnormal single-bit and mixed-bit boundary.
|
||||
for (std::uint64_t exponent = 1; exponent < 2047; ++exponent) {
|
||||
for (unsigned trailing = 0; trailing <= 52U; ++trailing) {
|
||||
const auto fraction = trailing == 52U ? 0ULL : 1ULL << trailing;
|
||||
for (const auto sign : {0ULL, 1ULL << 63U}) {
|
||||
const double value = FromBits(sign | (exponent << 52U) | fraction);
|
||||
ASSERT_EQ(LowestBitExponent(value),
|
||||
fesa::high_low_original::LowestBitExponent(value));
|
||||
namespace {
|
||||
TEST(HighLowArithmetic, PacketDispatchRetainsComponentsAndInstructionGates) {
|
||||
using namespace fesa::hex8_internal;
|
||||
const std::uint32_t leaf1 = (1U << 12) | (1U << 26) | (1U << 27) | (1U << 28);
|
||||
const std::uint32_t leaf7 = (1U << 3) | (1U << 5) | (1U << 8);
|
||||
EXPECT_TRUE(SupportsMaterialPacketInstructions(leaf1, 6, leaf7));
|
||||
for (const auto bit : {12,26,27,28})
|
||||
EXPECT_FALSE(SupportsMaterialPacketInstructions(leaf1 & ~(1U << bit),6,leaf7));
|
||||
for (const auto bit : {3,5,8})
|
||||
EXPECT_FALSE(SupportsMaterialPacketInstructions(leaf1,6,leaf7 & ~(1U << bit)));
|
||||
EXPECT_FALSE(SupportsMaterialPacketInstructions(leaf1,2,leaf7));
|
||||
EXPECT_FALSE(SupportsMaterialPacketInstructions(leaf1,4,leaf7));
|
||||
const auto selected = SelectMaterialPacketEvaluator();
|
||||
std::size_t finite_pairs = 0, retained_low_pairs = 0, invalid_pairs = 0;
|
||||
RecordProperty("selected_backend", selected == EvaluateMaterialPacketAvx2 ? "avx2" : "scalar");
|
||||
const auto verify = [selected, &finite_pairs, &retained_low_pairs, &invalid_pairs](
|
||||
const MaterialPacketInput& input) {
|
||||
PairPacket expected{}, actual{};
|
||||
EvaluateMaterialPacketScalar(input, &expected);
|
||||
selected(input, &actual);
|
||||
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||
if (std::isnan(expected.values[lane].high)) {
|
||||
++invalid_pairs;
|
||||
EXPECT_TRUE(std::isnan(actual.values[lane].high));
|
||||
} else {
|
||||
++finite_pairs;
|
||||
if (expected.values[lane].low != 0) ++retained_low_pairs;
|
||||
EXPECT_DOUBLE_EQ(actual.values[lane].high, expected.values[lane].high);
|
||||
EXPECT_DOUBLE_EQ(actual.values[lane].low, expected.values[lane].low);
|
||||
EXPECT_EQ(std::signbit(actual.values[lane].low),
|
||||
std::signbit(expected.values[lane].low));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (unsigned bit = 0; bit < 52U; ++bit) {
|
||||
for (const auto significand : {1ULL << bit, (1ULL << 52U) - (1ULL << bit)}) {
|
||||
const double value = FromBits(significand);
|
||||
EXPECT_EQ(LowestBitExponent(value),
|
||||
fesa::high_low_original::LowestBitExponent(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(HighLowArithmetic, OptimizedArithmeticMatchesFrozenOriginalBits) {
|
||||
ASSERT_TRUE(ArithmeticEnvironmentSupported());
|
||||
std::vector<double> values{0.0, -0.0,
|
||||
std::numeric_limits<double>::denorm_min(),
|
||||
-std::numeric_limits<double>::denorm_min(),
|
||||
std::numeric_limits<double>::min(),
|
||||
std::nextafter(std::numeric_limits<double>::min(), 0.0),
|
||||
std::numeric_limits<double>::max(),
|
||||
-std::numeric_limits<double>::max(), 1.0, -1.0,
|
||||
std::nextafter(1.0, 2.0), std::ldexp(1.0, -53),
|
||||
std::ldexp(1.0, -54), std::numeric_limits<double>::infinity(),
|
||||
-std::numeric_limits<double>::infinity(),
|
||||
std::numeric_limits<double>::quiet_NaN()};
|
||||
std::mt19937_64 random{0x58a0fU};
|
||||
for (int sample = 0; sample < 4000; ++sample) {
|
||||
values.push_back(FromBits(random()));
|
||||
}
|
||||
for (std::size_t index = 0; index < values.size(); ++index) {
|
||||
const double high = values[index];
|
||||
const double low = values[(index * 13U + 7U) % values.size()];
|
||||
const fesa::high_low_original::HighLow original{high, low};
|
||||
EXPECT_EQ(IsNormalized({high, low}),
|
||||
fesa::high_low_original::IsNormalized(original));
|
||||
// Normalize a raw pair with the frozen operation, never the new predicate.
|
||||
const auto a = fesa::high_low_original::TwoSum(high, low);
|
||||
const auto b = fesa::high_low_original::TwoSum(
|
||||
values[(index + 1U) % values.size()],
|
||||
values[(index * 3U + 2U) % values.size()]);
|
||||
ExpectSamePair(Add({a.high, a.low}, {b.high, b.low}),
|
||||
fesa::high_low_original::Add(a, b));
|
||||
ExpectSamePair(Multiply({a.high, a.low}, {b.high, b.low}),
|
||||
fesa::high_low_original::Multiply(a, b));
|
||||
ExpectSamePair(Divide({a.high, a.low}, {b.high, b.low}),
|
||||
fesa::high_low_original::Divide(a, b));
|
||||
ExpectSamePair(TwoProduct(high, low),
|
||||
fesa::high_low_original::TwoProduct(high, low));
|
||||
}
|
||||
for (double high : {1.0, std::nextafter(1.0, 2.0), -1.0,
|
||||
std::numeric_limits<double>::max()}) {
|
||||
for (double low : {0.0, -0.0, std::ldexp(1.0, -53),
|
||||
-std::ldexp(1.0, -53)}) {
|
||||
EXPECT_EQ(IsNormalized({high, low}),
|
||||
fesa::high_low_original::IsNormalized({high, low}));
|
||||
};
|
||||
for (const bool full_low : {false,true})
|
||||
for (const int exponent : {-970,-100,0,100,400}) {
|
||||
MaterialPacketInput input{};
|
||||
input.weight = {0.5, 0};
|
||||
for (std::size_t lane = 0; lane < 4; ++lane) {
|
||||
input.deviatoric_left.values[lane] =
|
||||
{std::ldexp(1.0, exponent), full_low ? std::ldexp(1.0,exponent-54) : 0};
|
||||
input.deviatoric_right.values[lane] = {1.25, std::ldexp(1.0,-60)};
|
||||
input.shear_left.values[lane] = {0, 0};
|
||||
input.shear_right.values[lane] = {1,0};
|
||||
input.tensor_left.values[lane] = {-1, std::ldexp(1.0,-55)};
|
||||
input.gradient_dot.values[lane] = {std::ldexp(1.0,exponent),0};
|
||||
input.accumulated.values[lane] = {std::ldexp(1.0,exponent),0};
|
||||
}
|
||||
verify(input);
|
||||
// Mixed-low packets explicitly exercise the scalar bridge.
|
||||
input.deviatoric_left.values[1].low = 0;
|
||||
verify(input);
|
||||
input.shear_left.values[3] = {-0.0,0}; // raw signed-zero is invalid by contract
|
||||
input.deviatoric_left.values[0] = {std::numeric_limits<double>::max(),0};
|
||||
input.deviatoric_right.values[0] = {2,0};
|
||||
input.deviatoric_left.values[1] = {1,1};
|
||||
input.deviatoric_left.values[2] = {std::numeric_limits<double>::denorm_min(),0};
|
||||
input.deviatoric_right.values[2] = {0.5,0};
|
||||
verify(input);
|
||||
}
|
||||
EXPECT_GT(finite_pairs, 0U);
|
||||
EXPECT_GT(retained_low_pairs, 0U);
|
||||
EXPECT_GT(invalid_pairs, 0U);
|
||||
RecordProperty("finite_pairs", static_cast<int>(finite_pairs));
|
||||
RecordProperty("retained_low_pairs", static_cast<int>(retained_low_pairs));
|
||||
RecordProperty("invalid_pairs", static_cast<int>(invalid_pairs));
|
||||
// Selection is thread-environment dependent rather than cached after first use.
|
||||
#if defined(_M_X64) || defined(__SSE2__)
|
||||
const unsigned saved = _mm_getcsr();
|
||||
_mm_setcsr(saved | 0x8000U);
|
||||
EXPECT_EQ(SelectMaterialPacketEvaluator(), EvaluateMaterialPacketScalar);
|
||||
_mm_setcsr(saved);
|
||||
EXPECT_EQ(SelectMaterialPacketEvaluator(), selected);
|
||||
#endif
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user