diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9b164e416af..28ea411ffe3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -44,7 +44,6 @@ jobs: -DSINGULARITY_PLUGINS=$(pwd)/../example/plugin \ -DCMAKE_LINKER=ld.gold \ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_CXX_STANDARD=20 \ .. #-DSINGULARITY_TEST_PYTHON=ON \ #-DSINGULARITY_TEST_STELLAR_COLLAPSE=ON \ diff --git a/CHANGELOG.md b/CHANGELOG.md index 09fdc21939c..3556f108dfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - [[PR630]](https://github.com/lanl/singularity-eos/pull/630) Thread execution spaces through vector API. ### Fixed (Repair bugs, etc) +- [[PR637]](https://github.com/lanl/singularity-eos/pull/637) Fix is_normal ### Changed (changing behavior/API/variables/...) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5af39066376..279bef29580 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -225,12 +225,12 @@ if(SINGULARITY_BUILD_PYTHON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() -# require at least C++17, but allow newer versions to become a client requirement if +# require at least C++20, but allow newer versions to become a client requirement if # explicitly set at build time (needed for building with newer Kokkos) if(CMAKE_CXX_STANDARD) target_compile_features(singularity-eos_Interface INTERFACE cxx_std_${CMAKE_CXX_STANDARD}) else() - target_compile_features(singularity-eos_Interface INTERFACE cxx_std_17) + target_compile_features(singularity-eos_Interface INTERFACE cxx_std_20) endif() # checks if this is our build, or we've been imported via `add_subdirectory` NB: diff --git a/singularity-eos/base/error_utils.hpp b/singularity-eos/base/error_utils.hpp index 4e0527f60f3..d5a1d71118f 100644 --- a/singularity-eos/base/error_utils.hpp +++ b/singularity-eos/base/error_utils.hpp @@ -1,5 +1,5 @@ //------------------------------------------------------------------------------ -// © 2021-2025. Triad National Security, LLC. All rights reserved. This +// © 2021-2026. Triad National Security, LLC. All rights reserved. This // program was produced under U.S. Government contract 89233218CNA000001 // for Los Alamos National Laboratory (LANL), which is operated by Triad // National Security, LLC for the U.S. Department of Energy/National @@ -12,27 +12,33 @@ // publicly and display publicly, and to permit others to do so. //------------------------------------------------------------------------------ +// This file created in part with the assistance of generative AI + #ifndef SINGULARITY_EOS_BASE_ERROR_UTILS_HPP_ #define SINGULARITY_EOS_BASE_ERROR_UTILS_HPP_ #include +#include #include #include +#include namespace singularity { namespace error_utils { using PortsOfCall::printf; -constexpr double _NORMAL_FACTOR = 1.0e10; +// Only accept values that are safely away from overflow by this factor. In +// other words, a nonzero value must be normal and have magnitude no larger +// than max()/NORMAL_FACTOR. +constexpr double NORMAL_FACTOR = 1.0e10; struct is_normal_or_zero { template constexpr bool PORTABLE_FORCEINLINE_FUNCTION operator()(valT value) const { - static_assert(std::is_floating_point::value); - return (value == valT{0}) || - (std::isnormal(_NORMAL_FACTOR * value) && std::isnormal(value)); + return PortsOfCall::Robust::is_normal_or_zero(value, + static_cast(NORMAL_FACTOR)); } }; @@ -52,16 +58,18 @@ struct is_non_negative { } }; -// Checks whether a value obeys some sort of provided condition. If not, returns true and -// prints the provided error message, variable name, and value (but does not abort!) +// Checks whether a value obeys some sort of provided condition. If not, returns true +// and prints the provided error message, variable name, and value (but does not abort!) template PORTABLE_FORCEINLINE_FUNCTION bool violates_condition(valT &&value, condT &&condition, nameT &&var_name) { const bool good = condition(std::forward(value)); +#ifndef NDEBUG if (!good) { printf("### ERROR: Bad singularity-eos value\n Var: %s\n Value: %.15e\n", var_name, value); } +#endif // NDEBUG return !good; } @@ -78,7 +86,7 @@ PORTABLE_FORCEINLINE_FUNCTION bool non_positive_value(valT &&value, nameT &&var_ } template PORTABLE_FORCEINLINE_FUNCTION bool negative_value(valT &&value, nameT &&var_name) { - return violates_condition(std::forward(value), is_strictly_positive{}, + return violates_condition(std::forward(value), is_non_negative{}, std::forward(var_name)); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d4e65ab60f4..e0d77246a46 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -37,6 +37,7 @@ add_executable( test_eos_modifiers_minimal.cpp test_eos_vector.cpp test_math_utils.cpp + test_error_utils.cpp test_robust_utils.cpp test_modifier_floored_energy.cpp test_variadic_utils.cpp diff --git a/test/test_error_utils.cpp b/test/test_error_utils.cpp new file mode 100644 index 00000000000..c2a3d67ec4f --- /dev/null +++ b/test/test_error_utils.cpp @@ -0,0 +1,104 @@ +//------------------------------------------------------------------------------ +// © 2021-2026. Triad National Security, LLC. All rights reserved. This +// program was produced under U.S. Government contract 89233218CNA000001 +// for Los Alamos National Laboratory (LANL), which is operated by Triad +// National Security, LLC for the U.S. Department of Energy/National +// Nuclear Security Administration. All rights in the program are +// reserved by Triad National Security, LLC, and the U.S. Department of +// Energy/National Nuclear Security Administration. The Government is +// granted for itself and others acting on its behalf a nonexclusive, +// paid-up, irrevocable worldwide license in this material to reproduce, +// prepare derivative works, distribute copies to the public, perform +// publicly and display publicly, and to permit others to do so. +//------------------------------------------------------------------------------ + +// This file created with the assistance of generative AI + +#include +#include + +#include +#include + +#ifndef CATCH_CONFIG_FAST_COMPILE +#define CATCH_CONFIG_FAST_COMPILE +#include +#endif + +namespace error_utils = singularity::error_utils; + +template +void CheckNormalOrZeroClassification(const char *type_name) { + using limits = std::numeric_limits; + const T factor = static_cast(error_utils::NORMAL_FACTOR); + const auto condition = error_utils::is_normal_or_zero{}; + + CAPTURE(type_name); + + REQUIRE(condition(T{0})); + REQUIRE(condition(-T{0})); + REQUIRE(condition(static_cast(1.382884838243760e+06))); + REQUIRE(condition(limits::min())); + REQUIRE(condition(-limits::min())); + + if constexpr (limits::has_denorm != std::denorm_absent) { + REQUIRE_FALSE(condition(limits::denorm_min())); + REQUIRE_FALSE(condition(-limits::denorm_min())); + } + + REQUIRE_FALSE(condition(limits::quiet_NaN())); + REQUIRE_FALSE(condition(limits::infinity())); + REQUIRE_FALSE(condition(-limits::infinity())); + + const T safely_bounded = (limits::max() / factor) * T{0.5}; + const T too_large = (limits::max() / factor) * T{2.0}; + REQUIRE(condition(safely_bounded)); + REQUIRE_FALSE(condition(too_large)); +} + +template +void CheckNormalOrZeroClassificationOnDevice(const char *type_name) { + using limits = std::numeric_limits; + const T factor = static_cast(error_utils::NORMAL_FACTOR); + const auto condition = error_utils::is_normal_or_zero{}; + + CAPTURE(type_name); + + int nwrong = 0; + portableReduce( + "Check error_utils::is_normal_or_zero on device", 0, 1, + PORTABLE_LAMBDA(const int /*i*/, int &nw) { + nw += !condition(T{0}); + nw += !condition(-T{0}); + nw += !condition(static_cast(1.382884838243760e+06)); + nw += !condition(limits::min()); + nw += !condition(-limits::min()); + + if constexpr (limits::has_denorm != std::denorm_absent) { + nw += condition(limits::denorm_min()); + nw += condition(-limits::denorm_min()); + } + + nw += condition(limits::quiet_NaN()); + nw += condition(limits::infinity()); + nw += condition(-limits::infinity()); + nw += !condition((limits::max() / factor) * T{0.5}); + nw += condition((limits::max() / factor) * T{2.0}); + }, + nwrong); + + REQUIRE(nwrong == 0); +} + +SCENARIO( + "Error utilities classify normal values, denormals, and overflow-adjacent values", + "[ErrorUtils]") { + CheckNormalOrZeroClassification("float"); + CheckNormalOrZeroClassification("double"); +} + +SCENARIO("Error utilities classify values consistently in device reductions", + "[ErrorUtils][Device]") { + CheckNormalOrZeroClassificationOnDevice("float"); + CheckNormalOrZeroClassificationOnDevice("double"); +} diff --git a/utils/kokkos b/utils/kokkos index 15dc143e5f3..3ec81abe181 160000 --- a/utils/kokkos +++ b/utils/kokkos @@ -1 +1 @@ -Subproject commit 15dc143e5f39949eece972a798e175c4b463d4b8 +Subproject commit 3ec81abe1816109f6f62ac48cef41921f91a4d00 diff --git a/utils/kokkos-kernels b/utils/kokkos-kernels index 957ac84922a..2c7c3c301e8 160000 --- a/utils/kokkos-kernels +++ b/utils/kokkos-kernels @@ -1 +1 @@ -Subproject commit 957ac84922a25ac76d0c67815f4cdfc243897f6a +Subproject commit 2c7c3c301e810c7ebde0f154db900850a108ddb0 diff --git a/utils/ports-of-call b/utils/ports-of-call index 0445b367a2a..2c687c13c6a 160000 --- a/utils/ports-of-call +++ b/utils/ports-of-call @@ -1 +1 @@ -Subproject commit 0445b367a2a55f6837f8b694797503e16176b6d6 +Subproject commit 2c687c13c6adb34b01ba56c34d15a4bed505c17f