From 2766704918141df158f14d1afb952898e876cc14 Mon Sep 17 00:00:00 2001 From: Jonah Miller Date: Wed, 6 May 2026 14:56:14 -0400 Subject: [PATCH 1/6] fix error utils to work on architectures where std::is_normal is not supported --- singularity-eos/base/error_utils.hpp | 19 +++-- test/CMakeLists.txt | 1 + test/test_error_utils.cpp | 104 +++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 test/test_error_utils.cpp diff --git a/singularity-eos/base/error_utils.hpp b/singularity-eos/base/error_utils.hpp index 4e0527f60f3..195d93ab3af 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,10 +12,13 @@ // 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 @@ -25,14 +28,20 @@ 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); + using limits = std::numeric_limits; + const valT abs_value = (value < valT{0}) ? -value : value; return (value == valT{0}) || - (std::isnormal(_NORMAL_FACTOR * value) && std::isnormal(value)); + ((abs_value >= limits::min()) && + (abs_value * static_cast(NORMAL_FACTOR) <= limits::max())); } }; @@ -58,10 +67,12 @@ 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 +89,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"); +} From f9b0652eb0fcc78776486a029af3555f515e6f37 Mon Sep 17 00:00:00 2001 From: Jonah Miller Date: Wed, 6 May 2026 16:08:18 -0400 Subject: [PATCH 2/6] man this really should never trigger so maybe it shouldn't be hidden behind an if? --- singularity-eos/base/error_utils.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/singularity-eos/base/error_utils.hpp b/singularity-eos/base/error_utils.hpp index 195d93ab3af..804fafb27e8 100644 --- a/singularity-eos/base/error_utils.hpp +++ b/singularity-eos/base/error_utils.hpp @@ -67,12 +67,10 @@ 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; } From dea748d10939e93eb0cc04c7b6c2e30d848089fe Mon Sep 17 00:00:00 2001 From: Jonah Miller Date: Wed, 6 May 2026 16:09:48 -0400 Subject: [PATCH 3/6] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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/...) From d8fac7ede9a80d7761b23d6bc95d47dd0dc68069 Mon Sep 17 00:00:00 2001 From: Jonah Miller Date: Fri, 8 May 2026 17:59:18 -0400 Subject: [PATCH 4/6] use ports of call method --- CMakeLists.txt | 4 ++-- singularity-eos/base/error_utils.hpp | 15 +++++++-------- utils/ports-of-call | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) 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 804fafb27e8..d5a1d71118f 100644 --- a/singularity-eos/base/error_utils.hpp +++ b/singularity-eos/base/error_utils.hpp @@ -22,6 +22,7 @@ #include #include +#include namespace singularity { namespace error_utils { @@ -36,12 +37,8 @@ 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); - using limits = std::numeric_limits; - const valT abs_value = (value < valT{0}) ? -value : value; - return (value == valT{0}) || - ((abs_value >= limits::min()) && - (abs_value * static_cast(NORMAL_FACTOR) <= limits::max())); + return PortsOfCall::Robust::is_normal_or_zero(value, + static_cast(NORMAL_FACTOR)); } }; @@ -61,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; } 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 From 2c8144f0eed8dba416e2987c31a2843e1d0b2786 Mon Sep 17 00:00:00 2001 From: Jonah Miller Date: Fri, 8 May 2026 18:00:56 -0400 Subject: [PATCH 5/6] no need to explicitly test C++20 now --- .github/workflows/tests.yml | 1 - 1 file changed, 1 deletion(-) 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 \ From 9a48595d00efe1c4fd12605e6282e5ffc222d500 Mon Sep 17 00:00:00 2001 From: Jonah Miller Date: Fri, 8 May 2026 18:03:41 -0400 Subject: [PATCH 6/6] update kokkos, kokkos-kernels to 5.1.0 --- utils/kokkos | 2 +- utils/kokkos-kernels | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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