diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e31b4cef754..fcd6f895d90 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -175,21 +175,21 @@ openmpi_fortran_kokkos_static_openmp_clang: SPACK_ENV_NAME: openmpi-fortran-kokkos-static-openmp-clang SUBMIT_TO_CDASH: "${ENABLE_CDASH}" -openmpi_rocm_gcc: +openmpi_rocm_rocmcc: extends: [.ascgit_job, .darwin_job, .darwin_regular_job, .build_and_test] needs: - prereq_offline_deps variables: - SPACK_ENV_NAME: openmpi-rocm-gcc + SPACK_ENV_NAME: openmpi-rocm-rocmcc SCHEDULER_PARAMETERS: "-n 1 --qos=debug -p shared-gpu-amd-mi250" SUBMIT_TO_CDASH: "${ENABLE_CDASH}" -openmpi_fortran_rocm_gcc: +openmpi_fortran_rocm_rocmcc: extends: [.ascgit_job, .darwin_job, .darwin_regular_job, .build_and_test] needs: - prereq_offline_deps variables: - SPACK_ENV_NAME: openmpi-fortran-rocm-gcc + SPACK_ENV_NAME: openmpi-fortran-rocm-rocmcc SCHEDULER_PARAMETERS: "-n 1 --qos=debug -p shared-gpu-amd-mi250" SUBMIT_TO_CDASH: "${ENABLE_CDASH}" @@ -204,6 +204,17 @@ rocinante_craympich_gcc: SUBMIT_ON_ERROR: false # Compute nodes don't have network access, don't try to send from compute node SUBMIT_AFTER: "${ENABLE_CDASH}" # after_script runs on a network connected system +venadito_craympich_cuda_gracehopper_gcc: + extends: [.ascgit_job, .venadito_job, .venadito_regular_job, .build_and_test] + needs: + - prereq_offline_deps + variables: + SPACK_ENV_NAME: craympich-cuda-gracehopper-gcc + SUBMIT_TO_CDASH: false + BUILD_WITH_CTEST: "${ENABLE_CDASH}" + SUBMIT_ON_ERROR: false # Compute nodes don't have network access, don't try to send from compute node + SUBMIT_AFTER: "${ENABLE_CDASH}" # after_script runs on a network connected system + rocinante_craympich_fortran_gcc: extends: [.ascgit_job, .rocinante_job, .rocinante_regular_job, .build_and_test] needs: @@ -215,23 +226,23 @@ rocinante_craympich_fortran_gcc: SUBMIT_ON_ERROR: false # Compute nodes don't have network access, don't try to send from compute node SUBMIT_AFTER: "${ENABLE_CDASH}" # after_script runs on a network connected system -rzvernal_craympich_rocm_mi250_cce: +rzvernal_craympich_rocm_mi250_rocmcc: extends: [.ascgit_job, .rzvernal_job, .rzvernal_regular_job, .build_and_test] needs: - prereq_offline_deps variables: - SPACK_ENV_NAME: craympich-rocm-gfx90a-cce + SPACK_ENV_NAME: craympich-rocm-gfx90a-rocmcc SUBMIT_TO_CDASH: false BUILD_WITH_CTEST: "${ENABLE_CDASH}" SUBMIT_ON_ERROR: false # Compute nodes don't have network access, don't try to send from compute node SUBMIT_AFTER: "${ENABLE_CDASH}" # after_script runs on a network connected system -rzadams_craympich_rocm_mi300_cce: +rzadams_craympich_rocm_mi300_rocmcc: extends: [.ascgit_job, .rzadams_job, .rzadams_regular_job, .build_and_test] needs: - prereq_offline_deps variables: - SPACK_ENV_NAME: craympich-rocm-gfx942-cce + SPACK_ENV_NAME: craympich-rocm-gfx942-rocmcc SUBMIT_TO_CDASH: false BUILD_WITH_CTEST: "${ENABLE_CDASH}" SUBMIT_ON_ERROR: false # Compute nodes don't have network access, don't try to send from compute node diff --git a/.gitlab/common.yml b/.gitlab/common.yml index 0410bb66130..2c56c5d2526 100644 --- a/.gitlab/common.yml +++ b/.gitlab/common.yml @@ -74,30 +74,22 @@ default: - if: $ENABLED_CLUSTERS =~ /rocinante/ && $CI_PIPELINE_SOURCE == "web" - if: $ENABLED_CLUSTERS =~ /rocinante/ && $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH -.venado_job: +.venadito_job: variables: - CLUSTER: venado - SCHEDULER_PARAMETERS: "-N 1 -A asc-ci_g -p ci_g --qos ci_g --reservation ci_g --time=02:00:00" + CLUSTER: venadito + SCHEDULER_PARAMETERS: "-N 1 -A vt-ci_g --reservation ci_g --time=02:00:00" tags: - - venado + - venadito - batch -.venado_shell_gpu_job: - variables: - CLUSTER: venado - tags: - - venado - - shell - - gpu - -.venado_regular_job: +.venadito_regular_job: variables: CTEST_MODE: Continuous rules: - - if: $ENABLED_CLUSTERS =~ /venado/ && $GITLAB_USER_LOGIN =~ $VENADO_USERS && $CI_PIPELINE_SOURCE == "merge_request_event" - - if: $ENABLED_CLUSTERS =~ /venado/ && $GITLAB_USER_LOGIN =~ $VENADO_USERS && $CI_PIPELINE_SOURCE == "schedule" - - if: $ENABLED_CLUSTERS =~ /venado/ && $GITLAB_USER_LOGIN =~ $VENADO_USERS && $CI_PIPELINE_SOURCE == "web" - - if: $ENABLED_CLUSTERS =~ /venado/ && $GITLAB_USER_LOGIN =~ $VENADO_USERS && $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + - if: $ENABLED_CLUSTERS =~ /venadito/ && $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $ENABLED_CLUSTERS =~ /venadito/ && $CI_PIPELINE_SOURCE == "schedule" + - if: $ENABLED_CLUSTERS =~ /venadito/ && $CI_PIPELINE_SOURCE == "web" + - if: $ENABLED_CLUSTERS =~ /venadito/ && $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH .rzansel_job: tags: diff --git a/.gitlab/kessel.sh b/.gitlab/kessel.sh index 7ead80d40fd..dcf2f4e944a 100644 --- a/.gitlab/kessel.sh +++ b/.gitlab/kessel.sh @@ -1,10 +1,11 @@ -export DEPLOYMENT_VERSION_CURRENT_DEFAULT="2026-03-03" +export DEPLOYMENT_VERSION_CURRENT_DEFAULT="2026-07-07" export DEPLOYMENT_VERSION_DEFAULT="${DEPLOYMENT_VERSION_DEFAULT:-$DEPLOYMENT_VERSION_CURRENT_DEFAULT}" SCRIPT_PATH=${BASH_SOURCE[0]:-${(%):-%x}} PARENT_DIR="$( cd "$( dirname "${SCRIPT_PATH}" )" &>/dev/null && pwd )" if [ -n "$DEPLOYMENT_MR" ]; then - export DEPLOYMENT_VERSION=${DEPLOYMENT_VERSION:-mr/$DEPLOYMENT_MR/$DEPLOYMENT_VERSION_DEFAULT} + DEPLOYMENT_MR_ID=$(printf "%08d\n" "${DEPLOYMENT_MR}") + export DEPLOYMENT_VERSION=${DEPLOYMENT_VERSION:-mr/$DEPLOYMENT_MR_ID/$DEPLOYMENT_VERSION_DEFAULT} else export DEPLOYMENT_VERSION=${DEPLOYMENT_VERSION:-$DEPLOYMENT_VERSION_DEFAULT} fi @@ -26,7 +27,7 @@ fi _KESSEL_WORKFLOW_DEPLOYMENT="$KESSEL_WORKFLOW_DEPLOYMENT" export KESSEL_WORKFLOW_DEPLOYMENT="${KESSEL_WORKFLOW_DEPLOYMENT:-${TMPDIR:-/tmp}/$USER-ci-envs}" -if [ "$SYSTEM_NAME" == "darwin" ] || [ "$SYSTEM_NAME" == "rocinante" ]; then +if [ "$SYSTEM_NAME" == "darwin" ] || [ "$SYSTEM_NAME" == "rocinante" ] || [ "$SYSTEM_NAME" == "venadito" ]; then export KESSEL_DEPLOYMENT=${KESSEL_DEPLOYMENT:-/usr/projects/xcap/oss/deployments/$DEPLOYMENT_VERSION/$SYSTEM_NAME} elif [ "$SYSTEM_NAME" == "rzadams" ] || [ "$SYSTEM_NAME" == "rzvernal" ] || [ "$SYSTEM_NAME" == "elcapitan" ] || [ "$SYSTEM_NAME" == "tuolumne" ]; then export KESSEL_DEPLOYMENT=${KESSEL_DEPLOYMENT:-/usr/workspace/xcap/oss/deployments/$DEPLOYMENT_VERSION/$SYSTEM_NAME} diff --git a/.kessel/workflows/cmake.py b/.kessel/workflows/cmake.py index 094ab9503e3..445cd0b22af 100644 --- a/.kessel/workflows/cmake.py +++ b/.kessel/workflows/cmake.py @@ -63,6 +63,7 @@ def test(self, args): """Testing""" self.exec(f""" pushd {self.build_dir} + source "$KESSEL_BUILD_ENV" if [[ -f sesame2spiner/sesame2spiner ]]; then echo "/usr/projects/data/eos/eos-developmental/Sn2162/v01/sn2162-v01.bin" > sesameFilesDir.txt sesame2spiner/sesame2spiner -s materials.sp5 ../sesame2spiner/examples/unit_tests/mats.dat diff --git a/CHANGELOG.md b/CHANGELOG.md index 064874acc79..db383d27344 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,10 @@ ### Fixed (Repair bugs, etc) - [[PR645]](https://github.com/lanl/singularity-eos/pull/645) Fix internal energy from density and pressure for JWL - [[PR63]](https://github.com/lanl/singularity-eos/pull/639) Fixed UnitSystem temperature bug. Temperature is now treated in the same as the time, mass, and length unit factors. - +- [[PR637]](https://github.com/lanl/singularity-eos/pull/637) Fix is_normal + ### Changed (changing behavior/API/variables/...) +- [[PR637]](https://github.com/lanl/singularity-eos/pull/637) For Kokkos 5/C++20, Python bindings now require context manager if run standalone - [[PR641]](https://github.com/lanl/singularity-eos/pull/641) The location of some header files located in `` have moved to ``. ### Infrastructure (changes irrelevant to downstream codes) diff --git a/CMakeLists.txt b/CMakeLists.txt index aaecf898839..98d39881118 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -247,6 +247,31 @@ if(SINGULARITY_BUILD_PYTHON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() +# 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_20) +endif() + +# checks if this is our build, or we've been imported via `add_subdirectory` NB: +# this should make the `option(SINGULARITY_SUBMODULE_MODE ...)` unnecessary +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + set(CMAKE_CXX_EXTENSIONS OFF) +else() + message( + STATUS + "Detected that `singularity-eos` is a subproject, will configure build in submodule mode" + ) + set(SINGULARITY_SUBMODULE_MODE ON) +endif() + +if(SINGULARITY_FORCE_SUBMODULE_MODE) + message(STATUS "Building as though project was a submodule.") + set(SINGULARITY_SUBMODULE_MODE ON) +endif() + # Use to determine if Eigen is used or not set(SINGULARITY_USE_EIGEN OFF @@ -625,8 +650,6 @@ target_compile_options( # `-Wclass-memaccess now default with -Wall but we explicitly # manage this ourselves in our serialization routines. $<$,$>:-Wno-class-memaccess> - # Disable finite-math-only in Debug builds to allow NaN/Inf checks (Intel compilers) - $<$,$,$>>:-fno-finite-math-only> ) if (SINGULARITY_STRICT_WARNINGS) target_compile_options(singularity-eos_Interface INTERFACE diff --git a/doc/sphinx/src/python.rst b/doc/sphinx/src/python.rst index aeb69f54485..94fecf45c69 100644 --- a/doc/sphinx/src/python.rst +++ b/doc/sphinx/src/python.rst @@ -20,20 +20,62 @@ Example :: - from singularity_eos import IdealGas + import singularity_eos as eos - # Parameters for ideal gas - gm1 = 0.6 - Cv = 2 - eos = IdealGas(gm1, Cv) - rho = ... - sie = ... - P = eos.PressureFromDensityInternalEnergy(rho, sie) - eos.Finalize() + with eos.context(): + # Parameters for ideal gas + gm1 = 0.6 + Cv = 2 + ideal_gas = eos.IdealGas(gm1, Cv) + rho = ... + sie = ... + P = ideal_gas.PressureFromDensityInternalEnergy(rho, sie) + ideal_gas.Finalize() + + # Leaving the context does not finalize the shared runtime. + eos.finalize() which should work for both scalar and vector (via numpy array) calls. A more elaborate example can be found in -``examples/get_sound_speed_press.py``. +``example/get_sound_speed_press.py``. + +Runtime Lifecycle +----------------- + +The module owns a single runtime context, returned by :func:`context`. Entering +the context calls :func:`initialize`, but leaving the context does **not** call +:func:`finalize`. This lets EOS objects and work created in separate ``with`` +blocks share the same runtime safely. + +When the bindings are built with Kokkos support, :func:`initialize` first +checks whether Kokkos is already initialized. If another library initialized +Kokkos, Singularity EOS uses that runtime but does not take ownership of it; +in that case, :func:`finalize` does not shut Kokkos down. If Singularity EOS +starts Kokkos, it records ownership and :func:`finalize` shuts it down. + +Calls to :func:`initialize` are idempotent while the runtime is active. Kokkos +cannot be initialized again after it has been finalized, so calling +:func:`initialize` after finalization raises an exception. When the bindings +are built without Kokkos, both lifecycle functions are no-ops. + +The runtime may also be managed explicitly without a context manager: + +:: + + import singularity_eos as eos + + eos.initialize() + try: + ideal_gas = eos.IdealGas(0.6, 2.0) + # Use ideal_gas here. + ideal_gas.Finalize() + finally: + eos.finalize() + +Call :func:`finalize` only after all Kokkos-dependent work and cleanup are +complete. An EOS object's ``Finalize()`` method releases resources owned by +that EOS object; it is separate from the module-level :func:`finalize`, which +manages the shared runtime. .. currentmodule:: singularity_eos @@ -41,6 +83,7 @@ Classes ------- List may not be complete. + * :class:`SingularityContext` * :class:`IdealGas` * :class:`Gruneisen` * :class:`JWL` @@ -79,10 +122,18 @@ along with any modifier arguments. ``Scaled(Shifted(IdealGas(gm1, Cv), shift), scale)`` will return a Python object that wraps the ``ScaledEOS>`` C++ type. -Class Reference ---------------- +API Reference +------------- List may not be complete. +.. autofunction:: initialize + +.. autofunction:: finalize + +.. autofunction:: context + +.. autoclass:: SingularityContext + .. autoclass:: EOSState :members: @@ -115,3 +166,4 @@ List may not be complete. .. autofunction:: UnitSystem +This file was made in part with generative AI. diff --git a/example/get_sound_speed_press.py b/example/get_sound_speed_press.py index 9d1591dfd4c..5ba7c3bd8b2 100644 --- a/example/get_sound_speed_press.py +++ b/example/get_sound_speed_press.py @@ -12,12 +12,18 @@ # publicly and display publicly, and to permit others to do so. #------------------------------------------------------------------------------ -from singularity_eos import IdealGas, thermalqs +# This file was made in part with generative AI. + +from singularity_eos import IdealGas, finalize, initialize, thermalqs import numpy as np import math SMALL = 1e-20 # to avoid dividing by zero +# Initialize the shared runtime before making EOS calls. This is idempotent and +# does not take ownership of Kokkos if another component initialized it first. +initialize() + def PressureSoundSpeedFromDensityEnergyDensity(rho, uu, eos, # inputs P, cs, Ncells # outputs ): @@ -123,3 +129,6 @@ def print_results(): # This usually only does anything if you're on device, but for GPU-data it # will call the appropriate destructor. eos1.Finalize() + +# Shut down the shared runtime if this module initialized it. +finalize() diff --git a/python/module.cpp b/python/module.cpp index 0472b2e28b2..4ec5b8c2da6 100644 --- a/python/module.cpp +++ b/python/module.cpp @@ -11,10 +11,58 @@ // prepare derivative works, distribute copies to the public, perform // publicly and display publicly, and to permit others to do so. //------------------------------------------------------------------------------ + +// This file was made in part with generative AI. + // clang-format off #include "module.hpp" PYBIND11_MODULE(singularity_eos, m) { + py::class_( + m, "SingularityContext", + "Module-owned context manager for the Singularity EOS runtime.\n\n" + "Use the singleton returned by ``singularity_eos.context()`` rather than " + "constructing this type directly. Entering the context initializes the " + "runtime, but leaving it does not finalize the runtime.") + .def("__enter__", + [](SingularityContext &self) -> SingularityContext & { + InitializeSingularity(); + return self; + }, + py::return_value_policy::reference_internal, + "Initialize the shared runtime and return this context." + ) + .def("__exit__", + [](SingularityContext &self, + py::object, + py::object, + py::object) { + (void)self; + return false; // do not suppress exceptions + }, + "Leave the context without finalizing the shared runtime." + ); + + m.def("context", + []() -> SingularityContext & { + return SingularityContext::Instance(); + }, + py::return_value_policy::reference, + "Return the module-owned Singularity EOS runtime context.\n\n" + "The same context is returned on every call. Entering it initializes " + "the runtime; leaving it does not finalize the runtime."); + m.def("initialize", &InitializeSingularity, + "Initialize the Singularity EOS runtime if necessary.\n\n" + "When built with Kokkos, this starts Kokkos only if it is not already " + "initialized. Calls are idempotent while Kokkos remains active. If " + "another component initialized Kokkos first, Singularity EOS does not " + "take ownership of its lifetime."); + m.def("finalize", &FinalizeSingularity, + "Finalize the Singularity EOS runtime when owned by this module.\n\n" + "When built with Kokkos, this finalizes Kokkos only if " + "singularity_eos.initialize() or singularity_eos.context() originally " + "started it. Kokkos cannot be initialized again after finalization."); + py::class_(m, "EOSState") .def(py::init()) .def_readwrite("density", &EOSState::density) @@ -157,5 +205,5 @@ PYBIND11_MODULE(singularity_eos, m) { eos_units.attr("ThermalUnits") = eos_units_init::thermal_units_init_tag; eos_units.attr("LengthTimeUnits") = eos_units_init::length_time_units_init_tag; - m.doc() = "Singularity EOS Python Bindings"; + m.doc() = "Python bindings for Singularity EOS."; } diff --git a/python/module.hpp b/python/module.hpp index 834292f13e9..06e93174ded 100644 --- a/python/module.hpp +++ b/python/module.hpp @@ -11,11 +11,15 @@ // prepare derivative works, distribute copies to the public, perform // publicly and display publicly, and to permit others to do so. //------------------------------------------------------------------------------ + +// This file was made in part with generative AI. + // clang-format off #include #include #include #include +#include #include #include @@ -100,6 +104,59 @@ class PyArrayHelper { const std::size_t stride_; }; +class SingularityContext { +public: + SingularityContext(const SingularityContext&) = delete; + SingularityContext& operator=(const SingularityContext&) = delete; + + SingularityContext(SingularityContext&&) = delete; + SingularityContext& operator=(SingularityContext&&) = delete; + + static SingularityContext &Instance() { + static SingularityContext context; + return context; + } + + void Initialize() { +#if defined(PORTABILITY_STRATEGY_KOKKOS) + if (Kokkos::is_finalized()) { + throw std::runtime_error("Kokkos has already been finalized " + "and cannot be initialized again."); + } + + if (!Kokkos::is_initialized()) { + Kokkos::initialize(); + owns_kokkos_ = true; + } +#endif // defined(PORTABILITY_STRATEGY_KOKKOS) + } + + void Finalize() { +#if defined(PORTABILITY_STRATEGY_KOKKOS) + if (owns_kokkos_ && Kokkos::is_initialized() && !Kokkos::is_finalized()) { + Kokkos::finalize(); + } +#endif // defined(PORTABILITY_STRATEGY_KOKKOS) + owns_kokkos_ = false; + } + + ~SingularityContext() { + Finalize(); + } + +private: + SingularityContext() = default; + + bool owns_kokkos_ = false; +}; + +inline void InitializeSingularity() { + SingularityContext::Instance().Initialize(); +} + +inline void FinalizeSingularity() { + SingularityContext::Instance().Finalize(); +} // so far didn't find a good way of working with template member function pointers // to generalize this without the preprocessor. diff --git a/singularity-utils/singularity-utils/error_utils.hpp b/singularity-utils/singularity-utils/error_utils.hpp index fcf306ec3c2..c4687d5fc18 100644 --- a/singularity-utils/singularity-utils/error_utils.hpp +++ b/singularity-utils/singularity-utils/error_utils.hpp @@ -12,27 +12,32 @@ // publicly and display publicly, and to permit others to do so. //------------------------------------------------------------------------------ +// This file created in part with the assistance of generative AI #ifndef SINGULARITY_UTILS_ERROR_UTILS_HPP_ #define SINGULARITY_UTILS_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 +57,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 +85,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 9b40ff1972f..b3ccc7c498d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -38,6 +38,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 @@ -46,6 +47,12 @@ add_executable( test_generic_indxer.cpp ) +# We want to test things like quiet NaN so finite math/fast-math needs to be disabled. +target_compile_options(eos_infrastructure_tests PRIVATE + $<$:-fno-fast-math;-fno-finite-math-only> + $<$:--ftz=false;--prec-div=true;--prec-sqrt=true> +) + add_executable( eos_tabulated_unit_tests catch2_define.cpp diff --git a/test/compare_on_data.cpp b/test/compare_on_data.cpp index 12f4f1f8208..790acb9bd8b 100644 --- a/test/compare_on_data.cpp +++ b/test/compare_on_data.cpp @@ -55,7 +55,7 @@ using Spiner::RegularGrid1D; #ifdef PORTABILITY_STRATEGY_KOKKOS using RView = Kokkos::View; -using RMirror = typename RView::HostMirror; +using RMirror = typename RView::host_mirror_type; #endif struct Data { diff --git a/test/compare_on_dataset.cpp b/test/compare_on_dataset.cpp index f100f5cc052..a2f0ceb1bbf 100644 --- a/test/compare_on_dataset.cpp +++ b/test/compare_on_dataset.cpp @@ -57,7 +57,7 @@ using BEOS = SpinerEOSDependsRhoT; #ifdef PORTABILITY_STRATEGY_KOKKOS using RView = Kokkos::View; -using RMirror = typename RView::HostMirror; +using RMirror = typename RView::host_mirror_type; using KokkosAtomic = Kokkos::MemoryTraits; #endif diff --git a/test/compare_to_eospac.cpp b/test/compare_to_eospac.cpp index bfe3cbc3063..f2bffbe008b 100644 --- a/test/compare_to_eospac.cpp +++ b/test/compare_to_eospac.cpp @@ -160,8 +160,7 @@ int main(int argc, char *argv[]) { #ifdef PORTABILITY_STRATEGY_KOKKOS using RView = Kokkos::View; RView pressSpinerDView("pressSpinerDevice", nFineRho * nFineT); - typename RView::HostMirror pressSpinerHView = - Kokkos::create_mirror_view(pressSpinerDView); + auto pressSpinerHView = Kokkos::create_mirror_view(pressSpinerDView); DataBox pressSpiner_d(pressSpinerDView.data(), nFineRho, nFineT); DataBox pressSpiner_hm(pressSpinerHView.data(), nFineRho, nFineT); #else @@ -181,8 +180,8 @@ int main(int argc, char *argv[]) { #ifdef PORTABILITY_STRATEGY_KOKKOS RView tempSpinerDView("tempSpinerDevice", nFineRho * nFineT); RView tempSpinerDViewE("tempSpinerSieDevice", nFineRho * nFineT); - RView::HostMirror tempSpinerHView = Kokkos::create_mirror_view(tempSpinerDView); - RView::HostMirror tempSpinerHViewE = Kokkos::create_mirror_view(tempSpinerDViewE); + auto tempSpinerHView = Kokkos::create_mirror_view(tempSpinerDView); + auto tempSpinerHViewE = Kokkos::create_mirror_view(tempSpinerDViewE); DataBox tempSpiner_d(tempSpinerDView.data(), nFineRho, nFineT); DataBox tempSpiner_hm(tempSpinerHView.data(), nFineRho, nFineT); DataBox tempSpinerE_d(tempSpinerDViewE.data(), nFineRho, nFineT); diff --git a/test/profile_eos.cpp b/test/profile_eos.cpp index 96980e872ce..73167a5b442 100644 --- a/test/profile_eos.cpp +++ b/test/profile_eos.cpp @@ -46,7 +46,7 @@ using RegularGrid1D = Spiner::RegularGrid1D; #ifdef PORTABILITY_STRATEGY_KOKKOS using RView = Kokkos::View; -using RMirror = typename RView::HostMirror; +using RMirror = typename RView::host_mirror_type; #endif constexpr Real RHO_MIN = 1e-2; // g/cm^3 diff --git a/test/profile_eospac.cpp b/test/profile_eospac.cpp index 4a051d87fbe..481b5bc9b08 100644 --- a/test/profile_eospac.cpp +++ b/test/profile_eospac.cpp @@ -49,7 +49,7 @@ using RegularGrid1D = Spiner::RegularGrid1D; #ifdef PORTABILITY_STRATEGY_KOKKOS using RView = Kokkos::View; -using RMirror = typename RView::HostMirror; +using RMirror = typename RView::host_mirror_type; #endif constexpr Real RHO_MIN = 1e-2; // g/cm^3 diff --git a/test/python_bindings.py b/test/python_bindings.py index c900f888a8e..93c02d4c34f 100644 --- a/test/python_bindings.py +++ b/test/python_bindings.py @@ -11,12 +11,18 @@ # prepare derivative works, distribute copies to the public, perform # publicly and display publicly, and to permit others to do so. #------------------------------------------------------------------------------ + +# This file was made in part with generative AI. + import math import unittest import singularity_eos import numpy as np from numpy.testing import assert_allclose +def setUpModule(): + singularity_eos.initialize() + class EOSTestBase(object): def assertIsClose(self, a, b, eps=5e-2): rel = abs(b - a) / (abs(a + b) + 1e-20) @@ -53,6 +59,12 @@ def assertEqualEOS(self, test_e, ref_e): self.assertIsClose(test_e.MinimumTemperature(), ref_e.MinimumTemperature(), 1.e-15) class EOS(unittest.TestCase): + def testContextSingleton(self): + context = singularity_eos.context() + self.assertIs(context, singularity_eos.context()) + with context as entered_context: + self.assertIs(entered_context, context) + def testConstants(self): from singularity_eos import thermalqs self.assertEqual(thermalqs.all_values, thermalqs.none | @@ -693,4 +705,5 @@ def test_particle_vel_and_hugoniot(self): self.assertIsClose(pres, true_pres, 1e-12) if __name__ == "__main__": - unittest.main() + with singularity_eos.context(): + unittest.main() diff --git a/test/test_eos_modifiers.cpp b/test/test_eos_modifiers.cpp index c4e99d06ccb..6eae711e4b3 100644 --- a/test/test_eos_modifiers.cpp +++ b/test/test_eos_modifiers.cpp @@ -95,7 +95,7 @@ using variadic_utils::transform_variadic_list; static constexpr const auto full_eos_list = tl{}; // modifiers that get applied to all eos's -static constexpr const auto apply_to_all = al{}; +// static constexpr const auto apply_to_all = al{}; // variadic list of eos's with shifted or scaled modifiers static constexpr const auto shifted = transform_variadic_list(full_eos_list, al{}); diff --git a/test/test_eos_tabulated.cpp b/test/test_eos_tabulated.cpp index 352aaff16d9..b3d457f6e05 100644 --- a/test/test_eos_tabulated.cpp +++ b/test/test_eos_tabulated.cpp @@ -50,9 +50,6 @@ namespace thermalqs = singularity::thermalqs; using singularity::variadic_utils::np; const std::string eosName = "../materials.sp5"; -const std::string airName = "air"; -const std::string steelName = "stainless steel 347"; -const std::string tinName = "tin"; #ifdef SPINER_USE_HDF #ifdef SINGULARITY_TEST_SESAME diff --git a/test/test_error_utils.cpp b/test/test_error_utils.cpp new file mode 100644 index 00000000000..93dc3924931 --- /dev/null +++ b/test/test_error_utils.cpp @@ -0,0 +1,117 @@ +//------------------------------------------------------------------------------ +// © 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 + +#if defined(__FINITE_MATH_ONLY__) && (__FINITE_MATH_ONLY__ > 0) +#define SG_FINITE_MATH_ONLY 1 +#else +#define SG_FINITE_MATH_ONLY 0 +#endif // finite math only + +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{}; + constexpr bool NANS_DISABLED = + SG_FINITE_MATH_ONLY || (limits::quiet_NaN() == limits::quiet_NaN()); + + 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())); + } + + 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)); + + if constexpr (NANS_DISABLED) { + INFO("Compiler does not honor IEEE NaN comparisons on host"); + } else { + REQUIRE_FALSE(condition(limits::quiet_NaN())); + if constexpr (limits::has_infinity) { + REQUIRE_FALSE(condition(limits::infinity())); + REQUIRE_FALSE(condition(-limits::infinity())); + } + } +} + +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); + + // TODO(JMM): For whatever reason, quiet NaNs do not behave on + // nvidia GPUs. It seems like nvidia does some NaN optimization I + // can't turn off. + int nwrong = 0; + portableReduce( + "Check error_utils::is_normal_or_zero on device, no NaNs", 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::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/test/test_indexable_types.cpp b/test/test_indexable_types.cpp index 390a9e22726..343728adfd6 100644 --- a/test/test_indexable_types.cpp +++ b/test/test_indexable_types.cpp @@ -34,10 +34,15 @@ class ManualLambda_t { public: constexpr static size_t length = 3; ManualLambda_t() = default; + PORTABLE_FORCEINLINE_FUNCTION const Real &operator[](const std::size_t idx) const { return data_[idx]; } + PORTABLE_FORCEINLINE_FUNCTION Real &operator[](const std::size_t idx) { return data_[idx]; } + PORTABLE_FORCEINLINE_FUNCTION Real &operator[](const MeanIonizationState &zbar) { return data_[0]; } + PORTABLE_FORCEINLINE_FUNCTION Real &operator[](const ElectronFraction &Ye) { return data_[1]; } + PORTABLE_FORCEINLINE_FUNCTION Real &operator[](const LogDensity &lRho) { return data_[2]; } private: 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