diff --git a/CHANGELOG.md b/CHANGELOG.md index a920d422aab..47253aa2839 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Current develop ### Added (new features/APIs/variables/...) +- [[PR410]](https://github.com/lanl/singularity-eos/pull/410) Enable serialization and de-serialization - [[PR330]](https://github.com/lanl/singularity-eos/pull/330) Piecewise grids for Spiner EOS. ### Fixed (Repair bugs, etc) diff --git a/CMakeLists.txt b/CMakeLists.txt index 85301d86358..5b1ebdecd17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,9 @@ cmake_dependent_option( option(SINGULARITY_USE_FORTRAN "Enable fortran bindings" ON) option(SINGULARITY_USE_KOKKOS "Use Kokkos for portability" OFF) option(SINGULARITY_USE_EOSPAC "Enable eospac backend" OFF) +option(SINGULARITY_EOSPAC_ENABLE_SHMEM + "Support shared memory with EOSPAC backend. Requires EOSPAC version 6.5.7." + OFF) # TODO This should be dependent option (or fortran option) option(SINGULARITY_BUILD_CLOSURE "Mixed Cell Closure" ON) @@ -271,6 +274,9 @@ endif() if(SINGULARITY_USE_SPINER_WITH_HDF5) target_compile_definitions(singularity-eos_Interface INTERFACE SINGULARITY_USE_SPINER_WITH_HDF5) endif() +if (SINGULARITY_USE_EOSPAC AND SINGULARITY_EOSPAC_ENABLE_SHMEM) + target_compile_definitions(singularity-eos_Interface INTERFACE SINGULARITY_EOSPAC_ENABLE_SHARED_MEMORY) +endif() # ------------------------------------------------------------------------------# # Handle dependencies diff --git a/doc/sphinx/src/building.rst b/doc/sphinx/src/building.rst index a716f931ef3..d5b4839b4ca 100644 --- a/doc/sphinx/src/building.rst +++ b/doc/sphinx/src/building.rst @@ -105,6 +105,7 @@ The main CMake options to configure building are in the following table: ``SINGULARITY_USE_FORTRAN`` ON Enable Fortran API for equation of state. ``SINGULARITY_USE_KOKKOS`` OFF Uses Kokkos as the portability backend. Currently only Kokkos is supported for GPUs. ``SINGULARITY_USE_EOSPAC`` OFF Link against EOSPAC. Needed for sesame2spiner and some tests. + ``SINGULARITY_EOSPAC_ENABLE_SHMEM`` OFF Enable shared memory support in EOSPAC backend. ``SINGULARITY_BUILD_CLOSURE`` OFF Build the mixed cell closure models ``SINGULARITY_BUILD_TESTS`` OFF Build test infrastructure. ``SINGULARITY_BUILD_PYTHON`` OFF Build Python bindings. diff --git a/doc/sphinx/src/using-eos.rst b/doc/sphinx/src/using-eos.rst index adabdbeb33b..fe570ad6259 100644 --- a/doc/sphinx/src/using-eos.rst +++ b/doc/sphinx/src/using-eos.rst @@ -19,13 +19,14 @@ The parallelism model ---------------------- For the most part, ``singularity-eos`` tries to be agnostic to how you -parallelize your code on-node. (It knows nothing at all about -distributed memory parallelism.) An ``EOS`` object can be copied into -any parallel code block by value (see below) and scalar calls do not -attempt any internal multi-threading, meaning ``EOS`` objects are not -thread-safe, but are compatible with thread safety, assuming the user -calls them appropriately. The main complication is ``lambda`` arrays, -which are discussed below. +parallelize your code on-node. It knows nothing at all about +distributed memory parallelism, with one exception, discussed +below. An ``EOS`` object can be copied into any parallel code block by +value (see below) and scalar calls do not attempt any internal +multi-threading, meaning ``EOS`` objects are not thread-safe, but are +compatible with thread safety, assuming the user calls them +appropriately. The main complication is ``lambda`` arrays, which are +discussed below. The vector ``EOS`` method overloads are a bit different. These are thread-parallel operations launched by ``singularity-EOS``. They run @@ -39,6 +40,271 @@ A more generic version of the vector calls exists in the ``Evaluate`` method, which allows the user to specify arbitrary parallel dispatch models by writing their own loops. See the relevant section below. +Serialization and shared memory +-------------------------------- + +While ``singularity-eos`` makes a best effort to be agnostic to +parallelism, it exposes several methods that are useful in a +distributed memory environment. In particular, there are two use-cases +the library seeks to support: + +#. To avoid stressing a filesystem, it may desirable to load a table from one thread (e.g., MPI rank) and broadcast this data to all other ranks. +#. To save memory it may be desirable to place tabulated data, which is read-only after it has been loaded from file, into shared memory on a given node, even if all other data is thread local in a distributed-memory environment. This is possible via, e.g., `MPI Windows`_. + +Therefore ``singularity-eos`` exposes several methods that can be used +in this context. The function + +.. cpp:function:: std::size_t EOS::SerializedSizeInBytes() const; + +returns the amount of memory required in bytes to hold a serialized +EOS object. The return value will depend on the underlying equation of +state model currently contained in the object. The function + +.. cpp:function:: std::size_t EOS::SharedMemorySizeInBytes() const; + +returns the amount of data (in bytes) that a given object can place into shared memory. Again, the return value depends on the model the object currently represents. + +.. note:: + + Many models may not be able to utilize shared memory at all. This + holds for most analytic models, for example. The ``EOSPAC`` backend + will only utilize shared memory if the ``EOSPAC`` version is sufficiently recent + to support it and if ``singularity-eos`` is built with serialization + support for ``EOSPAC`` (enabled with + ``-DSINGULARITY_EOSPAC_ENABLE_SHMEM=ON``). + +The function + +.. cpp:function:: std::size_t EOS::Serialize(char *dst); + +fills the ``dst`` pointer with the memory required for serialization +and returns the number of bytes written to ``dst``. The function + +.. cpp:function:: std::pair EOS::Serialize(); + +allocates a ``char*`` pointer to contain serialized data and fills +it. + +.. warning:: + + Serialization and de-serialization may only be performed on objects + that live in host memory, before you have called + ``eos.GetOnDevice()``. Attempting to serialize device-initialized + objects is undefined behavior, but will likely result in a + segmentation fault. + +The pair is the pointer and its size. The function + +.. code-block:: cpp + + std::size_t EOS::DeSerialize(char *src, + const SharedMemSettings &stngs = DEFAULT_SHMEM_STNGS) + +Sets an EOS object based on the serialized representation contained in +``src``. It returns the number of bytes read from ``src``. Optionally, +``DeSerialize`` may also write the data that can be shared to a +pointer contained in ``SharedMemSettings``. If you do this, you must +pass this pointer in, but designate only one thread per shared memory +domain (frequently a node or socket) to actually write to this +data. ``SharedMemSettings`` is a struct containing a ``data`` pointer +and a ``is_domain_root`` boolean: + +.. code-block:: cpp + + struct SharedMemSettings { + SharedMemSettings(); + SharedMemSettings(char *data_, bool is_domain_root_) + : data(data_), is_domain_root(is_domain_root_) {} + char *data = nullptr; // defaults + bool is_domain_root = false; + }; + +The ``data`` pointer should point to a shared memory allocation. The +``is_domain_root`` boolean should be true for exactly one thread per +shared memory domain. + +For example you might call ``DeSerialize`` as + +.. code-block:: cpp + + std::size_t read_size = eos.DeSerialize(packed_data, + singularity::SharedMemSettings(shared_data, + my_rank % NTHREADS == 0)); + assert(read_size == write_size); // for safety + +.. warning:: + + Note that for equation of state models that have dynamically + allocated memory, ``singularity-eos`` reserves the right to point + directly at data in ``src``, so it **cannot** be freed until you + would call ``eos.Finalize()``. If the ``SharedMemSettings`` are + utilized to request data be written to a shared memory pointer, + however, you can free the ``src`` pointer, so long as you don't free + the shared memory pointer. + +Putting everything together, a full sequence with MPI might look like this: + +.. code-block:: cpp + + singularity::EOS eos; + std::size_t packed_size, shared_size; + char *packed_data; + if (rank == 0) { // load eos object + eos = singularity::StellarCollapse(filename); + packed_size = eos.SerializedSizeInBytes(); + shared_size = eos.SharedMemorySizeInBytes(); + } + + // Send sizes + MPI_Bcast(&packed_size, 1, MPI_UNSIGNED_LONG, 0, MPI_COMM_WORLD); + MPI_Bcast(&spacked_size, 1, MPI_UNSIGNED_LONG, 0, MPI_COMM_WORLD); + + // Allocate data needed + packed_data = (char*)malloc(packed_size); + if (rank == 0) { + eos.Serialize(packed_data); + eos.Finalize(); // Clean up this EOS object so it can be reused. + } + MPI_Bcast(packed_data, packed_size, MPI_BYTE, 0, MPI_COMM_WORLD); + + // the default doesn't do shared memory. + // we will change it below if shared memory is enabled. + singularity::SharedMemSettings settings = singularity::DEFAULT_SHMEM_STNGS; + + char *shared_data; + char *mpi_base_pointer; + int mpi_unit; + MPI_Aint query_size; + MPI_Win window; + MPI_Comm shared_memory_comm; + int island_rank, island_size; // rank in, size of shared memory region + if (use_mpi_shared_memory) { + // Generate shared memory comms + MPI_Comm_split_type(MPI_COMM_WORLD, MPI_COMM_TYPE_SHARED, 0, MPI_INFO_NULL, &shared_memory_comm); + // rank on a region that shares memory + MPI_Comm_rank(shared_memory_comm, &island_rank); + // size on a region that shares memory + MPI_COMM_size(shared_memory_comm, &island_size); + + // Create the MPI shared memory object and get a pointer to shared data + // this allocation is a collective and must be called on every rank. + // the total size of the allocation is the sum over ranks in the shared memory comm + // of requested memory. So it's valid to request all you want on rank 0 and nothing + // on the remaining ranks. + MPI_Win_allocate_shared((island_rank == 0) ? shared_size : 0, + 1, MPI_INFO_NULL, shared_memory_comm, &mpi_base_pointer, + &window); + // This gets a pointer to the shared memory allocation valid in local address space + // on every rank + MPI_Win_shared_query(window, MPI_PROC_NULL, &query_size, &mpi_unit, &shared_data); + // Mutex for MPI window. Writing to shared memory currently allowed. + MPI_Win_lock_all(MPI_MODE_NOCHECK, window); + // Set SharedMemSettings + settings.data = shared_data; + settings.is_domain_root = (island_rank == 0); + } + eos.DeSerialize(packed_data, settings); + if (use_mpi_shared_memory) { + MPI_Win_unlock_all(window); // Writing to shared memory disabled. + MPI_Barrier(shared_memory_comm); + free(packed_data); + } + +In the case where many EOS objects may be active at once, you can +combine serialization and comm steps. You may wish to, for example, +have a single pointer containing all serialized EOS's. Same for the +shared memory. ``singularity-eos`` provides machinery to do this in +the ``singularity-eos/base/serialization_utils.hpp`` header. This +provides a helper struct, ``BulkSerializer``: + +.. code-block:: cpp + + template + singularity::BulkSerializer + +which may be initialized by a collection of ``EOS`` objects or by +simply assigning (or constructing) its member field, ``eos_objects`` +appropriately. An example ``Container_t`` might be +``std::vector``. A specialization for ``vector`` is provided as +``VectorSerializer``. The ``Resizer_t`` is a functor that knows how to +resize a collection. For example, the ``MemberResizor`` functor used +for ``std::vector``s + +.. code-block:: cpp + + struct MemberResizer { + template + void operator()(Collection_t &collection, std::size_t count) { + collection.resize(count); + } + }; + +which will work for any ``stl`` container with a ``resize`` method. + +The ``BulkSerializer`` provides all the above-described serialization +functions for ``EOS`` objects: ``SerializedSizeInBytes``, +``SharedMemorySizeInBytes``, ``Serialize``, and ``DeSerialize``, but +it operates on all ``EOS`` objects contained in the container it +wraps, not just one. Example usage might look like this: + +.. code-block:: cpp + + int packed_size, shared_size; + singularity::VectorSerializer serializer; + if (rank == 0) { // load eos object + // Code to initialize a bunch of EOS objects into a std::vector + /* + Initialization code goes here + */ + serializer = singularity::VectorSerializer(eos_vec); + packed_size = serializer.SerializedSizeInBytes(); + shared_size = serializer.SharedMemorySizeInBytes(); + } + + // Send sizes + MPI_Bcast(&packed_size, 1, MPI_UNSIGNED_LONG, 0, MPI_COMM_WORLD); + MPI_Bcast(&packed_size, 1, MPI_UNSIGNED_LONG, 0, MPI_COMM_WORLD); + + // Allocate data needed + packed_data = (char*)malloc(packed_size); + if (rank == 0) { + serializer.Serialize(packed_data); + serializer.Finalize(); // Clean up all EOSs owned by the serializer + } + MPI_Bcast(packed_data, packed_size, MPI_BYTE, 0, MPI_COMM_WORLD); + + singularity::SharedMemSettings settings = singularity::DEFAULT_SHMEM_STNGS; + // same MPI declarations as above + if (use_mpi_shared_memory) { + // same MPI code as above including setting the settings + settings.data = shared_data; + settings.is_domain_root = (island_rank == 0); + } + singularity::VectorSerializer deserializer; + deserializer.DeSerialize(packed_data, settings); + if (use_mpi_shared_memory) { + // same MPI code as above + } + // extract each individual EOS and do something with it + std::vector eos_host_vec = deserializer.eos_objects; + // get on device if you want + for (auto EOS : eos_host_vec) { + EOS eos_device = eos.GetOnDevice(); + // ... + } + +It is also possible to (with care) mix serializers... i.e., you might +serialize with a ``VectorSerializer`` and de-serialize with a +different container, as all that is required is that a container have +a ``size``, provide iterators, and be capable of being resized. + +.. warning:: + + Since EOSPAC is a library, DeSerialization is destructive for EOSPAC + and may have side-effects. + +.. _`MPI Windows`: https://www.mpi-forum.org/docs/mpi-4.1/mpi41-report/node311.htm + .. _variant section: Variants @@ -444,7 +710,7 @@ unmodified EOS model, call .. cpp:function:: auto GetUnmodifiedObject(); The return value here will be either the type of the ``EOS`` variant -type or the unmodified model (for example ``IdealGas``) or, depending +type or the unmodified model (for example ``IdealGas``), depending on whether this method was callled within a variant or on a standalone model outside a variant. @@ -552,6 +818,18 @@ might look something like this: .. _eos methods reference section: +CheckParams +------------ + +You may check whether or not an equation of state object is +constructed self-consistently and ready for use by calling + +.. cpp:function:: void CheckParams() const; + +which raise an error and/or print an equation of state specific error +message if something has gone wrong. Most EOS constructors and ways of +building an EOS call ``CheckParams`` by default. + Equation of State Methods Reference ------------------------------------ diff --git a/sesame2spiner/io_eospac.hpp b/sesame2spiner/io_eospac.hpp index dcbfce18eb7..454d44ec0f5 100644 --- a/sesame2spiner/io_eospac.hpp +++ b/sesame2spiner/io_eospac.hpp @@ -27,7 +27,7 @@ #include #include -#include +#include #include #include diff --git a/singularity-eos/CMakeLists.txt b/singularity-eos/CMakeLists.txt index 0096902e091..669d129b433 100644 --- a/singularity-eos/CMakeLists.txt +++ b/singularity-eos/CMakeLists.txt @@ -27,7 +27,8 @@ register_headers( base/fast-math/logs.hpp base/robust_utils.hpp base/root-finding-1d/root_finding.hpp - base/spiner_table_bounds.hpp + base/serialization_utils.hpp + base/spiner_table_utils.hpp base/variadic_utils.hpp base/math_utils.hpp base/constants.hpp diff --git a/singularity-eos/base/constants.hpp b/singularity-eos/base/constants.hpp index 303361534a3..73934daf31e 100644 --- a/singularity-eos/base/constants.hpp +++ b/singularity-eos/base/constants.hpp @@ -32,11 +32,21 @@ constexpr unsigned long all_values = (1 << 7) - 1; } // namespace thermalqs constexpr size_t MAX_NUM_LAMBDAS = 3; -enum class DataStatus { Deallocated = 0, OnDevice = 1, OnHost = 2 }; +enum class DataStatus { Deallocated = 0, OnDevice = 1, OnHost = 2, UnManaged = 3 }; enum class TableStatus { OnTable = 0, OffBottom = 1, OffTop = 2 }; constexpr Real ROOM_TEMPERATURE = 293; // K constexpr Real ATMOSPHERIC_PRESSURE = 1e6; +struct SharedMemSettings { + SharedMemSettings() = default; + SharedMemSettings(char *data_, bool is_domain_root_) + : data(data_), is_domain_root(is_domain_root_) {} + bool CopyNeeded() const { return (data != nullptr) && is_domain_root; } + char *data = nullptr; + bool is_domain_root = false; // default true or false? +}; +const SharedMemSettings DEFAULT_SHMEM_STNGS = SharedMemSettings(); + } // namespace singularity #endif // SINGULARITY_EOS_BASE_CONSTANTS_HPP_ diff --git a/singularity-eos/base/serialization_utils.hpp b/singularity-eos/base/serialization_utils.hpp new file mode 100644 index 00000000000..2c3028d54f5 --- /dev/null +++ b/singularity-eos/base/serialization_utils.hpp @@ -0,0 +1,108 @@ +//------------------------------------------------------------------------------ +// © 2024. 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. +//------------------------------------------------------------------------------ + +#ifndef SINGULARITY_EOS_BASE_SERIALIZATION_UTILS_ +#define SINGULARITY_EOS_BASE_SERIALIZATION_UTILS_ + +#include +#include +#include + +#include +#include + +#include + +namespace singularity { + +struct MemberResizer { + template + void operator()(Collection_t &collection, std::size_t count) { + collection.resize(count); + } +}; + +template +struct BulkSerializer { + Container_t eos_objects; + + BulkSerializer() = default; + BulkSerializer(const Container_t &eos_objects_) : eos_objects(eos_objects_) {} + void Finalize() { + for (auto &eos : eos_objects) { + eos.Finalize(); + } + } + const auto Size() const { return eos_objects.size(); } + std::size_t SerializedSizeInBytes() const { + return sizeof(std::size_t) + accumulate_([](std::size_t tot, const auto &eos) { + return tot + eos.SerializedSizeInBytes(); + }); + } + std::size_t SharedMemorySizeInBytes() const { + return accumulate_([](std::size_t tot, const auto &eos) { + return tot + eos.SharedMemorySizeInBytes(); + }); + } + std::size_t Serialize(char *dst) { + std::size_t vsize = eos_objects.size(); + memcpy(dst, &vsize, sizeof(std::size_t)); + std::size_t offst = sizeof(std::size_t); + for (auto &eos : eos_objects) { + offst += eos.Serialize(dst + offst); + } + PORTABLE_ALWAYS_REQUIRE(offst == SerializedSizeInBytes(), "Serialization failed!"); + return offst; + } + auto Serialize() { + std::size_t size = SerializedSizeInBytes(); + char *dst = (char *)malloc(size); + std::size_t new_size = Serialize(dst); + PORTABLE_ALWAYS_REQUIRE(size == new_size, "Serialization failed!"); + return std::make_pair(size, dst); + } + std::size_t DeSerialize(char *src, + const SharedMemSettings &stngs = DEFAULT_SHMEM_STNGS) { + std::size_t vsize; + memcpy(&vsize, src, sizeof(std::size_t)); + Resizer_t()(eos_objects, vsize); + SharedMemSettings stngs_loc = stngs; // non-const + std::size_t offst = sizeof(std::size_t); + std::size_t shared_offst = 0; + for (auto &eos : eos_objects) { + offst += eos.DeSerialize(src + offst, stngs_loc); + // needs to be after eos.Deserialize so type/size are unpacked + std::size_t shared_bytes = eos.SharedMemorySizeInBytes(); + stngs_loc.data += shared_bytes; + shared_offst += shared_bytes; + } + PORTABLE_ALWAYS_REQUIRE(offst == SerializedSizeInBytes(), "De-Serialization failed!"); + PORTABLE_ALWAYS_REQUIRE(shared_offst == SharedMemorySizeInBytes(), + "De-Serialization into shared memory failed!"); + return offst; + } + + private: + template + std::size_t accumulate_(const F &f) const { + std::size_t init = 0; + return std::accumulate(eos_objects.cbegin(), eos_objects.cend(), init, f); + } +}; + +template +using VectorSerializer = BulkSerializer, MemberResizer>; + +} // namespace singularity +#endif // SINGULARITY_EOS_BASE_SERIALIZATION_UTILS_ diff --git a/singularity-eos/base/spiner_table_bounds.hpp b/singularity-eos/base/spiner_table_utils.hpp similarity index 80% rename from singularity-eos/base/spiner_table_bounds.hpp rename to singularity-eos/base/spiner_table_utils.hpp index 80d3dbd0808..06b167e244e 100644 --- a/singularity-eos/base/spiner_table_bounds.hpp +++ b/singularity-eos/base/spiner_table_utils.hpp @@ -12,8 +12,8 @@ // publicly and display publicly, and to permit others to do so. //------------------------------------------------------------------------------ -#ifndef SINGULARITY_EOS_BASE_TABLE_BOUNDS_HPP_ -#define SINGULARITY_EOS_BASE_TABLE_BOUNDS_HPP_ +#ifndef SINGULARITY_EOS_BASE_TABLE_UTILS_HPP_ +#define SINGULARITY_EOS_BASE_TABLE_UTILS_HPP_ #ifdef SINGULARITY_USE_SPINER #include @@ -25,7 +25,10 @@ #include #include +#include #include + +#include #include namespace singularity { @@ -279,8 +282,80 @@ class Bounds { private: Real linmin_, linmax_; }; + +// JMM: Making this a struct with static methods, rather than a +// namespace, saves a few "friend" declarations. Broadly these methods +// rely on the EOS providing a `GetDataBoxPointers_` method and +// declaring this class a friend. Doing so lets us loop over each data +// box member of a spiner-based EOS ond operate on it. For example, by +// calling `finalize` or `getOnDevice`. The intent here is that the +// static methods of `SpinerTricks` are called from class methods of +// spiner-based EOS's. +template +struct SpinerTricks { + static auto GetOnDevice(EOS *peos_h) { + // trivially copy all but dynamic memory + EOS eos_d = *peos_h; + auto pdbs_d = eos_d.GetDataBoxPointers_(); + auto pdbs_h = peos_h->GetDataBoxPointers_(); + int idb = 0; + for (auto *pdb_d : pdbs_d) { + auto *pdb_h = pdbs_h[idb++]; + *pdb_d = pdb_h->getOnDevice(); + } + // set memory status + eos_d.memoryStatus_ = DataStatus::OnDevice; + return eos_d; + } + static void Finalize(EOS *peos) { + if (peos->memoryStatus_ != DataStatus::UnManaged) { + for (auto *pdb : peos->GetDataBoxPointers_()) { + pdb->finalize(); + } + } + peos->memoryStatus_ = DataStatus::Deallocated; + } + static std::size_t DynamicMemorySizeInBytes(const EOS *peos) { + std::size_t out = 0; + for (const auto *pdb : peos->GetDataBoxPointers_()) { + out += pdb->sizeBytes(); + } + return out; + } + static std::size_t DumpDynamicMemory(char *dst, const EOS *peos) { + std::size_t offst = 0; + for (const auto *pdb : peos->GetDataBoxPointers_()) { + std::size_t size = pdb->sizeBytes(); + memcpy(dst + offst, pdb->data(), size); + offst += size; + } + return offst; + } + static std::size_t SetDynamicMemory(char *src, EOS *peos) { + std::size_t offst = 0; + for (auto *pdb : peos->GetDataBoxPointers_()) { + offst += pdb->setPointer(src + offst); + } + peos->memoryStatus_ = DataStatus::UnManaged; + return offst; + } + static bool DataBoxesPointToSameMemory(const EOS &eos_a, const EOS &eos_b) { + const auto pdbs_a = eos_a.GetDataBoxPointers_(); + const auto pdbs_b = eos_b.GetDataBoxPointers_(); + int idb = 0; + for (const auto *pdb_a : pdbs_a) { + const auto &db_a = *pdb_a; + const auto &db_b = *(pdbs_b[idb++]); + if (&(db_a(0)) != &(db_b(0))) return false; + } + return true; + } + static bool DataBoxesPointToDifferentMemory(const EOS &eos_a, const EOS &eos_b) { + return !DataBoxesPointToSameMemory(eos_a, eos_b); + } +}; } // namespace table_utils } // namespace singularity #endif // SINGULARITY_USE_SPINER -#endif // SINGULARITY_EOS_BASE_TABLE_BOUNDS_HPP_ +#endif // SINGULARITY_EOS_BASE_TABLE_UTILS_HPP_ diff --git a/singularity-eos/eos/eos_base.hpp b/singularity-eos/eos/eos_base.hpp index cc40752045e..0d627e75fea 100644 --- a/singularity-eos/eos/eos_base.hpp +++ b/singularity-eos/eos/eos_base.hpp @@ -1,5 +1,5 @@ //------------------------------------------------------------------------------ -// © 2021-2023. Triad National Security, LLC. All rights reserved. This +// © 2021-2024. 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 @@ -20,6 +20,7 @@ #include #include +#include #include namespace singularity { @@ -61,8 +62,8 @@ char *StrCat(char *destination, const char *source) { } // namespace impl // This Macro adds the `using` statements that allow for the base class -// vector functionality to overload the scalar implementations in the derived -// classes +// VECTOR functionality to overload the scalar implementations in the derived +// classes. Do not add functions here that are not overloads of derived class features. // TODO(JMM): Should we have more macros that capture just some of these? #define SG_ADD_BASE_CLASS_USINGS(EOSDERIVED) \ using EosBase::TemperatureFromDensityInternalEnergy; \ @@ -76,16 +77,28 @@ char *StrCat(char *destination, const char *source) { using EosBase::BulkModulusFromDensityInternalEnergy; \ using EosBase::GruneisenParamFromDensityTemperature; \ using EosBase::GruneisenParamFromDensityInternalEnergy; \ - using EosBase::MinimumDensity; \ - using EosBase::MinimumTemperature; \ using EosBase::FillEos; \ using EosBase::EntropyFromDensityTemperature; \ - using EosBase::EntropyFromDensityInternalEnergy; \ - using EosBase::EntropyIsNotEnabled; \ - using EosBase::MinInternalEnergyIsNotEnabled; \ - using EosBase::IsModified; \ - using EosBase::UnmodifyOnce; \ - using EosBase::GetUnmodifiedObject; + using EosBase::EntropyFromDensityInternalEnergy; + +// This macro adds several methods that most modifiers will +// want. Not ALL modifiers will want these methods as written here, +// so use this macro with care. +// TODO(JMM): Find a better solution. Multiple inheritence and mixins +// dont' seem to work as desired here. +#define SG_ADD_MODIFIER_METHODS(T, t_) \ + static inline constexpr bool IsModified() { return true; } \ + inline constexpr T UnmodifyOnce() { return t_; } \ + std::size_t DynamicMemorySizeInBytes() const { return t_.DynamicMemorySizeInBytes(); } \ + std::size_t SharedMemorySizeInBytes() const { return t_.SharedMemorySizeInBytes(); } \ + std::size_t DumpDynamicMemory(char *dst) { return t_.DumpDynamicMemory(dst); } \ + std::size_t SetDynamicMemory(char *src, \ + const SharedMemSettings &stngs = DEFAULT_SHMEM_STNGS) { \ + return t_.SetDynamicMemory(src, stngs); \ + } \ + constexpr bool AllDynamicMemoryIsShareable() const { \ + return t_.AllDynamicMemoryIsShareable(); \ + } class Factor { Real value_ = 1.0; @@ -657,13 +670,93 @@ class EosBase { PORTABLE_ALWAYS_THROW_OR_ABORT(msg); } + // Serialization + /* + The methodology here is there are *three* size methods all EOS's provide: + - `SharedMemorySizeInBytes()` which is the amount of memory a class can share + - `DynamicMemorySizeInBytes()` which is the amount of memory not covered by + `sizeof(this)` + - `SerializedSizeInBytes()` which is the total size of the object. + + I wanted serialization machinery to work if you use a standalone + class or if you use the variant. To make that possible, each class + provides its own implementation of `SharedMemorySizeInBytes` and + `DynamicMemorySizeInBytes()`. But then there is a separate + implementation for the variant and for the base class for + `SerializedSizeInBytes`, `Serialize`, and `DeSerialize`. + */ + + // JMM: These must frequently be special-cased. + std::size_t DynamicMemorySizeInBytes() const { return 0; } + std::size_t DumpDynamicMemory(char *dst) { return 0; } + std::size_t SetDynamicMemory(char *src, + const SharedMemSettings &stngs = DEFAULT_SHMEM_STNGS) { + return 0; + } + // JMM: These usually don't need to be special cased. + std::size_t SharedMemorySizeInBytes() const { + const CRTP *pcrtp = static_cast(this); + return pcrtp->DynamicMemorySizeInBytes(); + } + constexpr bool AllDynamicMemoryIsShareable() const { return true; } + // JMM: These are generic and never need to be special-cased. + // However, there must be a separate implementation for these + // separately in the base class and in the variant. + std::size_t SerializedSizeInBytes() const { + // sizeof(*this) returns the size of JUST the base class. + const CRTP *pcrtp = static_cast(this); + std::size_t dyn_size = pcrtp->DynamicMemorySizeInBytes(); + return dyn_size + sizeof(CRTP); + } + std::size_t Serialize(char *dst) { + CRTP *pcrtp = static_cast(this); + memcpy(dst, pcrtp, sizeof(CRTP)); + std::size_t offst = sizeof(CRTP); + std::size_t dyn_size = pcrtp->DynamicMemorySizeInBytes(); + if (dyn_size > 0) { + offst += pcrtp->DumpDynamicMemory(dst + sizeof(CRTP)); + } + const std::size_t tot_size = pcrtp->SerializedSizeInBytes(); + PORTABLE_ALWAYS_REQUIRE(offst == tot_size, "Serialization failed!"); + return offst; + } + auto Serialize() { + CRTP *pcrtp = static_cast(this); + std::size_t size = pcrtp->SerializedSizeInBytes(); + char *dst = (char *)malloc(size); + std::size_t size_new = Serialize(dst); + PORTABLE_ALWAYS_REQUIRE(size_new == size, "Serialization failed!"); + return std::make_pair(size, dst); + } + std::size_t DeSerialize(char *src, + const SharedMemSettings &stngs = DEFAULT_SHMEM_STNGS) { + CRTP *pcrtp = static_cast(this); + memcpy(pcrtp, src, sizeof(CRTP)); + std::size_t offst = sizeof(CRTP); + std::size_t dyn_size = pcrtp->DynamicMemorySizeInBytes(); + if (dyn_size > 0) { + const bool sizes_same = pcrtp->AllDynamicMemoryIsShareable(); + if (stngs.CopyNeeded() && sizes_same) { + memcpy(stngs.data, src + offst, dyn_size); + } + offst += pcrtp->SetDynamicMemory(src + offst, stngs); + } + const std::size_t tot_size = pcrtp->SerializedSizeInBytes(); + PORTABLE_ALWAYS_REQUIRE(offst == tot_size, "Deserialization failed!"); + return offst; + } + // Tooling for modifiers - inline constexpr bool IsModified() const { return false; } + static inline constexpr bool IsModified() { return false; } inline constexpr decltype(auto) UnmodifyOnce() { return *static_cast(this); } inline constexpr decltype(auto) GetUnmodifiedObject() { - return *static_cast(this); + if constexpr (CRTP::IsModified()) { + return ((static_cast(this))->UnmodifyOnce()).GetUnmodifiedObject(); + } else { + return *static_cast(this); + } } }; } // namespace eos_base diff --git a/singularity-eos/eos/eos_carnahan_starling.hpp b/singularity-eos/eos/eos_carnahan_starling.hpp index b54bdb52422..e77e8443326 100644 --- a/singularity-eos/eos/eos_carnahan_starling.hpp +++ b/singularity-eos/eos/eos_carnahan_starling.hpp @@ -47,7 +47,7 @@ class CarnahanStarling : public EosBase { (PartialRhoZedFromDensity(_rho0) + ZedFromDensity(_rho0) * ZedFromDensity(_rho0) * gm1)), _dpde0(_rho0 * ZedFromDensity(_rho0) * gm1) { - checkParams(); + CheckParams(); } PORTABLE_INLINE_FUNCTION CarnahanStarling(Real gm1, Real Cv, Real bb, Real qq, Real qp, Real T0, Real P0) @@ -58,7 +58,7 @@ class CarnahanStarling : public EosBase { (PartialRhoZedFromDensity(_rho0) + ZedFromDensity(_rho0) * ZedFromDensity(_rho0) * gm1)), _dpde0(_rho0 * ZedFromDensity(_rho0) * gm1) { - checkParams(); + CheckParams(); } CarnahanStarling GetOnDevice() { return *this; } template @@ -67,7 +67,7 @@ class CarnahanStarling : public EosBase { Indexer_t &&lambda = static_cast(nullptr)) const { return std::max(robust::SMALL(), (sie - _qq) / _Cv); } - PORTABLE_INLINE_FUNCTION void checkParams() const { + PORTABLE_INLINE_FUNCTION void CheckParams() const { PORTABLE_ALWAYS_REQUIRE(_Cv >= 0, "Heat capacity must be positive"); PORTABLE_ALWAYS_REQUIRE(_gm1 >= 0, "Gruneisen parameter must be positive"); PORTABLE_ALWAYS_REQUIRE(_bb >= 0, "Covolume must be positive"); diff --git a/singularity-eos/eos/eos_davis.hpp b/singularity-eos/eos/eos_davis.hpp index fb34c2a2296..2bfd96ad764 100644 --- a/singularity-eos/eos/eos_davis.hpp +++ b/singularity-eos/eos/eos_davis.hpp @@ -37,8 +37,15 @@ class DavisReactants : public EosBase { const Real A, const Real B, const Real C, const Real G0, const Real Z, const Real alpha, const Real Cv0) : _rho0(rho0), _e0(e0), _P0(P0), _T0(T0), _A(A), _B(B), _C(C), _G0(G0), _Z(Z), - _alpha(alpha), _Cv0(Cv0) {} + _alpha(alpha), _Cv0(Cv0) { + CheckParams(); + } DavisReactants GetOnDevice() { return *this; } + PORTABLE_INLINE_FUNCTION + void CheckParams() const { + PORTABLE_REQUIRE(_rho0 >= 0, "Density must be positive"); + PORTABLE_REQUIRE(_T0 >= 0, "Temperature must be positive"); + } template PORTABLE_INLINE_FUNCTION Real TemperatureFromDensityInternalEnergy( const Real rho, const Real sie, @@ -193,6 +200,10 @@ class DavisProducts : public EosBase { DavisProducts(const Real a, const Real b, const Real k, const Real n, const Real vc, const Real pc, const Real Cv) : _a(a), _b(b), _k(k), _n(n), _vc(vc), _pc(pc), _Cv(Cv) {} + PORTABLE_INLINE_FUNCTION + void CheckParams() const { + // TODO(JMM): Stub. + } DavisProducts GetOnDevice() { return *this; } template PORTABLE_INLINE_FUNCTION Real TemperatureFromDensityInternalEnergy( diff --git a/singularity-eos/eos/eos_eospac.hpp b/singularity-eos/eos/eos_eospac.hpp index 343d72690b9..e65c71d95c0 100644 --- a/singularity-eos/eos/eos_eospac.hpp +++ b/singularity-eos/eos/eos_eospac.hpp @@ -140,7 +140,20 @@ class EOSPAC : public EosBase { bool apply_smoothing = false, eospacSplit apply_splitting = eospacSplit::none, bool linear_interp = false); + PORTABLE_INLINE_FUNCTION void CheckParams() const { + // TODO(JMM): More validation checks? + PORTABLE_ALWAYS_REQUIRE(rho_min_ >= 0, "Non-negative minimum density"); + PORTABLE_ALWAYS_REQUIRE(temp_min_ >= 0, "Non-negative minimum temperature"); + } inline EOSPAC GetOnDevice() { return *this; } + + std::size_t DynamicMemorySizeInBytes() const; + std::size_t DumpDynamicMemory(char *dst); + std::size_t SetDynamicMemory(char *src, + const SharedMemSettings &stngs = DEFAULT_SHMEM_STNGS); + std::size_t SharedMemorySizeInBytes() const; + constexpr bool AllDynamicMemoryIsShareable() const { return false; } + SG_PIF_NOWARN template PORTABLE_INLINE_FUNCTION Real TemperatureFromDensityInternalEnergy( @@ -1119,7 +1132,6 @@ class EOSPAC : public EosBase { EOS_INTEGER PofRE_table_; EOS_INTEGER EcofD_table_; EOS_INTEGER tablehandle[NT]; - EOS_INTEGER EOS_Info_table_; static constexpr Real temp_ref_ = 293; Real rho_ref_ = 1; Real sie_ref_ = 1; @@ -1130,6 +1142,9 @@ class EOSPAC : public EosBase { Real dvdt_ref_ = 1; Real rho_min_ = 0; Real temp_min_ = 0; + // TODO(JMM): Is the fact that EOS_INTEGER isn't a size_t a + // problem? Could it ever realistically overflow? + EOS_INTEGER shared_size_, packed_size_; static inline std::map &scratch_nbuffers() { static std::map nbuffers = { @@ -1166,7 +1181,7 @@ inline EOSPAC::EOSPAC(const int matid, bool invert_at_setup, Real insert_data, NT, matid, tableType, tablehandle, std::vector({"EOS_Pt_DT", "EOS_T_DUt", "EOS_Ut_DT", "EOS_D_PtT", "EOS_T_DPt", "EOS_Pt_DUt", "EOS_Uc_D"}), - Verbosity::Quiet, invert_at_setup = invert_at_setup, insert_data = insert_data, + Verbosity::Debug, invert_at_setup = invert_at_setup, insert_data = insert_data, monotonicity = monotonicity, apply_smoothing = apply_smoothing, apply_splitting = apply_splitting, linear_interp = linear_interp); PofRT_table_ = tablehandle[0]; @@ -1177,9 +1192,23 @@ inline EOSPAC::EOSPAC(const int matid, bool invert_at_setup, Real insert_data, PofRE_table_ = tablehandle[5]; EcofD_table_ = tablehandle[6]; + // Shared memory info + { + EOS_INTEGER NTABLES[] = {NT}; + EOS_INTEGER error_code = EOS_OK; +#ifdef SINGULARITY_EOSPAC_ENABLE_SHARED_MEMORY + eos_GetSharedPackedTablesSize(NTABLES, tablehandle, &packed_size_, &shared_size_, + &error_code); +#else + eos_GetPackedTablesSize(NTABLES, tablehandle, &packed_size_, &error_code); + shared_size_ = 0; +#endif // SINGULARITY_EOSPAC_ENABLE_SHARED_MEMORY + eosCheckError(error_code, "Get shared memory info", Verbosity::Debug); + } + // Set reference states and table bounds SesameMetadata m; - eosGetMetadata(matid, m, Verbosity::Quiet); + eosGetMetadata(matid, m, Verbosity::Debug); rho_ref_ = m.normalDensity; rho_min_ = m.rhoMin; temp_min_ = m.TMin; @@ -1208,6 +1237,43 @@ inline EOSPAC::EOSPAC(const int matid, bool invert_at_setup, Real insert_data, robust::ratio(dpde_ref_ * cv_ref_, rho_ref_ * rho_ref_ * pressureFromSesame(DPDR)); } +inline std::size_t EOSPAC::DynamicMemorySizeInBytes() const { return packed_size_; } +inline std::size_t EOSPAC::DumpDynamicMemory(char *dst) { + static_assert(sizeof(char) == sizeof(EOS_CHAR), "EOS_CHAR is one byte"); + EOS_INTEGER NTABLES[] = {NT}; + EOS_INTEGER error_code = EOS_OK; + eos_GetPackedTables(NTABLES, tablehandle, (EOS_CHAR *)dst, &error_code); + eosCheckError(error_code, "eos_GetPackedTables", Verbosity::Debug); + return packed_size_; // JMM: Note this is NOT shared memory size +} +inline std::size_t EOSPAC::SetDynamicMemory(char *src, const SharedMemSettings &stngs) { + static_assert(sizeof(char) == sizeof(EOS_CHAR), "EOS_CHAR is one byte"); + EOS_INTEGER NTABLES[] = {NT}; + EOS_INTEGER error_code = EOS_OK; + eosCheckError(error_code, "eos_DestroyAll", Verbosity::Debug); +#ifdef SINGULARITY_EOSPAC_ENABLE_SHARED_MEMORY + PORTABLE_ALWAYS_REQUIRE( + stngs.data != nullptr, + "EOSPAC with shared memory active requires a shared memory pointer"); + // JMM: EOS_BOOLEAN is an enum with EOS_FALSE=0 and EOS_TRUE=1. + eos_SetSharedPackedTables(NTABLES, &packed_size_, (EOS_CHAR *)src, + (EOS_CHAR *)stngs.data, stngs.is_node_root, tablehandle, + &error_code); +#else + eos_SetPackedTables(NTABLES, &packed_size_, (EOS_CHAR *)src, tablehandle, &error_code); +#endif // SINGULARITY_EOSPAC_ENABLE_SHARED_MEMORY + eosCheckError(error_code, "eos_SetSharedPackedTables", Verbosity::Debug); + PofRT_table_ = tablehandle[0]; // these get re-set + TofRE_table_ = tablehandle[1]; + EofRT_table_ = tablehandle[2]; + RofPT_table_ = tablehandle[3]; + TofRP_table_ = tablehandle[4]; + PofRE_table_ = tablehandle[5]; + EcofD_table_ = tablehandle[6]; + return packed_size_; +} +inline std::size_t EOSPAC::SharedMemorySizeInBytes() const { return shared_size_; } + SG_PIF_NOWARN template PORTABLE_INLINE_FUNCTION Real EOSPAC::TemperatureFromDensityInternalEnergy( diff --git a/singularity-eos/eos/eos_gruneisen.hpp b/singularity-eos/eos/eos_gruneisen.hpp index 9f2e179e233..131e523d7f8 100644 --- a/singularity-eos/eos/eos_gruneisen.hpp +++ b/singularity-eos/eos/eos_gruneisen.hpp @@ -63,6 +63,15 @@ class Gruneisen : public EosBase { _Cv(Cv), _rho_max(RHOMAX_SAFETY * ComputeRhoMax(s1, s2, s3, rho0)) {} static PORTABLE_INLINE_FUNCTION Real ComputeRhoMax(const Real s1, const Real s2, const Real s3, const Real rho0); + PORTABLE_INLINE_FUNCTION + void CheckParams() const { + PORTABLE_ALWAYS_REQUIRE(_T0 >= 0, "Non-negative reference temperature required"); + PORTABLE_ALWAYS_REQUIRE(_rho0 >= 0, "Non-negative reference density required"); + PORTABLE_ALWAYS_REQUIRE(_C0 >= 0, "Non-negative Hugoniot intercept required"); + PORTABLE_ALWAYS_REQUIRE(_Cv >= 0, "Non-negative heat capacity required"); + PORTABLE_ALWAYS_REQUIRE(_rho_max > _rho0, + "Maximum density must be greater than reference"); + } PORTABLE_INLINE_FUNCTION Real MaxStableDensityAtTemperature(const Real temperature) const; Gruneisen GetOnDevice() { return *this; } diff --git a/singularity-eos/eos/eos_helmholtz.hpp b/singularity-eos/eos/eos_helmholtz.hpp index 3dc5b07d416..e5fbb0be786 100644 --- a/singularity-eos/eos/eos_helmholtz.hpp +++ b/singularity-eos/eos/eos_helmholtz.hpp @@ -34,6 +34,7 @@ #include #include #include +#include // ports of call #include @@ -45,6 +46,7 @@ #include #include #include +#include #include // spiner @@ -203,16 +205,39 @@ inline void SetTablesFromFile(std::ifstream &file, int n1, Real r1min, Real r1ma } // namespace HelmUtils class HelmElectrons { + friend class table_utils::SpinerTricks; + using SpinerTricks = table_utils::SpinerTricks; + public: // may change with time using DataBox = HelmUtils::DataBox; static constexpr std::size_t NDERIV = HelmUtils::NDERIV; HelmElectrons() = default; - inline HelmElectrons(const std::string &filename) { InitDataFile_(filename); } + inline HelmElectrons(const std::string &filename) { + InitDataFile_(filename); + CheckParams(); + } inline HelmElectrons GetOnDevice(); inline void Finalize(); + inline void CheckParams() const { + // better than nothing... + PORTABLE_ALWAYS_REQUIRE(rho_.size() == NRHO, "Density grid correct"); + PORTABLE_ALWAYS_REQUIRE(T_.size() == NTEMP, "Temperature grid correct"); + } + + std::size_t DynamicMemorySizeInBytes() const { + return SpinerTricks::DynamicMemorySizeInBytes(this); + } + std::size_t DumpDynamicMemory(char *dst) { + return SpinerTricks::DumpDynamicMemory(dst, this); + } + std::size_t SetDynamicMemory(char *src, + const SharedMemSettings &stngs = DEFAULT_SHMEM_STNGS) { + return SpinerTricks::SetDynamicMemory((stngs.data == nullptr) ? src : stngs.data, + this); + } PORTABLE_INLINE_FUNCTION void GetFromDensityTemperature(Real rho, Real lT, Real Ye, Real Ytot, Real De, Real lDe, @@ -259,6 +284,15 @@ class HelmElectrons { // number density DataBox xf_, xfd_, xft_, xfdt_; +#define DBLIST \ + &rho_, &T_, &f_, &fd_, &ft_, &fdd_, &ftt_, &fdt_, &fddt_, &fdtt_, &fddtt_, &dpdf_, \ + &dpdfd_, &dpdft_, &dpdfdt_, &ef_, &efd_, &eft_, &efdt_, &xf_, &xfd_, &xft_, &xfdt_ + auto GetDataBoxPointers_() const { return std::vector{DBLIST}; } + auto GetDataBoxPointers_() { return std::vector{DBLIST}; } +#undef DBLIST + + DataStatus memoryStatus_ = DataStatus::Deallocated; + static constexpr std::size_t NTEMP = 101; static constexpr std::size_t NRHO = 271; @@ -416,6 +450,8 @@ class Helmholtz : public EosBase { : electrons_(filename), options_(rad, gas, coul, ion, ele, verbose, newton_raphson) {} + PORTABLE_INLINE_FUNCTION void CheckParams() const { electrons_.CheckParams(); } + PORTABLE_INLINE_FUNCTION int nlambda() const noexcept { return 3; } static constexpr unsigned long PreferredInput() { return thermalqs::density | thermalqs::temperature; @@ -451,6 +487,14 @@ class Helmholtz : public EosBase { coul_.Finalize(); electrons_.Finalize(); } + std::size_t DynamicMemorySizeInBytes() const { + return electrons_.DynamicMemorySizeInBytes(); + } + std::size_t DumpDynamicMemory(char *dst) { return electrons_.DumpDynamicMemory(dst); } + std::size_t SetDynamicMemory(char *src, + const SharedMemSettings &stngs = DEFAULT_SHMEM_STNGS) { + return electrons_.SetDynamicMemory(src); + } PORTABLE_INLINE_FUNCTION void GetMassFractions(const Real rho, const Real temp, const Real ytot, Real &xni, @@ -880,63 +924,15 @@ inline void HelmElectrons::InitDataFile_(const std::string &filename) { for (int i = 0; i < NTEMP; ++i) { T_(i) = math_utils::pow10(lTRange.x(i)); } + + memoryStatus_ = DataStatus::OnHost; } inline HelmElectrons HelmElectrons::GetOnDevice() { - HelmElectrons other; - other.rho_ = Spiner::getOnDeviceDataBox(rho_); - other.T_ = Spiner::getOnDeviceDataBox(T_); - other.f_ = Spiner::getOnDeviceDataBox(f_); - other.fd_ = Spiner::getOnDeviceDataBox(fd_); - other.ft_ = Spiner::getOnDeviceDataBox(ft_); - other.fdd_ = Spiner::getOnDeviceDataBox(fdd_); - other.ftt_ = Spiner::getOnDeviceDataBox(ftt_); - other.fdt_ = Spiner::getOnDeviceDataBox(fdt_); - other.fdd_ = Spiner::getOnDeviceDataBox(fdd_); - other.fddt_ = Spiner::getOnDeviceDataBox(fddt_); - other.fdtt_ = Spiner::getOnDeviceDataBox(fdtt_); - other.fddtt_ = Spiner::getOnDeviceDataBox(fddtt_); - other.dpdf_ = Spiner::getOnDeviceDataBox(dpdf_); - other.dpdfd_ = Spiner::getOnDeviceDataBox(dpdfd_); - other.dpdft_ = Spiner::getOnDeviceDataBox(dpdft_); - other.dpdfdt_ = Spiner::getOnDeviceDataBox(dpdfdt_); - other.ef_ = Spiner::getOnDeviceDataBox(ef_); - other.efd_ = Spiner::getOnDeviceDataBox(efd_); - other.eft_ = Spiner::getOnDeviceDataBox(eft_); - other.efdt_ = Spiner::getOnDeviceDataBox(efdt_); - other.xf_ = Spiner::getOnDeviceDataBox(xf_); - other.xfd_ = Spiner::getOnDeviceDataBox(xfd_); - other.xft_ = Spiner::getOnDeviceDataBox(xft_); - other.xfdt_ = Spiner::getOnDeviceDataBox(xfdt_); - return other; + return SpinerTricks::GetOnDevice(this); } -inline void HelmElectrons::Finalize() { - rho_.finalize(); - T_.finalize(); - f_.finalize(); - fd_.finalize(); - ft_.finalize(); - fdd_.finalize(); - ftt_.finalize(); - fdt_.finalize(); - fdd_.finalize(); - fddt_.finalize(); - fdtt_.finalize(); - fddtt_.finalize(); - dpdf_.finalize(); - dpdfd_.finalize(); - dpdft_.finalize(); - dpdfdt_.finalize(); - ef_.finalize(); - efd_.finalize(); - eft_.finalize(); - efdt_.finalize(); - xf_.finalize(); - xfd_.finalize(); - xft_.finalize(); - xfdt_.finalize(); -} +inline void HelmElectrons::Finalize() { SpinerTricks::Finalize(this); } PORTABLE_INLINE_FUNCTION void HelmElectrons::GetFromDensityTemperature(Real rho, Real lT, Real Ye, Real Ytot, diff --git a/singularity-eos/eos/eos_ideal.hpp b/singularity-eos/eos/eos_ideal.hpp index 489c3a65536..d3959284049 100644 --- a/singularity-eos/eos/eos_ideal.hpp +++ b/singularity-eos/eos/eos_ideal.hpp @@ -42,13 +42,13 @@ class IdealGas : public EosBase { : _Cv(Cv), _gm1(gm1), _rho0(_P0 / (_gm1 * _Cv * _T0)), _sie0(_Cv * _T0), _bmod0((_gm1 + 1) * _gm1 * _rho0 * _Cv * _T0), _dpde0(_gm1 * _rho0), _dvdt0(1. / (_rho0 * _T0)), _EntropyT0(_T0), _EntropyRho0(_rho0) { - checkParams(); + CheckParams(); } PORTABLE_INLINE_FUNCTION IdealGas(Real gm1, Real Cv, Real EntropyT0, Real EntropyRho0) : _Cv(Cv), _gm1(gm1), _rho0(_P0 / (_gm1 * _Cv * _T0)), _sie0(_Cv * _T0), _bmod0((_gm1 + 1) * _gm1 * _rho0 * _Cv * _T0), _dpde0(_gm1 * _rho0), _dvdt0(1. / (_rho0 * _T0)), _EntropyT0(EntropyT0), _EntropyRho0(EntropyRho0) { - checkParams(); + CheckParams(); } IdealGas GetOnDevice() { return *this; } @@ -58,7 +58,7 @@ class IdealGas : public EosBase { Indexer_t &&lambda = static_cast(nullptr)) const { return MYMAX(0.0, sie / _Cv); } - PORTABLE_INLINE_FUNCTION void checkParams() const { + PORTABLE_INLINE_FUNCTION void CheckParams() const { // Portable_require seems to do the opposite of what it should. Conditions // reflect this and the code should be changed when ports-of-call changes PORTABLE_ALWAYS_REQUIRE(_Cv >= 0, "Heat capacity must be positive"); @@ -67,6 +67,7 @@ class IdealGas : public EosBase { "Entropy reference temperature must be positive"); PORTABLE_ALWAYS_REQUIRE(_EntropyRho0 >= 0, "Entropy reference density must be positive"); + return; } template PORTABLE_INLINE_FUNCTION Real InternalEnergyFromDensityTemperature( diff --git a/singularity-eos/eos/eos_jwl.hpp b/singularity-eos/eos/eos_jwl.hpp index 6cf06ad9474..1931541630f 100644 --- a/singularity-eos/eos/eos_jwl.hpp +++ b/singularity-eos/eos/eos_jwl.hpp @@ -45,9 +45,12 @@ class JWL : public EosBase { _c1(robust::ratio(A, rho0 * R1)), _c2(robust::ratio(B, rho0 * R2)) { assert(R1 > 0.0); assert(R2 > 0.0); - assert(w > 0.0); - assert(rho0 > 0.0); - assert(Cv > 0.0); + CheckParams(); + } + PORTABLE_INLINE_FUNCTION void CheckParams() const { + PORTABLE_ALWAYS_REQUIRE(_w > 0.0, "w > 0"); + PORTABLE_ALWAYS_REQUIRE(_rho0 > 0.0, "Positive reference density"); + PORTABLE_ALWAYS_REQUIRE(_Cv > 0.0, "Positive specific heat"); } JWL GetOnDevice() { return *this; } template diff --git a/singularity-eos/eos/eos_mgusup.hpp b/singularity-eos/eos/eos_mgusup.hpp index ab42dbe7c45..8fae0690566 100644 --- a/singularity-eos/eos/eos_mgusup.hpp +++ b/singularity-eos/eos/eos_mgusup.hpp @@ -40,8 +40,9 @@ class MGUsup : public EosBase { MGUsup(const Real rho0, const Real T0, const Real Cs, const Real s, const Real G0, const Real Cv0, const Real E0, const Real S0) : _rho0(rho0), _T0(T0), _Cs(Cs), _s(s), _G0(G0), _Cv0(Cv0), _E0(E0), _S0(S0) { - _CheckMGUsup(); + CheckParams(); } + PORTABLE_INLINE_FUNCTION void CheckParams() const; MGUsup GetOnDevice() { return *this; } template @@ -156,11 +157,9 @@ class MGUsup : public EosBase { static constexpr const unsigned long _preferred_input = thermalqs::density | thermalqs::specific_internal_energy; Real _rho0, _T0, _Cs, _s, _G0, _Cv0, _E0, _S0; - void _CheckMGUsup(); }; -inline void MGUsup::_CheckMGUsup() { - +PORTABLE_INLINE_FUNCTION void MGUsup::CheckParams() const { if (_rho0 < 0.0) { PORTABLE_ALWAYS_THROW_OR_ABORT("Required MGUsup model parameter rho0 < 0"); } diff --git a/singularity-eos/eos/eos_noble_abel.hpp b/singularity-eos/eos/eos_noble_abel.hpp index 898f1b8a3d6..a39816996b4 100644 --- a/singularity-eos/eos/eos_noble_abel.hpp +++ b/singularity-eos/eos/eos_noble_abel.hpp @@ -44,7 +44,7 @@ class NobleAbel : public EosBase { _bmod0(robust::ratio(_rho0 * Cv * _T0 * gm1 * (gm1 + 1.0), (1.0 - bb * _rho0) * (1.0 - bb * _rho0))), _dpde0(robust::ratio(_rho0 * gm1, 1.0 - bb * _rho0)) { - checkParams(); + CheckParams(); } PORTABLE_INLINE_FUNCTION NobleAbel(Real gm1, Real Cv, Real bb, Real qq, Real qp, Real T0, Real P0) @@ -54,7 +54,7 @@ class NobleAbel : public EosBase { _bmod0(robust::ratio(_rho0 * Cv * T0 * gm1 * (gm1 + 1.0), (1.0 - bb * _rho0) * (1.0 - bb * _rho0))), _dpde0(robust::ratio(_rho0 * gm1, 1.0 - bb * _rho0)) { - checkParams(); + CheckParams(); } NobleAbel GetOnDevice() { return *this; } template @@ -63,7 +63,7 @@ class NobleAbel : public EosBase { Indexer_t &&lambda = static_cast(nullptr)) const { return std::max(robust::SMALL(), (sie - _qq) / _Cv); } - PORTABLE_INLINE_FUNCTION void checkParams() const { + PORTABLE_INLINE_FUNCTION void CheckParams() const { PORTABLE_ALWAYS_REQUIRE(_Cv > 0, "Heat capacity must be positive"); PORTABLE_ALWAYS_REQUIRE(_gm1 >= 0, "Gruneisen parameter must be positive"); PORTABLE_ALWAYS_REQUIRE(_bb >= 0, "Covolume must be positive"); diff --git a/singularity-eos/eos/eos_powermg.hpp b/singularity-eos/eos/eos_powermg.hpp index d73a994a5ac..3b17e8aae9a 100644 --- a/singularity-eos/eos/eos_powermg.hpp +++ b/singularity-eos/eos/eos_powermg.hpp @@ -41,9 +41,15 @@ class PowerMG : public EosBase { const Real S0, const Real Pmin, const Real *expconsts) : _rho0(rho0), _T0(T0), _G0(G0), _Cv0(Cv0), _E0(E0), _S0(S0), _Pmin(Pmin) { _InitializePowerMG(expconsts); - _CheckPowerMG(); + CheckParams(); + if (_Pmin >= 0.0) { + _Pmin = -1000 * _K0toK40[0]; + PORTABLE_WARN("PowerMG model parameter Pmin not set or positive. Reset to default " + "(-1000*K0)"); + } } + PORTABLE_INLINE_FUNCTION void CheckParams() const; PowerMG GetOnDevice() { return *this; } template PORTABLE_INLINE_FUNCTION Real TemperatureFromDensityInternalEnergy( @@ -164,7 +170,6 @@ class PowerMG : public EosBase { static constexpr const int PressureCoeffsK0toK40Size = 41; int _M; Real _K0toK40[PressureCoeffsK0toK40Size]; - void _CheckPowerMG(); void _InitializePowerMG(const Real *expcoeffs); PORTABLE_INLINE_FUNCTION Real _HugPressureFromDensity(const Real rho) const; PORTABLE_INLINE_FUNCTION Real _HugTemperatureFromDensity(const Real rho) const; @@ -175,7 +180,7 @@ class PowerMG : public EosBase { _compBulkModulusFromDensityInternalEnergy(const Real rho, const Real sie) const; }; -inline void PowerMG::_CheckPowerMG() { +PORTABLE_INLINE_FUNCTION void PowerMG::CheckParams() const { if (_rho0 < 0.0) { PORTABLE_ALWAYS_THROW_OR_ABORT("Required PowerMG model parameter rho0 < 0"); @@ -189,13 +194,6 @@ inline void PowerMG::_CheckPowerMG() { if (_K0toK40[0] < 0.0) { PORTABLE_ALWAYS_THROW_OR_ABORT("Required PowerMG model parameter K0 < 0"); } - if (_Pmin >= 0.0) { - _Pmin = -1000 * _K0toK40[0]; -#ifndef NDEBUG - PORTABLE_WARN( - "PowerMG model parameter Pmin not set or positive. Reset to default (-1000*K0)"); -#endif // NDEBUG - } } inline void PowerMG::_InitializePowerMG(const Real *K0toK40input) { diff --git a/singularity-eos/eos/eos_sap_polynomial.hpp b/singularity-eos/eos/eos_sap_polynomial.hpp index 8499de7c596..29681300e83 100644 --- a/singularity-eos/eos/eos_sap_polynomial.hpp +++ b/singularity-eos/eos/eos_sap_polynomial.hpp @@ -42,11 +42,11 @@ class SAP_Polynomial : public EosBase { const Real b2c, const Real b2e, const Real b3) : _rho0(rho0), _a0(a0), _a1(a1), _a2c(a2c), _a2e(a2e), _a3(a3), _b0(b0), _b1(b1), _b2c(b2c), _b2e(b2e), _b3(b3) { - checkParams(); + CheckParams(); } SAP_Polynomial GetOnDevice() { return *this; } - PORTABLE_INLINE_FUNCTION void checkParams() const { + PORTABLE_INLINE_FUNCTION void CheckParams() const { PORTABLE_ALWAYS_REQUIRE(_rho0 >= 0, "Reference density must be non-negative"); } template diff --git a/singularity-eos/eos/eos_spiner.hpp b/singularity-eos/eos/eos_spiner.hpp index 547c2c22cb3..952e0f0cf03 100644 --- a/singularity-eos/eos/eos_spiner.hpp +++ b/singularity-eos/eos/eos_spiner.hpp @@ -31,6 +31,7 @@ // ports-of-call #include +#include // base #include @@ -38,6 +39,7 @@ #include #include #include +#include #include #include @@ -66,6 +68,9 @@ using namespace eos_base; we use log-linear extrapolation. */ class SpinerEOSDependsRhoT : public EosBase { + friend class table_utils::SpinerTricks; + using SpinerTricks = table_utils::SpinerTricks; + public: static constexpr int NGRIDS = 3; using Grid_t = Spiner::PiecewiseGrid1D; @@ -75,26 +80,7 @@ class SpinerEOSDependsRhoT : public EosBase { struct Lambda { enum Index { lRho = 0, lT = 1 }; }; - // Generic functions provided by the base class. These contain - // e.g. the vector overloads that use the scalar versions declared - // here We explicitly list, rather than using the macro because we - // overload some methods. - using EosBase::TemperatureFromDensityInternalEnergy; - using EosBase::InternalEnergyFromDensityTemperature; - using EosBase::PressureFromDensityTemperature; - using EosBase::PressureFromDensityInternalEnergy; - using EosBase::MinInternalEnergyFromDensity; - using EosBase::EntropyFromDensityTemperature; - using EosBase::EntropyFromDensityInternalEnergy; - using EosBase::SpecificHeatFromDensityTemperature; - using EosBase::SpecificHeatFromDensityInternalEnergy; - using EosBase::BulkModulusFromDensityTemperature; - using EosBase::BulkModulusFromDensityInternalEnergy; - using EosBase::GruneisenParamFromDensityTemperature; - using EosBase::GruneisenParamFromDensityInternalEnergy; - using EosBase::FillEos; - using EosBase::EntropyIsNotEnabled; - + SG_ADD_BASE_CLASS_USINGS(SpinerEOSDependsRhoT); inline SpinerEOSDependsRhoT(const std::string &filename, int matid, bool reproduciblity_mode = false); inline SpinerEOSDependsRhoT(const std::string &filename, @@ -105,6 +91,24 @@ class SpinerEOSDependsRhoT : public EosBase { inline SpinerEOSDependsRhoT GetOnDevice(); + PORTABLE_INLINE_FUNCTION void CheckParams() const { + PORTABLE_ALWAYS_REQUIRE(numRho_ > 0, "Finite number of density points"); + PORTABLE_ALWAYS_REQUIRE(numT_ > 0, "Finite number of temperature points"); + PORTABLE_ALWAYS_REQUIRE(!(std::isnan(lRhoMin_) || std::isnan(lRhoMax_)), + "Density bounds well defined"); + PORTABLE_ALWAYS_REQUIRE(lRhoMax_ > lRhoMin_, "Density bounds ordered"); + PORTABLE_ALWAYS_REQUIRE(rhoMax_ > 0, "Max density must be positive"); + PORTABLE_ALWAYS_REQUIRE(!(std::isnan(lTMin_) || std::isnan(lTMax_)), + "Temperature bounds well defined"); + PORTABLE_ALWAYS_REQUIRE(lTMax_ > lTMin_, "Temperature bounds ordered"); + PORTABLE_ALWAYS_REQUIRE(TMax_ > 0, "Max temperature must be positive"); + } + + std::size_t DynamicMemorySizeInBytes() const; + std::size_t DumpDynamicMemory(char *dst); + std::size_t SetDynamicMemory(char *src, + const SharedMemSettings &stngs = DEFAULT_SHMEM_STNGS); + template PORTABLE_INLINE_FUNCTION Real TemperatureFromDensityInternalEnergy( const Real rho, const Real sie, @@ -277,13 +281,25 @@ class SpinerEOSDependsRhoT : public EosBase { static constexpr const unsigned long _preferred_input = thermalqs::density | thermalqs::temperature; // static constexpr const char _eos_type[] {"SpinerEOSDependsRhoT"}; - static constexpr const int numDataBoxes_ = 12; + + // TODO(JMM): Could unify declarations and macro below by using + // reference_wrapper instead of pointers... worth it? DataBox P_, sie_, bMod_, dPdRho_, dPdE_, dTdRho_, dTdE_, dEdRho_, dEdT_; DataBox PMax_, sielTMax_, dEdTMax_, gm1Max_; DataBox lTColdCrit_; DataBox PCold_, sieCold_, bModCold_; DataBox dPdRhoCold_, dPdECold_, dTdRhoCold_, dTdECold_, dEdTCold_; DataBox rho_at_pmin_; + + // TODO(JMM): Pointers here? or reference_wrapper? IMO the pointers are more clear +#define DBLIST \ + &P_, &sie_, &bMod_, &dPdRho_, &dPdE_, &dTdRho_, &dTdE_, &dEdRho_, &dEdT_, &PMax_, \ + &sielTMax_, &dEdTMax_, &gm1Max_, &lTColdCrit_, &PCold_, &sieCold_, &bModCold_, \ + &dPdRhoCold_, &dPdECold_, &dTdRhoCold_, &dTdECold_, &dEdTCold_, &rho_at_pmin_ + auto GetDataBoxPointers_() const { return std::vector{DBLIST}; } + auto GetDataBoxPointers_() { return std::vector{DBLIST}; } +#undef DBLIST + int numRho_, numT_; Real lRhoMin_, lRhoMax_, rhoMax_; Real lRhoMinSearch_; @@ -323,32 +339,17 @@ class SpinerEOSDependsRhoT : public EosBase { mitigated by Ye and (1-Ye) to control how important each term is. */ class SpinerEOSDependsRhoSie : public EosBase { + friend class table_utils::SpinerTricks; + public: using Grid_t = SpinerEOSDependsRhoT::Grid_t; using DataBox = SpinerEOSDependsRhoT::DataBox; struct SP5Tables { DataBox P, bMod, dPdRho, dPdE, dTdRho, dTdE, dEdRho; }; - // Generic functions provided by the base class. These contain - // e.g. the vector overloads that use the scalar versions declared - // here We explicitly list, rather than using the macro because we - // overload some methods. - using EosBase::TemperatureFromDensityInternalEnergy; - using EosBase::InternalEnergyFromDensityTemperature; - using EosBase::PressureFromDensityTemperature; - using EosBase::PressureFromDensityInternalEnergy; - using EosBase::MinInternalEnergyFromDensity; - using EosBase::EntropyFromDensityTemperature; - using EosBase::EntropyFromDensityInternalEnergy; - using EosBase::SpecificHeatFromDensityTemperature; - using EosBase::SpecificHeatFromDensityInternalEnergy; - using EosBase::BulkModulusFromDensityTemperature; - using EosBase::BulkModulusFromDensityInternalEnergy; - using EosBase::GruneisenParamFromDensityTemperature; - using EosBase::GruneisenParamFromDensityInternalEnergy; - using EosBase::FillEos; - using EosBase::EntropyIsNotEnabled; + using STricks = table_utils::SpinerTricks; + SG_ADD_BASE_CLASS_USINGS(SpinerEOSDependsRhoSie); PORTABLE_INLINE_FUNCTION SpinerEOSDependsRhoSie() : memoryStatus_(DataStatus::Deallocated) {} inline SpinerEOSDependsRhoSie(const std::string &filename, int matid, @@ -358,6 +359,19 @@ class SpinerEOSDependsRhoSie : public EosBase { bool reproducibility_mode = false); inline SpinerEOSDependsRhoSie GetOnDevice(); + PORTABLE_INLINE_FUNCTION void CheckParams() const { + PORTABLE_ALWAYS_REQUIRE(numRho_ > 0, "Finite number of density points"); + PORTABLE_ALWAYS_REQUIRE(!(std::isnan(lRhoMin_) || std::isnan(lRhoMax_)), + "Density bounds well defined"); + PORTABLE_ALWAYS_REQUIRE(lRhoMax_ > lRhoMin_, "Density bounds ordered"); + PORTABLE_ALWAYS_REQUIRE(rhoMax_ > 0, "Max density must be positive"); + } + + std::size_t DynamicMemorySizeInBytes() const; + std::size_t DumpDynamicMemory(char *dst); + std::size_t SetDynamicMemory(char *src, + const SharedMemSettings &stngs = DEFAULT_SHMEM_STNGS); + template PORTABLE_INLINE_FUNCTION Real TemperatureFromDensityInternalEnergy( const Real rho, const Real sie, @@ -508,6 +522,18 @@ class SpinerEOSDependsRhoSie : public EosBase { Real lRhoOffset_, lTOffset_, lEOffset_; // offsets must be non-negative +#define DBLIST \ + &sie_, &T_, &(dependsRhoT_.P), &(dependsRhoT_.bMod), &(dependsRhoT_.dPdRho), \ + &(dependsRhoT_.dPdE), &(dependsRhoT_.dTdRho), &(dependsRhoT_.dTdE), \ + &(dependsRhoT_.dEdRho), &(dependsRhoSie_.P), &(dependsRhoSie_.bMod), \ + &(dependsRhoSie_.dPdRho), &(dependsRhoSie_.dPdE), &(dependsRhoSie_.dTdRho), \ + &(dependsRhoSie_.dTdE), &(dependsRhoSie_.dEdRho), &PlRhoMax_, &dPdRhoMax_ + std::vector GetDataBoxPointers_() const { + return std::vector{DBLIST}; + } + std::vector GetDataBoxPointers_() { return std::vector{DBLIST}; } +#undef DBLIST + static constexpr unsigned long _preferred_input = thermalqs::density | thermalqs::temperature; // static constexpr const char _eos_type[] = "SpinerEOSDependsRhoSie"; @@ -610,6 +636,8 @@ inline SpinerEOSDependsRhoT::SpinerEOSDependsRhoT(const std::string &filename, i if (status != H5_SUCCESS) { EOS_ERROR("SpinerDependsRHoT: HDF5 error\n"); // TODO: make this better } + + CheckParams(); } inline SpinerEOSDependsRhoT::SpinerEOSDependsRhoT(const std::string &filename, @@ -641,84 +669,27 @@ inline SpinerEOSDependsRhoT::SpinerEOSDependsRhoT(const std::string &filename, if (status != H5_SUCCESS) { EOS_ERROR("SpinerDependsRhoT: HDF5 error\n"); } + + CheckParams(); } inline SpinerEOSDependsRhoT SpinerEOSDependsRhoT::GetOnDevice() { - SpinerEOSDependsRhoT other; - other.P_ = Spiner::getOnDeviceDataBox(P_); - other.sie_ = Spiner::getOnDeviceDataBox(sie_); - other.bMod_ = Spiner::getOnDeviceDataBox(bMod_); - other.dPdRho_ = Spiner::getOnDeviceDataBox(dPdRho_); - other.dPdE_ = Spiner::getOnDeviceDataBox(dPdE_); - other.dTdRho_ = Spiner::getOnDeviceDataBox(dTdRho_); - other.dTdE_ = Spiner::getOnDeviceDataBox(dTdE_); - other.dEdRho_ = Spiner::getOnDeviceDataBox(dEdRho_); - other.dEdT_ = Spiner::getOnDeviceDataBox(dEdT_); - other.PMax_ = Spiner::getOnDeviceDataBox(PMax_); - other.sielTMax_ = Spiner::getOnDeviceDataBox(sielTMax_); - other.dEdTMax_ = Spiner::getOnDeviceDataBox(dEdTMax_); - other.gm1Max_ = Spiner::getOnDeviceDataBox(gm1Max_); - other.PCold_ = Spiner::getOnDeviceDataBox(PCold_); - other.sieCold_ = Spiner::getOnDeviceDataBox(sieCold_); - other.bModCold_ = Spiner::getOnDeviceDataBox(bModCold_); - other.dPdRhoCold_ = Spiner::getOnDeviceDataBox(dPdRhoCold_); - other.dPdECold_ = Spiner::getOnDeviceDataBox(dPdECold_); - other.dTdRhoCold_ = Spiner::getOnDeviceDataBox(dTdRhoCold_); - other.dTdECold_ = Spiner::getOnDeviceDataBox(dTdECold_); - other.dEdTCold_ = Spiner::getOnDeviceDataBox(dEdTCold_); - other.lTColdCrit_ = Spiner::getOnDeviceDataBox(lTColdCrit_); - other.rho_at_pmin_ = Spiner::getOnDeviceDataBox(rho_at_pmin_); - other.lRhoMin_ = lRhoMin_; - other.lRhoMax_ = lRhoMax_; - other.rhoMax_ = rhoMax_; - other.lRhoMinSearch_ = lRhoMinSearch_; - other.lTMin_ = lTMin_; - other.lTMax_ = lTMax_; - other.TMax_ = TMax_; - other.lRhoOffset_ = lRhoOffset_; - other.lTOffset_ = lTOffset_; - other.rhoNormal_ = rhoNormal_; - other.TNormal_ = TNormal_; - other.sieNormal_ = sieNormal_; - other.PNormal_ = PNormal_; - other.CvNormal_ = CvNormal_; - other.bModNormal_ = bModNormal_; - other.dPdENormal_ = dPdENormal_; - other.dVdTNormal_ = dVdTNormal_; - other.numRho_ = numRho_; - other.numT_ = numT_; - other.matid_ = matid_; - other.reproducible_ = reproducible_; - other.status_ = status_; - other.memoryStatus_ = DataStatus::OnDevice; - return other; + return SpinerTricks::GetOnDevice(this); } -void SpinerEOSDependsRhoT::Finalize() { - P_.finalize(); - sie_.finalize(); - bMod_.finalize(); - dPdRho_.finalize(); - dPdE_.finalize(); - dTdRho_.finalize(); - dTdE_.finalize(); - dEdRho_.finalize(); - dEdT_.finalize(); - PMax_.finalize(); - sielTMax_.finalize(); - dEdTMax_.finalize(); - gm1Max_.finalize(); - PCold_.finalize(); - sieCold_.finalize(); - bModCold_.finalize(); - dPdRhoCold_.finalize(); - dPdECold_.finalize(); - dTdRhoCold_.finalize(); - dEdTCold_.finalize(); - dTdECold_.finalize(); - lTColdCrit_.finalize(); - rho_at_pmin_.finalize(); - memoryStatus_ = DataStatus::Deallocated; +void SpinerEOSDependsRhoT::Finalize() { SpinerTricks::Finalize(this); } + +inline std::size_t SpinerEOSDependsRhoT::DynamicMemorySizeInBytes() const { + return SpinerTricks::DynamicMemorySizeInBytes(this); +} + +inline std::size_t SpinerEOSDependsRhoT::DumpDynamicMemory(char *dst) { + return SpinerTricks::DumpDynamicMemory(dst, this); +} + +inline std::size_t +SpinerEOSDependsRhoT::SetDynamicMemory(char *src, const SharedMemSettings &stngs) { + return SpinerTricks::SetDynamicMemory((stngs.data == nullptr) ? src : stngs.data, this); } inline herr_t SpinerEOSDependsRhoT::loadDataboxes_(const std::string &matid_str, @@ -1661,70 +1632,23 @@ inline void SpinerEOSDependsRhoSie::calcBMod_(SP5Tables &tables) { } inline SpinerEOSDependsRhoSie SpinerEOSDependsRhoSie::GetOnDevice() { - SpinerEOSDependsRhoSie other; - using Spiner::getOnDeviceDataBox; - other.sie_ = getOnDeviceDataBox(sie_); - other.T_ = getOnDeviceDataBox(T_); - other.dependsRhoT_.P = getOnDeviceDataBox(dependsRhoT_.P); - other.dependsRhoT_.bMod = getOnDeviceDataBox(dependsRhoT_.bMod); - other.dependsRhoT_.dPdRho = getOnDeviceDataBox(dependsRhoT_.dPdRho); - other.dependsRhoT_.dPdE = getOnDeviceDataBox(dependsRhoT_.dPdE); - other.dependsRhoT_.dTdRho = getOnDeviceDataBox(dependsRhoT_.dTdRho); - other.dependsRhoT_.dTdE = getOnDeviceDataBox(dependsRhoT_.dTdE); - other.dependsRhoT_.dEdRho = getOnDeviceDataBox(dependsRhoT_.dEdRho); - other.dependsRhoSie_.P = getOnDeviceDataBox(dependsRhoSie_.P); - other.dependsRhoSie_.bMod = getOnDeviceDataBox(dependsRhoSie_.bMod); - other.dependsRhoSie_.dPdRho = getOnDeviceDataBox(dependsRhoSie_.dPdRho); - other.dependsRhoSie_.dPdE = getOnDeviceDataBox(dependsRhoSie_.dPdE); - other.dependsRhoSie_.dTdRho = getOnDeviceDataBox(dependsRhoSie_.dTdRho); - other.dependsRhoSie_.dTdE = getOnDeviceDataBox(dependsRhoSie_.dTdE); - other.dependsRhoSie_.dEdRho = getOnDeviceDataBox(dependsRhoSie_.dEdRho); - other.numRho_ = numRho_; - other.lRhoMin_ = lRhoMin_; - other.lRhoMax_ = lRhoMax_; - other.rhoMax_ = rhoMax_; - other.PlRhoMax_ = getOnDeviceDataBox(PlRhoMax_); - other.dPdRhoMax_ = getOnDeviceDataBox(dPdRhoMax_); - other.lRhoOffset_ = lRhoOffset_; - other.lTOffset_ = lTOffset_; - other.lEOffset_ = lEOffset_; - other.rhoNormal_ = rhoNormal_; - other.TNormal_ = TNormal_; - other.sieNormal_ = sieNormal_; - other.PNormal_ = PNormal_; - other.CvNormal_ = CvNormal_; - other.bModNormal_ = bModNormal_; - other.dPdENormal_ = dPdENormal_; - other.dVdTNormal_ = dVdTNormal_; - other.matid_ = matid_; - other.reproducible_ = reproducible_; - other.status_ = status_; - other.memoryStatus_ = DataStatus::OnDevice; - return other; + return STricks::GetOnDevice(this); +} + +void SpinerEOSDependsRhoSie::Finalize() { STricks::Finalize(this); } + +inline std::size_t SpinerEOSDependsRhoSie::DynamicMemorySizeInBytes() const { + return STricks::DynamicMemorySizeInBytes(this); +} + +inline std::size_t SpinerEOSDependsRhoSie::DumpDynamicMemory(char *dst) { + return STricks::DumpDynamicMemory(dst, this); } -void SpinerEOSDependsRhoSie::Finalize() { - sie_.finalize(); - T_.finalize(); - dependsRhoT_.P.finalize(); - dependsRhoT_.bMod.finalize(); - dependsRhoT_.dPdRho.finalize(); - dependsRhoT_.dPdE.finalize(); - dependsRhoT_.dTdRho.finalize(); - dependsRhoT_.dTdE.finalize(); - dependsRhoT_.dEdRho.finalize(); - dependsRhoSie_.P.finalize(); - dependsRhoSie_.bMod.finalize(); - dependsRhoSie_.dPdRho.finalize(); - dependsRhoSie_.dPdE.finalize(); - dependsRhoSie_.dTdRho.finalize(); - dependsRhoSie_.dTdE.finalize(); - dependsRhoSie_.dEdRho.finalize(); - if (memoryStatus_ == DataStatus::OnDevice) { // these are slices on host - PlRhoMax_.finalize(); - dPdRhoMax_.finalize(); - } - memoryStatus_ = DataStatus::Deallocated; +inline std::size_t +SpinerEOSDependsRhoSie::SetDynamicMemory(char *src, const SharedMemSettings &stngs) { + if (stngs.data != nullptr) src = stngs.data; + return STricks::SetDynamicMemory(src, this); } template diff --git a/singularity-eos/eos/eos_stellar_collapse.hpp b/singularity-eos/eos/eos_stellar_collapse.hpp index fd9e98a3823..e9c9c500bdf 100644 --- a/singularity-eos/eos/eos_stellar_collapse.hpp +++ b/singularity-eos/eos/eos_stellar_collapse.hpp @@ -30,6 +30,7 @@ // ports-of-call #include +#include // singularity-eos #include @@ -37,6 +38,7 @@ #include #include #include +#include #include #include @@ -60,6 +62,7 @@ using namespace eos_base; // and introduce extrapolation as needed. class StellarCollapse : public EosBase { public: + friend class table_utils::SpinerTricks; using DataBox = Spiner::DataBox; using Grid_t = Spiner::RegularGrid1D; @@ -68,24 +71,7 @@ class StellarCollapse : public EosBase { enum Index { Ye = 0, lT = 1 }; }; - // Generic functions provided by the base class. These contain - // e.g. the vector overloads that use the scalar versions declared - // here We explicitly list, rather than using the macro because we - // overload some methods. - using EosBase::TemperatureFromDensityInternalEnergy; - using EosBase::InternalEnergyFromDensityTemperature; - using EosBase::PressureFromDensityTemperature; - using EosBase::PressureFromDensityInternalEnergy; - using EosBase::MinInternalEnergyFromDensity; - using EosBase::EntropyFromDensityTemperature; - using EosBase::EntropyFromDensityInternalEnergy; - using EosBase::SpecificHeatFromDensityTemperature; - using EosBase::SpecificHeatFromDensityInternalEnergy; - using EosBase::BulkModulusFromDensityTemperature; - using EosBase::BulkModulusFromDensityInternalEnergy; - using EosBase::GruneisenParamFromDensityTemperature; - using EosBase::GruneisenParamFromDensityInternalEnergy; - using EosBase::FillEos; + SG_ADD_BASE_CLASS_USINGS(StellarCollapse); inline StellarCollapse(const std::string &filename, bool use_sp5 = false, bool filter_bmod = true); @@ -96,6 +82,27 @@ class StellarCollapse : public EosBase { PORTABLE_INLINE_FUNCTION StellarCollapse() : memoryStatus_(DataStatus::Deallocated) {} + PORTABLE_INLINE_FUNCTION void CheckParams() const { + PORTABLE_ALWAYS_REQUIRE(numRho_ > 0, "Table must be finite"); + PORTABLE_ALWAYS_REQUIRE(numT_ > 0, "Table must be finite"); + PORTABLE_ALWAYS_REQUIRE(numYe_ > 0, "Table must be finite"); + PORTABLE_ALWAYS_REQUIRE(!std::isnan(lRhoMin_) && !std::isnan(lRhoMax_), + "Density bounds must be well defined"); + PORTABLE_ALWAYS_REQUIRE(lRhoMax_ > lRhoMin_, "Density bounds must be ordered"); + PORTABLE_ALWAYS_REQUIRE(!std::isnan(lTMin_) && !std::isnan(lTMax_), + "Density bounds must be well defined"); + PORTABLE_ALWAYS_REQUIRE(lTMax_ > lTMin_, "Temperature bounds must be ordered"); + PORTABLE_ALWAYS_REQUIRE(!(std::isnan(YeMin_) || std::isnan(YeMax_)), + "Ye bounds must be well defined"); + PORTABLE_ALWAYS_REQUIRE(YeMin_ >= 0.0, "Ye must be positive"); + PORTABLE_ALWAYS_REQUIRE(YeMax_ <= 1.0, "Ye must be a fraction"); + PORTABLE_ALWAYS_REQUIRE(YeMax_ > YeMin_, "Ye bounds must be ordered"); + PORTABLE_ALWAYS_REQUIRE(!(std::isnan(sieMin_) || std::isnan(sieMax_)), + "Energy bounds must be well defined"); + PORTABLE_ALWAYS_REQUIRE(sieMax_ > sieMin_, "Energy bounds must be ordered"); + return; + } + inline StellarCollapse GetOnDevice(); template @@ -219,6 +226,11 @@ class StellarCollapse : public EosBase { inline static void dataBoxToFastLogs(DataBox &db, DataBox &scratch, bool dependent_var_log); + std::size_t DynamicMemorySizeInBytes() const; + std::size_t DumpDynamicMemory(char *dst); + std::size_t SetDynamicMemory(char *src, + const SharedMemSettings &stngs = DEFAULT_SHMEM_STNGS); + private: class LogT { public: @@ -352,6 +364,14 @@ class StellarCollapse : public EosBase { // Bounds of dependent variables. Needed for root finding. DataBox eCold_, eHot_; + // TODO(JMM): Pointers here? or reference_wrapper? IMO the pointers are more clear +#define DBLIST \ + &lP_, &lE_, &dPdRho_, &dPdE_, &dEdT_, &lBMod_, &entropy_, &Xa_, &Xh_, &Xn_, &Xp_, \ + &Abar_, &Zbar_, &mu_e_, &mu_n_, &mu_p_, &muhat_, &munu_, &eCold_, &eHot_ + auto GetDataBoxPointers_() const { return std::vector{DBLIST}; } + auto GetDataBoxPointers_() { return std::vector{DBLIST}; } +#undef DBLIST + // Independent variable bounds int numRho_, numT_, numYe_; Real lRhoMin_, lRhoMax_; @@ -405,6 +425,7 @@ inline StellarCollapse::StellarCollapse(const std::string &filename, bool use_sp LoadFromStellarCollapseFile_(filename, filter_bmod); } setNormalValues_(); + CheckParams(); } // Saves to an SP5 file @@ -449,71 +470,25 @@ inline void StellarCollapse::Save(const std::string &filename) { } inline StellarCollapse StellarCollapse::GetOnDevice() { - StellarCollapse other; - other.lP_ = Spiner::getOnDeviceDataBox(lP_); - other.lE_ = Spiner::getOnDeviceDataBox(lE_); - other.dPdRho_ = Spiner::getOnDeviceDataBox(dPdRho_); - other.dPdE_ = Spiner::getOnDeviceDataBox(dPdE_); - other.dEdT_ = Spiner::getOnDeviceDataBox(dEdT_); - other.entropy_ = Spiner::getOnDeviceDataBox(entropy_); - other.Xa_ = Spiner::getOnDeviceDataBox(Xa_); - other.Xh_ = Spiner::getOnDeviceDataBox(Xh_); - other.Xn_ = Spiner::getOnDeviceDataBox(Xn_); - other.Xp_ = Spiner::getOnDeviceDataBox(Xp_); - other.Abar_ = Spiner::getOnDeviceDataBox(Abar_); - other.Zbar_ = Spiner::getOnDeviceDataBox(Zbar_); - other.lBMod_ = Spiner::getOnDeviceDataBox(lBMod_); - other.eCold_ = Spiner::getOnDeviceDataBox(eCold_); - other.eHot_ = Spiner::getOnDeviceDataBox(eHot_); - other.mu_e_ = Spiner::getOnDeviceDataBox(mu_e_); - other.mu_n_ = Spiner::getOnDeviceDataBox(mu_n_); - other.mu_p_ = Spiner::getOnDeviceDataBox(mu_p_); - other.muhat_ = Spiner::getOnDeviceDataBox(muhat_); - other.munu_ = Spiner::getOnDeviceDataBox(munu_); - other.memoryStatus_ = DataStatus::OnDevice; - other.numRho_ = numRho_; - other.numT_ = numT_; - other.numYe_ = numYe_; - other.lTMin_ = lTMin_; - other.lTMax_ = lTMax_; - other.YeMin_ = YeMin_; - other.YeMax_ = YeMax_; - other.sieMin_ = sieMin_; - other.sieMax_ = sieMax_; - other.lEOffset_ = lEOffset_; - other.sieNormal_ = sieNormal_; - other.PNormal_ = PNormal_; - other.SNormal_ = SNormal_; - other.CvNormal_ = CvNormal_; - other.bModNormal_ = bModNormal_; - other.dPdENormal_ = dPdENormal_; - other.dVdTNormal_ = dVdTNormal_; - other.status_ = status_; - return other; + return table_utils::SpinerTricks::GetOnDevice(this); } inline void StellarCollapse::Finalize() { - lP_.finalize(); - lE_.finalize(); - dPdRho_.finalize(); - dPdE_.finalize(); - dEdT_.finalize(); - entropy_.finalize(); - Xa_.finalize(); - Xh_.finalize(); - Xn_.finalize(); - Xp_.finalize(); - Abar_.finalize(); - Zbar_.finalize(); - lBMod_.finalize(); - eCold_.finalize(); - eHot_.finalize(); - mu_e_.finalize(); - mu_n_.finalize(); - mu_p_.finalize(); - muhat_.finalize(); - munu_.finalize(); - memoryStatus_ = DataStatus::Deallocated; + table_utils::SpinerTricks::Finalize(this); +} + +inline std::size_t StellarCollapse::DynamicMemorySizeInBytes() const { + return table_utils::SpinerTricks::DynamicMemorySizeInBytes(this); +} + +inline std::size_t StellarCollapse::DumpDynamicMemory(char *dst) { + return table_utils::SpinerTricks::DumpDynamicMemory(dst, this); +} + +inline std::size_t StellarCollapse::SetDynamicMemory(char *src, + const SharedMemSettings &stngs) { + if (stngs.data != nullptr) src = stngs.data; + return table_utils::SpinerTricks::SetDynamicMemory(src, this); } template diff --git a/singularity-eos/eos/eos_stiff.hpp b/singularity-eos/eos/eos_stiff.hpp index d0ca49e096b..4a64aeffc55 100644 --- a/singularity-eos/eos/eos_stiff.hpp +++ b/singularity-eos/eos/eos_stiff.hpp @@ -44,7 +44,7 @@ class StiffGas : public EosBase { _sie0(robust::ratio(_P0 + (gm1 + 1.0) * Pinf, _P0 + Pinf) * Cv * _T0 + qq), _bmod0(gm1 * (gm1 + 1.0) * robust::ratio(_P0 + Pinf, gm1 * Cv * _T0) * Cv * _T0), _dpde0(_rho0 * _gm1) { - checkParams(); + CheckParams(); } PORTABLE_INLINE_FUNCTION StiffGas(Real gm1, Real Cv, Real Pinf, Real qq, Real qp, Real T0, Real P0) @@ -54,7 +54,7 @@ class StiffGas : public EosBase { _sie0(robust::ratio(P0 + (gm1 + 1.0) * Pinf, P0 + Pinf) * Cv * T0 + qq), _bmod0(gm1 * (gm1 + 1.0) * robust::ratio(P0 + Pinf, gm1 * Cv * T0) * Cv * T0), _dpde0(_rho0 * _gm1) { - checkParams(); + CheckParams(); } StiffGas GetOnDevice() { return *this; } template @@ -63,7 +63,7 @@ class StiffGas : public EosBase { Indexer_t &&lambda = static_cast(nullptr)) const { return std::max(robust::SMALL(), robust::ratio(rho * (sie - _qq) - _Pinf, rho * _Cv)); } - PORTABLE_INLINE_FUNCTION void checkParams() const { + PORTABLE_INLINE_FUNCTION void CheckParams() const { PORTABLE_ALWAYS_REQUIRE(_Cv >= 0, "Heat capacity must be positive"); PORTABLE_ALWAYS_REQUIRE(_gm1 >= 0, "Gruneisen parameter must be positive"); } diff --git a/singularity-eos/eos/eos_variant.hpp b/singularity-eos/eos/eos_variant.hpp index 1a98dfdcc8e..507e8884234 100644 --- a/singularity-eos/eos/eos_variant.hpp +++ b/singularity-eos/eos/eos_variant.hpp @@ -1,5 +1,5 @@ //------------------------------------------------------------------------------ -// © 2021-2023. Triad National Security, LLC. All rights reserved. This +// © 2021-2024. 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 @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -49,7 +50,7 @@ class Variant { PORTABLE_FUNCTION Variant(EOSChoice &&choice) : eos_(std::move(std::forward(choice))) {} - Variant() noexcept = default; + Variant() = default; template constexpr void Evaluate(Functor_t &f) const { return mpark::visit([&f](const auto &eos) { return eos.Evaluate(f); }, eos_); @@ -1013,6 +1019,79 @@ class Variant { eos_); } + // Serialization + /* + The methodology here is there are *three* size methods all EOS's provide: + - `SharedMemorySizeInBytes()` which is the amount of memory a class can share + - `DynamicMemorySizeInBytes()` which is the amount of memory not covered by + `sizeof(this)` + - `SerializedSizeInBytes()` which is the total size of the object. + + I wanted serialization machinery to work if you use a standalone + class or if you use the variant. To make that possible, each class + provides its own implementation of `SharedMemorySizeInBytes` and + `DynamicMemorySizeInBytes()`. But then there is a separate + implementation for the variant and for the base class for + `SerializedSizeInBytes`, `Serialize`, and `DeSerialize`. + */ + // JMM: This must be implemented separately for Variant vs the base + // class/individual EOS's so that the variant state is properly + // carried. Otherwise de-serialization would need to specify a type. + std::size_t DynamicMemorySizeInBytes() const { + return mpark::visit([](const auto &eos) { return eos.DynamicMemorySizeInBytes(); }, + eos_); + } + std::size_t DumpDynamicMemory(char *dst) { + return mpark::visit([dst](auto &eos) { return eos.DumpDynamicMemory(dst); }, eos_); + } + std::size_t SetDynamicMemory(char *src, + const SharedMemSettings &stngs = DEFAULT_SHMEM_STNGS) { + return mpark::visit( + [src, stngs](auto &eos) { return eos.SetDynamicMemory(src, stngs); }, eos_); + } + std::size_t SharedMemorySizeInBytes() const { + return mpark::visit([](const auto &eos) { return eos.SharedMemorySizeInBytes(); }, + eos_); + } + constexpr bool AllDynamicMemoryIsShareable() const { + return mpark::visit([](const auto &eos) { return eos.AllDynamicMemoryIsShareable(); }, + eos_); + } + std::size_t SerializedSizeInBytes() const { + return sizeof(*this) + DynamicMemorySizeInBytes(); + } + std::size_t Serialize(char *dst) { + memcpy(dst, this, sizeof(*this)); + std::size_t offst = sizeof(*this); + std::size_t dyn_size = DynamicMemorySizeInBytes(); + if (dyn_size > 0) { + offst += DumpDynamicMemory(dst + offst); + } + PORTABLE_ALWAYS_REQUIRE(offst == SerializedSizeInBytes(), "Serialization failed!"); + return offst; + } + auto Serialize() { + std::size_t size = SerializedSizeInBytes(); + char *dst = (char *)malloc(size); + std::size_t new_size = Serialize(dst); + PORTABLE_ALWAYS_REQUIRE(size == new_size, "Serialization failed!"); + return std::make_pair(size, dst); + } + std::size_t DeSerialize(char *src, + const SharedMemSettings &stngs = DEFAULT_SHMEM_STNGS) { + memcpy(this, src, sizeof(*this)); + std::size_t offst = sizeof(*this); + std::size_t dyn_size = DynamicMemorySizeInBytes(); + if (dyn_size > 0) { + const bool sizes_same = AllDynamicMemoryIsShareable(); + if (stngs.CopyNeeded() && sizes_same) { + memcpy(stngs.data, src + offst, dyn_size); + } + offst += SetDynamicMemory(src + offst, stngs); + } + return offst; + } + // Tooling for modifiers inline constexpr bool IsModified() const { return mpark::visit([](const auto &eos) { return eos.IsModified(); }, eos_); diff --git a/singularity-eos/eos/eos_vinet.hpp b/singularity-eos/eos/eos_vinet.hpp index e7bbf8e8092..3b5e0204b66 100644 --- a/singularity-eos/eos/eos_vinet.hpp +++ b/singularity-eos/eos/eos_vinet.hpp @@ -42,9 +42,10 @@ class Vinet : public EosBase { Vinet(const Real rho0, const Real T0, const Real B0, const Real BP0, const Real A0, const Real Cv0, const Real E0, const Real S0, const Real *expconsts) : _rho0(rho0), _T0(T0), _B0(B0), _BP0(BP0), _A0(A0), _Cv0(Cv0), _E0(E0), _S0(S0) { - CheckVinet(); + CheckParams(); InitializeVinet(expconsts); } + PORTABLE_INLINE_FUNCTION void CheckParams() const; Vinet GetOnDevice() { return *this; } template @@ -163,13 +164,12 @@ class Vinet : public EosBase { static constexpr const int PressureCoeffsd2tod40Size = 39; static constexpr const int VinetInternalParametersSize = PressureCoeffsd2tod40Size + 4; Real _VIP[VinetInternalParametersSize], _d2tod40[PressureCoeffsd2tod40Size]; - void CheckVinet(); void InitializeVinet(const Real *expcoeffs); PORTABLE_INLINE_FUNCTION void Vinet_F_DT_func(const Real rho, const Real T, Real *output) const; }; -inline void Vinet::CheckVinet() { +PORTABLE_INLINE_FUNCTION void Vinet::CheckParams() const { if (_rho0 < 0.0) { PORTABLE_ALWAYS_THROW_OR_ABORT("Required Vinet model parameter rho0 < 0"); diff --git a/singularity-eos/eos/modifiers/eos_unitsystem.hpp b/singularity-eos/eos/modifiers/eos_unitsystem.hpp index 5d513fd5433..cb98a373ae2 100644 --- a/singularity-eos/eos/modifiers/eos_unitsystem.hpp +++ b/singularity-eos/eos/modifiers/eos_unitsystem.hpp @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -43,29 +44,7 @@ static struct LengthTimeUnitsInit { template class UnitSystem : public EosBase> { public: - // Generic functions provided by the base class. These contain - // e.g. the vector overloads that use the scalar versions declared - // here We explicitly list, rather than using the macro because we - // overload some methods. - - // TODO(JMM): The modifier EOS's should probably call the specific - // sub-functions of the class they modify so that they can leverage, - // e.g., an especially performant or special version of these - using EosBase>::TemperatureFromDensityInternalEnergy; - using EosBase>::InternalEnergyFromDensityTemperature; - using EosBase>::PressureFromDensityTemperature; - using EosBase>::PressureFromDensityInternalEnergy; - using EosBase>::MinInternalEnergyFromDensity; - using EosBase>::EntropyFromDensityTemperature; - using EosBase>::EntropyFromDensityInternalEnergy; - using EosBase>::SpecificHeatFromDensityTemperature; - using EosBase>::SpecificHeatFromDensityInternalEnergy; - using EosBase>::BulkModulusFromDensityTemperature; - using EosBase>::BulkModulusFromDensityInternalEnergy; - using EosBase>::GruneisenParamFromDensityTemperature; - using EosBase>::GruneisenParamFromDensityInternalEnergy; - using EosBase>::FillEos; - + SG_ADD_BASE_CLASS_USINGS(UnitSystem); using BaseType = T; // give me std::format or fmt::format... @@ -93,7 +72,9 @@ class UnitSystem : public EosBase> { inv_dpdr_unit_(rho_unit / press_unit_), inv_dtdr_unit_(rho_unit / temp_unit), inv_dtde_unit_(sie_unit / temp_unit) // obviously this is also Cv , - inv_cv_unit_(temp_unit / sie_unit), inv_bmod_unit_(1 / press_unit_) {} + inv_cv_unit_(temp_unit / sie_unit), inv_bmod_unit_(1 / press_unit_) { + CheckParams(); + } UnitSystem(T &&t, eos_units_init::LengthTimeUnitsInit, const Real time_unit, const Real mass_unit, const Real length_unit, const Real temp_unit) : UnitSystem(std::forward(t), eos_units_init::thermal_units_init_tag, @@ -104,6 +85,13 @@ class UnitSystem : public EosBase> { sie_unit, temp_unit) {} UnitSystem() = default; + PORTABLE_INLINE_FUNCTION void CheckParams() const { + PORTABLE_ALWAYS_REQUIRE(rho_unit_ > 0, "Nonzero density unit"); + PORTABLE_ALWAYS_REQUIRE(sie_unit_ > 0, "Nonzero energy unit"); + PORTABLE_ALWAYS_REQUIRE(temp_unit_ > 0, "Nonzero temperature unit"); + t_.CheckParams(); + } + auto GetOnDevice() { return UnitSystem(t_.GetOnDevice(), eos_units_init::thermal_units_init_tag, rho_unit_, sie_unit_, temp_unit_); @@ -437,13 +425,7 @@ class UnitSystem : public EosBase> { printf("Units = %e %e %e %e\n", rho_unit_, sie_unit_, temp_unit_, press_unit_); } - inline constexpr bool IsModified() const { return true; } - - inline constexpr T UnmodifyOnce() { return t_; } - - inline constexpr decltype(auto) GetUnmodifiedObject() { - return t_.GetUnmodifiedObject(); - } + SG_ADD_MODIFIER_METHODS(T, t_); private: T t_; diff --git a/singularity-eos/eos/modifiers/ramps_eos.hpp b/singularity-eos/eos/modifiers/ramps_eos.hpp index d9c46048c81..a0ce08db2ed 100644 --- a/singularity-eos/eos/modifiers/ramps_eos.hpp +++ b/singularity-eos/eos/modifiers/ramps_eos.hpp @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -83,15 +84,22 @@ class BilinearRampEOS : public EosBase> { : t_(std::forward(t)), r0_(r0), a_(a), b_(b), c_(c), rmid_(r0 * (a - b * c) / (a - b)), Pmid_(a * (rmid_ / r0 - 1.0)) { // add input parameter checks to ensure validity of the ramp - assert(r0 > 0.0); - assert(a > 0.0); - assert(b >= 0); - assert(a != b); + CheckParams(); } BilinearRampEOS() = default; using BaseType = T; + PORTABLE_INLINE_FUNCTION void CheckParams() const { + PORTABLE_ALWAYS_REQUIRE(r0_ > 0.0, "Reference density > 0"); + PORTABLE_ALWAYS_REQUIRE(a_ > 0.0, "Ramp a coefficient > 0"); + PORTABLE_ALWAYS_REQUIRE(b_ >= 0, "Non-negative ramp b coefficient"); + PORTABLE_ALWAYS_REQUIRE(a_ != b_, "Ramp a and b coefficients may not be the same"); + PORTABLE_ALWAYS_REQUIRE(!std::isnan(rmid_), "Mid density must be well defined"); + PORTABLE_ALWAYS_REQUIRE(!std::isnan(Pmid_), "Mid pressure must be well defined"); + t_.CheckParams(); + } + // give me std::format or fmt::format... static std::string EosType() { return std::string("BilinearRampEOS<") + T::EosType() + std::string(">"); @@ -444,13 +452,7 @@ class BilinearRampEOS : public EosBase> { t_.ValuesAtReferenceState(rho, temp, sie, press, cv, bmod, dpde, dvdt, lambda); } - inline constexpr bool IsModified() const { return true; } - - inline constexpr T UnmodifyOnce() { return t_; } - - inline constexpr decltype(auto) GetUnmodifiedObject() { - return t_.GetUnmodifiedObject(); - } + SG_ADD_MODIFIER_METHODS(T, t_); private: T t_; diff --git a/singularity-eos/eos/modifiers/relativistic_eos.hpp b/singularity-eos/eos/modifiers/relativistic_eos.hpp index 47726b8c6e4..0fd296d78db 100644 --- a/singularity-eos/eos/modifiers/relativistic_eos.hpp +++ b/singularity-eos/eos/modifiers/relativistic_eos.hpp @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -35,29 +36,7 @@ using namespace eos_base; template class RelativisticEOS : public EosBase> { public: - // Generic functions provided by the base class. These contain - // e.g. the vector overloads that use the scalar versions declared - // here We explicitly list, rather than using the macro because we - // overload some methods. - - // TODO(JMM): The modifier EOS's should probably call the specific - // sub-functions of the class they modify so that they can leverage, - // e.g., an especially performant or special version of these - using EosBase>::TemperatureFromDensityInternalEnergy; - using EosBase>::InternalEnergyFromDensityTemperature; - using EosBase>::PressureFromDensityTemperature; - using EosBase>::PressureFromDensityInternalEnergy; - using EosBase>::MinInternalEnergyFromDensity; - using EosBase>::EntropyFromDensityTemperature; - using EosBase>::EntropyFromDensityInternalEnergy; - using EosBase>::SpecificHeatFromDensityTemperature; - using EosBase>::SpecificHeatFromDensityInternalEnergy; - using EosBase>::BulkModulusFromDensityTemperature; - using EosBase>::BulkModulusFromDensityInternalEnergy; - using EosBase>::GruneisenParamFromDensityTemperature; - using EosBase>::GruneisenParamFromDensityInternalEnergy; - using EosBase>::FillEos; - + SG_ADD_BASE_CLASS_USINGS(RelativisticEOS); using BaseType = T; // give me std::format or fmt::format... @@ -72,9 +51,19 @@ class RelativisticEOS : public EosBase> { : t_(std::forward(t)), cl_(cl) // speed of light, units arbitrary , cl2_(cl * cl) // speed of light squared - {} + { + CheckParams(); + } RelativisticEOS() = default; + PORTABLE_INLINE_FUNCTION void CheckParams() const { + PORTABLE_ALWAYS_REQUIRE(cl_ > 0, "Positive speed of light"); + PORTABLE_ALWAYS_REQUIRE(cl2_ > 0, "Positive speed of light squared"); + PORTABLE_ALWAYS_REQUIRE(!std::isnan(cl_), "Well defined speed of light"); + PORTABLE_ALWAYS_REQUIRE(!std::isnan(cl2_), "Well defined speed of light squared"); + t_.CheckParams(); + } + auto GetOnDevice() { return RelativisticEOS(t_.GetOnDevice(), cl_); } inline void Finalize() { t_.Finalize(); } @@ -193,13 +182,7 @@ class RelativisticEOS : public EosBase> { t_.ValuesAtReferenceState(rho, temp, sie, press, cv, bmod, dpde, dvdt, lambda); } - inline constexpr bool IsModified() const { return true; } - - inline constexpr T UnmodifyOnce() { return t_; } - - inline constexpr decltype(auto) GetUnmodifiedObject() { - return t_.GetUnmodifiedObject(); - } + SG_ADD_MODIFIER_METHODS(T, t_); private: T t_; diff --git a/singularity-eos/eos/modifiers/scaled_eos.hpp b/singularity-eos/eos/modifiers/scaled_eos.hpp index 930e5baa83d..bf64e90a042 100644 --- a/singularity-eos/eos/modifiers/scaled_eos.hpp +++ b/singularity-eos/eos/modifiers/scaled_eos.hpp @@ -1,5 +1,5 @@ //------------------------------------------------------------------------------ -// © 2021-2023. Triad National Security, LLC. All rights reserved. This +// © 2021-2024. 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 @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -35,29 +36,7 @@ using namespace eos_base; template class ScaledEOS : public EosBase> { public: - // Generic functions provided by the base class. These contain - // e.g. the vector overloads that use the scalar versions declared - // here We explicitly list, rather than using the macro because we - // overload some methods. - - // TODO(JMM): The modifier EOS's should probably call the specific - // sub-functions of the class they modify so that they can leverage, - // e.g., an especially performant or special version of these - using EosBase>::TemperatureFromDensityInternalEnergy; - using EosBase>::InternalEnergyFromDensityTemperature; - using EosBase>::PressureFromDensityTemperature; - using EosBase>::PressureFromDensityInternalEnergy; - using EosBase>::MinInternalEnergyFromDensity; - using EosBase>::EntropyFromDensityTemperature; - using EosBase>::EntropyFromDensityInternalEnergy; - using EosBase>::SpecificHeatFromDensityTemperature; - using EosBase>::SpecificHeatFromDensityInternalEnergy; - using EosBase>::BulkModulusFromDensityTemperature; - using EosBase>::BulkModulusFromDensityInternalEnergy; - using EosBase>::GruneisenParamFromDensityTemperature; - using EosBase>::GruneisenParamFromDensityInternalEnergy; - using EosBase>::FillEos; - + SG_ADD_BASE_CLASS_USINGS(ScaledEOS); using BaseType = T; // give me std::format or fmt::format... @@ -69,9 +48,19 @@ class ScaledEOS : public EosBase> { // move semantics ensures dynamic memory comes along for the ride ScaledEOS(T &&t, const Real scale) - : t_(std::forward(t)), scale_(scale), inv_scale_(1. / scale) {} + : t_(std::forward(t)), scale_(scale), inv_scale_(1. / scale) { + CheckParams(); + } ScaledEOS() = default; + PORTABLE_INLINE_FUNCTION void CheckParams() const { + PORTABLE_ALWAYS_REQUIRE(std::abs(scale_) > 0, "Scale must not be zero."); + PORTABLE_ALWAYS_REQUIRE(std::abs(inv_scale_) > 0, "Inverse scale must not be zero."); + PORTABLE_ALWAYS_REQUIRE(!std::isnan(scale_), "Scale must be well defined."); + PORTABLE_ALWAYS_REQUIRE(!std::isnan(inv_scale_), + "Inverse scale must be well defined."); + t_.CheckParams(); + } auto GetOnDevice() { return ScaledEOS(t_.GetOnDevice(), scale_); } inline void Finalize() { t_.Finalize(); } @@ -353,13 +342,7 @@ class ScaledEOS : public EosBase> { return t_.MinimumTemperature(); } - inline constexpr bool IsModified() const { return true; } - - inline constexpr T UnmodifyOnce() { return t_; } - - inline constexpr decltype(auto) GetUnmodifiedObject() { - return t_.GetUnmodifiedObject(); - } + SG_ADD_MODIFIER_METHODS(T, t_); private: T t_; diff --git a/singularity-eos/eos/modifiers/shifted_eos.hpp b/singularity-eos/eos/modifiers/shifted_eos.hpp index d36a243135a..79eaad6e4e8 100644 --- a/singularity-eos/eos/modifiers/shifted_eos.hpp +++ b/singularity-eos/eos/modifiers/shifted_eos.hpp @@ -1,5 +1,5 @@ //------------------------------------------------------------------------------ -// © 2021-2023. Triad National Security, LLC. All rights reserved. This +// © 2021-2024. 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 @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -35,29 +36,7 @@ using namespace eos_base; template class ShiftedEOS : public EosBase> { public: - // Generic functions provided by the base class. These contain - // e.g. the vector overloads that use the scalar versions declared - // here We explicitly list, rather than using the macro because we - // overload some methods. - - // TODO(JMM): The modifier EOS's should probably call the specific - // sub-functions of the class they modify so that they can leverage, - // e.g., an especially performant or special version of these - using EosBase>::TemperatureFromDensityInternalEnergy; - using EosBase>::InternalEnergyFromDensityTemperature; - using EosBase>::PressureFromDensityTemperature; - using EosBase>::PressureFromDensityInternalEnergy; - using EosBase>::MinInternalEnergyFromDensity; - using EosBase>::EntropyFromDensityTemperature; - using EosBase>::EntropyFromDensityInternalEnergy; - using EosBase>::SpecificHeatFromDensityTemperature; - using EosBase>::SpecificHeatFromDensityInternalEnergy; - using EosBase>::BulkModulusFromDensityTemperature; - using EosBase>::BulkModulusFromDensityInternalEnergy; - using EosBase>::GruneisenParamFromDensityTemperature; - using EosBase>::GruneisenParamFromDensityInternalEnergy; - using EosBase>::FillEos; - + SG_ADD_BASE_CLASS_USINGS(ShiftedEOS); using BaseType = T; // give me std::format or fmt::format... @@ -68,9 +47,16 @@ class ShiftedEOS : public EosBase> { static std::string EosPyType() { return std::string("Shifted") + T::EosPyType(); } // move semantics ensures dynamic memory comes along for the ride - ShiftedEOS(T &&t, const Real shift) : t_(std::forward(t)), shift_(shift) {} + ShiftedEOS(T &&t, const Real shift) : t_(std::forward(t)), shift_(shift) { + CheckParams(); + } ShiftedEOS() = default; + PORTABLE_INLINE_FUNCTION void CheckParams() const { + PORTABLE_ALWAYS_REQUIRE(!std::isnan(shift_), "Shift must be a number"); + t_.CheckParams(); + } + auto GetOnDevice() { return ShiftedEOS(t_.GetOnDevice(), shift_); } inline void Finalize() { t_.Finalize(); } @@ -366,13 +352,7 @@ class ShiftedEOS : public EosBase> { return t_.MinimumTemperature(); } - inline constexpr bool IsModified() const { return true; } - - inline constexpr T UnmodifyOnce() { return t_; } - - inline constexpr decltype(auto) GetUnmodifiedObject() { - return t_.GetUnmodifiedObject(); - } + SG_ADD_MODIFIER_METHODS(T, t_); private: T t_; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b7009c57f19..bca7e9e0611 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -41,6 +41,7 @@ add_executable( eos_tabulated_unit_tests catch2_define.cpp eos_unit_test_helpers.hpp + test_spiner_tricks.cpp test_eos_helmholtz.cpp test_eos_tabulated.cpp test_eos_stellar_collapse.cpp diff --git a/test/test_bounds.cpp b/test/test_bounds.cpp index 6dcd06c637f..c291df06107 100644 --- a/test/test_bounds.cpp +++ b/test/test_bounds.cpp @@ -19,7 +19,7 @@ #include #include -#include +#include #ifndef CATCH_CONFIG_FAST_COMPILE #define CATCH_CONFIG_FAST_COMPILE diff --git a/test/test_eos_helmholtz.cpp b/test/test_eos_helmholtz.cpp index c12af5f866c..01a2aa11038 100644 --- a/test/test_eos_helmholtz.cpp +++ b/test/test_eos_helmholtz.cpp @@ -1,5 +1,5 @@ //------------------------------------------------------------------------------ -// © 2021-2023. Triad National Security, LLC. All rights reserved. This +// © 2021-2024. 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 @@ -45,6 +45,16 @@ SCENARIO("Helmholtz equation of state - Table interpolation (tgiven)", "[Helmhol Helmholtz eos = host_eos.GetOnDevice(); THEN("We loaded the file!") { REQUIRE(true); } + Helmholtz host_eos_2; + auto [size, data] = host_eos.Serialize(); + auto read_size = host_eos_2.DeSerialize(data); + THEN("We can serialize!") { + host_eos_2.CheckParams(); + REQUIRE(size == read_size); + REQUIRE(size > sizeof(Helmholtz)); + } + Helmholtz eos_2 = host_eos_2.GetOnDevice(); + /* Compare test values. Difference should be less than 1e-10 */ #ifdef PORTABILITY_STRATEGY_KOKKOS int nwrong = 1; // != 0 @@ -116,6 +126,22 @@ SCENARIO("Helmholtz equation of state - Table interpolation (tgiven)", "[Helmhol Real gruen = eos.GruneisenParamFromDensityTemperature(rho_in[i], temp_in[j], lambda); + if (!isClose(ein, ein_ref[k], 1e-10)) nwrong += 1; + if (!isClose(press, press_ref[k], 1e-10)) nwrong += 1; + if (!isClose(cv, cv_ref[k], 1e-6)) nwrong += 1; + if (!isClose(bulkmod, bulkmod_ref[k], 1e-8)) nwrong += 1; + if (!isClose(gruen, gruen_ref[k], 1e-6)) nwrong += 1; + + ein = eos_2.InternalEnergyFromDensityTemperature(rho_in[i], temp_in[j], + lambda); + press = eos_2.PressureFromDensityTemperature(rho_in[i], temp_in[j], lambda); + cv = + eos_2.SpecificHeatFromDensityTemperature(rho_in[i], temp_in[j], lambda); + bulkmod = + eos_2.BulkModulusFromDensityTemperature(rho_in[i], temp_in[j], lambda); + gruen = eos_2.GruneisenParamFromDensityTemperature(rho_in[i], temp_in[j], + lambda); + if (!isClose(ein, ein_ref[k], 1e-10)) nwrong += 1; if (!isClose(press, press_ref[k], 1e-10)) nwrong += 1; if (!isClose(cv, cv_ref[k], 1e-6)) nwrong += 1; @@ -127,6 +153,8 @@ SCENARIO("Helmholtz equation of state - Table interpolation (tgiven)", "[Helmhol }, nwrong); REQUIRE(nwrong == 0); + eos.Finalize(); + host_eos.Finalize(); } } @@ -198,6 +226,8 @@ SCENARIO("Helmholtz equation of state - Root finding (egiven)", "[HelmholtzEOS]" }, nwrong); REQUIRE(nwrong == 0); + eos.Finalize(); + host_eos.Finalize(); } } diff --git a/test/test_eos_ideal.cpp b/test/test_eos_ideal.cpp index 211ef6b3ba3..d33c883ea01 100644 --- a/test/test_eos_ideal.cpp +++ b/test/test_eos_ideal.cpp @@ -1,5 +1,5 @@ //------------------------------------------------------------------------------ -// © 2021-2023. Triad National Security, LLC. All rights reserved. This +// © 2021-2024. 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 @@ -137,3 +137,65 @@ SCENARIO("Ideal gas vector Evaluate call", "[IdealGas][Evaluate]") { PORTABLE_FREE(sie); } } + +struct Dummy {}; +SCENARIO("Ideal gas serialization", "[IdealGas][Serialization]") { + GIVEN("An ideal gas object on host and a variant on host") { + constexpr Real Cv = 2.0; + constexpr Real gm1 = 0.5; + IdealGas eos_bare(gm1, Cv); + EOS eos_variant = IdealGas(gm1, Cv); + + THEN("They both report zero dynamic memory size") { + REQUIRE(eos_bare.DynamicMemorySizeInBytes() == 0); + REQUIRE(eos_variant.DynamicMemorySizeInBytes() == 0); + } + + THEN("They both report sizes larger than a trivial struct, such that the eos variant " + "size >= eos_bare size") { + REQUIRE(eos_bare.SerializedSizeInBytes() > sizeof(Dummy)); + REQUIRE(eos_variant.SerializedSizeInBytes() > sizeof(Dummy)); + REQUIRE(eos_variant.SerializedSizeInBytes() >= eos_bare.SerializedSizeInBytes()); + } + + WHEN("We serialize each") { + auto [size_bare, data_bare] = eos_bare.Serialize(); + auto [size_var, data_var] = eos_variant.Serialize(); + + THEN("The reported sizes are what we expect") { + REQUIRE(size_bare == eos_bare.SerializedSizeInBytes()); + REQUIRE(size_var == eos_variant.SerializedSizeInBytes()); + } + + THEN("We can de-serialize new objects from them") { + IdealGas new_bare; + new_bare.DeSerialize(data_bare); + + EOS new_variant; + new_variant.DeSerialize(data_var); + + AND_THEN("The bare eos has the right Cv and Gruneisen params") { + REQUIRE(new_bare.SpecificHeatFromDensityTemperature(1.0, 1.0) == Cv); + REQUIRE(new_bare.GruneisenParamFromDensityTemperature(1.0, 1.0) == gm1); + } + + AND_THEN("The variant has the right type") { + REQUIRE(new_variant.IsType()); + } + + AND_THEN("The bare eos has the right Cv and Gruneisen params") { + REQUIRE(new_variant.SpecificHeatFromDensityTemperature(1.0, 1.0) == Cv); + REQUIRE(new_variant.GruneisenParamFromDensityTemperature(1.0, 1.0) == gm1); + } + } + + // cleanup + free(data_bare); + free(data_var); + } + + // cleanup + eos_bare.Finalize(); + eos_variant.Finalize(); + } +} diff --git a/test/test_eos_modifiers.cpp b/test/test_eos_modifiers.cpp index 6adadf9b214..e9ac142bd86 100644 --- a/test/test_eos_modifiers.cpp +++ b/test/test_eos_modifiers.cpp @@ -1,5 +1,5 @@ //------------------------------------------------------------------------------ -// © 2021-2023. Triad National Security, LLC. All rights reserved. This +// © 2021-2024. 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,11 +12,14 @@ // publicly and display publicly, and to permit others to do so. //------------------------------------------------------------------------------ +#include + #include #include #include #include #include +#include #include #include @@ -289,13 +292,66 @@ SCENARIO("EOS Unit System", "[EOSBuilder][UnitSystem][IdealGas]") { eos = Modify(eos, eos_units_init::length_time_units_init_tag, time_unit, mass_unit, length_unit, temp_unit); THEN("Units cancel out for an ideal gas") { - Real rho = 1e3; - Real sie = 1e3; + constexpr Real rho = 1e3; + constexpr Real sie = 1e3; + constexpr Real Ptrue = gm1 * rho * sie; Real P = eos.PressureFromDensityInternalEnergy(rho, sie); - Real Ptrue = gm1 * rho * sie; REQUIRE(std::abs(P - Ptrue) / Ptrue < 1e-3); } } } } } + +SCENARIO("Serialization of modified EOSs preserves their properties", + "[ScaledEOS][IdealGas][Serialization]") { + GIVEN("A scaled ideal gas object") { + constexpr Real Cv = 2.0; + constexpr Real gm1 = 0.5; + constexpr Real scale = 2.0; + + constexpr Real rho_test = 1.0; + constexpr Real sie_test = 1.0; + constexpr Real temp_trivial = sie_test / (Cv); // = 1./2. + constexpr Real temp_test = sie_test / (Cv * scale); // = 1./4. + constexpr Real EPS = 10 * std::numeric_limits::epsilon(); + + ScaledEOS eos(IdealGas(gm1, Cv), scale); + REQUIRE(isClose(eos.TemperatureFromDensityInternalEnergy(rho_test, sie_test), + temp_test, EPS)); + EOS eos_scaled = eos; + + EOS eos_trivial = ScaledEOS(IdealGas(gm1, Cv), 1.0); + REQUIRE(isClose(eos_trivial.TemperatureFromDensityInternalEnergy(rho_test, sie_test), + temp_trivial, EPS)); + + THEN("The size of the object is larger than just the ideal gas by itself") { + REQUIRE(eos.SerializedSizeInBytes() > sizeof(IdealGas)); + } + + WHEN("We serialize the EOS") { + singularity::VectorSerializer serializer({eos_scaled, eos_trivial}); + auto [size, data] = serializer.Serialize(); + REQUIRE(size == serializer.SerializedSizeInBytes()); + REQUIRE(size > 0); + REQUIRE(data != nullptr); + + THEN("We can de-serialize the EOS") { + singularity::VectorSerializer deserializer; + deserializer.DeSerialize(data); + REQUIRE(deserializer.Size() == serializer.Size()); + + AND_THEN("The de-serialized EOS still evaluates properly") { + auto eos_new = deserializer.eos_objects[0]; + REQUIRE( + isClose(eos_new.TemperatureFromDensityInternalEnergy(rho_test, sie_test), + temp_test, EPS)); + } + } + + free(data); + } + + eos.Finalize(); + } +} diff --git a/test/test_eos_stellar_collapse.cpp b/test/test_eos_stellar_collapse.cpp index e4969001283..8b0a9fcfe12 100644 --- a/test/test_eos_stellar_collapse.cpp +++ b/test/test_eos_stellar_collapse.cpp @@ -37,6 +37,78 @@ #ifdef SPINER_USE_HDF #ifdef SINGULARITY_TEST_STELLAR_COLLAPSE + +#include + +template +void CompareStellarCollapse(EOS_t sc, EOS_t sc2) { + Real yemin = sc.YeMin(); + Real yemax = sc.YeMax(); + Real tmin = sc.TMin(); + Real tmax = sc.TMax(); + Real ltmin = std::log10(tmin); + Real ltmax = std::log10(tmax); + Real lrhomin = std::log10(sc.rhoMin()); + Real lrhomax = std::log10(sc.rhoMax()); + REQUIRE(yemin == sc2.YeMin()); + REQUIRE(yemax == sc2.YeMax()); + REQUIRE(sc.TMin() == sc2.TMin()); + REQUIRE(sc.TMax() == sc2.TMax()); + REQUIRE(isClose(lrhomin, std::log10(sc2.rhoMin()), 1e-12)); + REQUIRE(isClose(lrhomax, std::log10(sc2.rhoMax()), 1e-12)); + + auto sc1_d = sc.GetOnDevice(); + auto sc2_d = sc2.GetOnDevice(); + + int nwrong_h = 0; +#ifdef PORTABILITY_STRATEGY_KOKKOS + using atomic_view = Kokkos::MemoryTraits; + Kokkos::View nwrong_d("wrong"); +#else + PortableMDArray nwrong_d(&nwrong_h, 1); +#endif + + const int N = 123; + constexpr Real gamma = 1.4; + const Real dY = (yemax - yemin) / (N + 1); + const Real dlT = (ltmax - ltmin) / (N + 1); + const Real dlR = (lrhomax - lrhomin) / (N + 1); + portableFor( + "fill eos", 0, N, 0, N, 0, N, + PORTABLE_LAMBDA(const int &k, const int &j, const int &i) { + Real lambda[2]; + Real Ye = yemin + k * dY; + Real lT = ltmin + j * dlT; + Real lR = lrhomin + i * dlR; + Real T = std::pow(10., lT); + Real R = std::pow(10., lR); + Real e1, e2, p1, p2, cv1, cv2, b1, b2, s1, s2; + unsigned long output = (singularity::thermalqs::pressure | + singularity::thermalqs::specific_internal_energy | + singularity::thermalqs::specific_heat | + singularity::thermalqs::bulk_modulus); + lambda[0] = Ye; + + sc1_d.FillEos(R, T, e1, p1, cv1, b1, output, lambda); + sc2_d.FillEos(R, T, e2, p2, cv2, b2, output, lambda); + // Fill entropy. Will need to change later. + s1 = sc1_d.EntropyFromDensityTemperature(R, T, lambda); + s2 = p2 * std::pow(R, -gamma); // ideal + if (!isClose(e1, e2)) nwrong_d() += 1; + if (!isClose(p1, p2)) nwrong_d() += 1; + if (!isClose(cv1, cv2)) nwrong_d() += 1; + if (!isClose(b1, b2)) nwrong_d() += 1; + if (!isClose(s1, s2)) nwrong_d() += 1; + }); +#ifdef PORTABILITY_STRATEGY_KOKKOS + Kokkos::deep_copy(nwrong_h, nwrong_d); +#endif + REQUIRE(nwrong_h == 0); + + sc1_d.Finalize(); + sc2_d.Finalize(); +} + SCENARIO("Test 3D reinterpolation to fast log grid", "[StellarCollapse]") { WHEN("We generate a 3D DataBox of a 2D power law times a line") { using singularity::StellarCollapse; @@ -91,8 +163,6 @@ SCENARIO("Test 3D reinterpolation to fast log grid", "[StellarCollapse]") { singularity::FastMath::log10(std::pow(10, g0.max())), 1e-12)); } - AND_THEN("The re-interpolated fast log is a sane number") {} - AND_THEN("The fast-log table approximately interpolates the power law") { const Real x2 = 0.5; const Real x1 = 100; @@ -220,76 +290,128 @@ SCENARIO("Stellar Collapse EOS", "[StellarCollapse]") { AND_THEN("We can load the sp5 file") { StellarCollapse sc2(savename, true); AND_THEN("The two stellar collapse EOS's agree") { + CompareStellarCollapse(sc, sc2); + } + sc2.Finalize(); + } + } - Real yemin = sc.YeMin(); - Real yemax = sc.YeMax(); - Real tmin = sc.TMin(); - Real tmax = sc.TMax(); - Real ltmin = std::log10(tmin); - Real ltmax = std::log10(tmax); - Real lrhomin = std::log10(sc.rhoMin()); - Real lrhomax = std::log10(sc.rhoMax()); - REQUIRE(yemin == sc2.YeMin()); - REQUIRE(yemax == sc2.YeMax()); - REQUIRE(sc.TMin() == sc2.TMin()); - REQUIRE(sc.TMax() == sc2.TMax()); - REQUIRE(isClose(lrhomin, std::log10(sc2.rhoMin()), 1e-12)); - REQUIRE(isClose(lrhomax, std::log10(sc2.rhoMax()), 1e-12)); + WHEN("We serialize the StellarCollapse EOS") { + using Tricks = singularity::table_utils::SpinerTricks; + auto [size, data] = sc.Serialize(); + REQUIRE(size > 0); + THEN("We can de-serialize it into a new object") { + StellarCollapse sc2; + std::size_t read_size = sc2.DeSerialize(data); + REQUIRE(read_size == size); + REQUIRE(size > sizeof(StellarCollapse)); + sc2.CheckParams(); + AND_THEN("The new eos uses different memory than the original") { + REQUIRE(Tricks::DataBoxesPointToDifferentMemory(sc, sc2)); + } + AND_THEN("The two stellar collapse EOS's agree") { + CompareStellarCollapse(sc, sc2); + } + AND_THEN("We can de-serialize into two objects") { + StellarCollapse sc3; + std::size_t read_size_2 = sc3.DeSerialize(data); + REQUIRE(read_size_2 == size); + sc3.CheckParams(); + AND_THEN("The two de-serialized objects use the same memory") { + REQUIRE(Tricks::DataBoxesPointToSameMemory(sc2, sc3)); + } + } + THEN("We can de-serialize it into a new object AROUND a new allocation") { + using singularity::SharedMemSettings; + // mockup of shared data + std::size_t shared_size = sc.SharedMemorySizeInBytes(); + REQUIRE(shared_size == sc.DynamicMemorySizeInBytes()); + char *shared_data = (char *)malloc(shared_size); + SharedMemSettings settings(shared_data, true); + StellarCollapse sc3; + std::size_t read_size = sc3.DeSerialize(data, settings); + REQUIRE(read_size == size); + REQUIRE(read_size > sizeof(StellarCollapse)); + sc3.CheckParams(); + AND_THEN("The new eos uses different memory than the original") { + REQUIRE(Tricks::DataBoxesPointToDifferentMemory(sc, sc3)); + } + AND_THEN( + "The new eos uses different memory than the one de-serialized aorund " + "the non-shared memory") { + REQUIRE(Tricks::DataBoxesPointToDifferentMemory(sc2, sc3)); + } + AND_THEN("The two stellar collapse EOS's agree") { + CompareStellarCollapse(sc, sc3); + } + AND_THEN("We can de-serialize from shared memory around one more object") { + SharedMemSettings settings2(shared_data, false); + StellarCollapse sc4; + std::size_t read_size_2 = sc4.DeSerialize(data, settings); + REQUIRE(read_size_2 == size); + REQUIRE(read_size_2 > sizeof(StellarCollapse)); + sc4.CheckParams(); + AND_THEN("The two shared-mem objects use the same memory") { + REQUIRE(Tricks::DataBoxesPointToSameMemory(sc3, sc4)); + } + AND_THEN("The two stellar collapse EOS's agree") { + CompareStellarCollapse(sc, sc4); + } + } + free(shared_data); + } + } + free(data); + } - auto sc1_d = sc.GetOnDevice(); - auto sc2_d = sc2.GetOnDevice(); + WHEN("We serialize a variant that owns a StellarCollapse EOS") { + using EOS = singularity::Variant; + using Tricks = singularity::table_utils::SpinerTricks; + EOS e1 = sc; + auto [size, data] = e1.Serialize(); + REQUIRE(size > 0); + THEN("We can de-serialize it into a new object") { + EOS e2; + std::size_t read_size = e2.DeSerialize(data); + REQUIRE(read_size == size); + e2.CheckParams(); + AND_THEN("The two stellar collapse EOS's agree") { + StellarCollapse sc2 = e2.get(); + CompareStellarCollapse(sc, sc2); + REQUIRE(Tricks::DataBoxesPointToDifferentMemory(sc, sc2)); + } + AND_THEN("We can de-serialize it twice into shared memory") { + using singularity::SharedMemSettings; - int nwrong_h = 0; -#ifdef PORTABILITY_STRATEGY_KOKKOS - using atomic_view = Kokkos::MemoryTraits; - Kokkos::View nwrong_d("wrong"); -#else - PortableMDArray nwrong_d(&nwrong_h, 1); -#endif + std::size_t dyn_size = e1.DynamicMemorySizeInBytes(); + char *shared_data = (char *)malloc(dyn_size); + EOS e3, e4; - const int N = 123; - constexpr Real gamma = 1.4; - const Real dY = (yemax - yemin) / (N + 1); - const Real dlT = (ltmax - ltmin) / (N + 1); - const Real dlR = (lrhomax - lrhomin) / (N + 1); - portableFor( - "fill eos", 0, N, 0, N, 0, N, - PORTABLE_LAMBDA(const int &k, const int &j, const int &i) { - Real lambda[2]; - Real Ye = yemin + k * dY; - Real lT = ltmin + j * dlT; - Real lR = lrhomin + i * dlR; - Real T = std::pow(10., lT); - Real R = std::pow(10., lR); - Real e1, e2, p1, p2, cv1, cv2, b1, b2, s1, s2; - unsigned long output = - (singularity::thermalqs::pressure | - singularity::thermalqs::specific_internal_energy | - singularity::thermalqs::specific_heat | - singularity::thermalqs::bulk_modulus); - lambda[0] = Ye; + std::size_t read_size_e3 = + e3.DeSerialize(data, SharedMemSettings(shared_data, true)); + REQUIRE(read_size_e3 == size); + e3.CheckParams(); - sc1_d.FillEos(R, T, e1, p1, cv1, b1, output, lambda); - sc2_d.FillEos(R, T, e2, p2, cv2, b2, output, lambda); - // Fill entropy. Will need to change later. - s1 = sc1_d.EntropyFromDensityTemperature(R, T, lambda); - s2 = p2 * std::pow(R, -gamma); // ideal - if (!isClose(e1, e2)) nwrong_d() += 1; - if (!isClose(p1, p2)) nwrong_d() += 1; - if (!isClose(cv1, cv2)) nwrong_d() += 1; - if (!isClose(b1, b2)) nwrong_d() += 1; - if (!isClose(s1, s2)) nwrong_d() += 1; - }); -#ifdef PORTABILITY_STRATEGY_KOKKOS - Kokkos::deep_copy(nwrong_h, nwrong_d); -#endif - REQUIRE(nwrong_h == 0); + std::size_t read_size_e4 = + e4.DeSerialize(data, SharedMemSettings(shared_data, false)); + REQUIRE(read_size_e4 == size); + e4.CheckParams(); - sc1_d.Finalize(); - sc2_d.Finalize(); + AND_THEN("They all agree") { + StellarCollapse sc2 = e2.get(); + StellarCollapse sc3 = e3.get(); + StellarCollapse sc4 = e4.get(); + CompareStellarCollapse(sc, sc3); + CompareStellarCollapse(sc, sc4); + REQUIRE(Tricks::DataBoxesPointToDifferentMemory(sc, sc3)); + REQUIRE(Tricks::DataBoxesPointToDifferentMemory(sc2, sc3)); + REQUIRE(Tricks::DataBoxesPointToSameMemory(sc3, sc4)); + } + + free(shared_data); } - sc2.Finalize(); } + free(data); } sc.Finalize(); } diff --git a/test/test_eos_tabulated.cpp b/test/test_eos_tabulated.cpp index f80d373d0a7..d18969e74db 100644 --- a/test/test_eos_tabulated.cpp +++ b/test/test_eos_tabulated.cpp @@ -1,5 +1,5 @@ //------------------------------------------------------------------------------ -// © 2021-2023. Triad National Security, LLC. All rights reserved. This +// © 2021-2024. 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 @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -32,6 +33,7 @@ #include #ifdef SPINER_USE_HDF +#include using singularity::SpinerEOSDependsRhoSie; using singularity::SpinerEOSDependsRhoT; #endif @@ -62,7 +64,7 @@ constexpr Real ev2k = 1.160451812e4; #ifdef SINGULARITY_USE_EOSPAC using EOS = singularity::Variant; -SCENARIO("SpinerEOS depends on Rho and T", "[SpinerEOS],[DependsRhoT][EOSPAC]") { +SCENARIO("SpinerEOS depends on Rho and T", "[SpinerEOS][DependsRhoT][EOSPAC]") { GIVEN("SpinerEOS and EOSPAC EOS for steel can be initialized with matid") { EOS steelEOS_host_polymorphic = SpinerEOSDependsRhoT(eosName, steelID); @@ -124,6 +126,7 @@ SCENARIO("SpinerEOS depends on Rho and T", "[SpinerEOS],[DependsRhoT][EOSPAC]") steelEOS_host_polymorphic.Finalize(); // host and device must be // finalized separately. steelEOS.Finalize(); // cleans up memory on device. + eospac.Finalize(); } GIVEN("SpinerEOS and EOSPAC for air can be initialized with matid") { @@ -179,6 +182,7 @@ SCENARIO("SpinerEOS depends on Rho and T", "[SpinerEOS],[DependsRhoT][EOSPAC]") } airEOS_host.Finalize(); airEOS.Finalize(); + eospac.Finalize(); } GIVEN("EOS initialized with matid") { @@ -199,6 +203,7 @@ SCENARIO("SpinerEOS depends on Rho and T", "[SpinerEOS],[DependsRhoT][EOSPAC]") REQUIRE(isClose(cv, cv_pac)); } eos_spiner.Finalize(); + eos_eospac.Finalize(); } GIVEN("EOS initialized with matid") { @@ -216,11 +221,11 @@ SCENARIO("SpinerEOS depends on Rho and T", "[SpinerEOS],[DependsRhoT][EOSPAC]") REQUIRE(isClose(rho, rho_pac)); } eos_spiner.Finalize(); + eos_eospac.Finalize(); } } -// Disabling these tests for now as the DependsRhoSie code is not well-maintained -SCENARIO("SpinerEOS depends on rho and sie", "[SpinerEOS],[DependsRhoSie]") { +SCENARIO("SpinerEOS depends on rho and sie", "[SpinerEOS][DependsRhoSie]") { GIVEN("SpinerEOSes for steel can be initialised with matid") { SpinerEOSDependsRhoSie steelEOS_host(eosName, steelID); @@ -257,6 +262,98 @@ SCENARIO("SpinerEOS depends on rho and sie", "[SpinerEOS],[DependsRhoSie]") { steelEOS.Finalize(); // cleans up device memory } } + +SCENARIO("SpinerEOS and EOSPAC Serialization", + "[SpinerEOS][DependsRhoT][DependsRhoSie][EOSPAC][Serialization]") { + GIVEN("Eoses initialized with matid") { + SpinerEOSDependsRhoT rhoT_orig = SpinerEOSDependsRhoT(eosName, steelID); + SpinerEOSDependsRhoSie rhoSie_orig = SpinerEOSDependsRhoSie(eosName, steelID); + EOS eospac_orig = EOSPAC(steelID); + THEN("They report dynamic vs static memory correctly") { + REQUIRE(rhoT_orig.AllDynamicMemoryIsShareable()); + REQUIRE(rhoSie_orig.AllDynamicMemoryIsShareable()); + REQUIRE(!eospac_orig.AllDynamicMemoryIsShareable()); + REQUIRE(eospac_orig.SerializedSizeInBytes() > + eospac_orig.DynamicMemorySizeInBytes()); + } + WHEN("We serialize") { + auto [rhoT_size, rhoT_data] = rhoT_orig.Serialize(); + REQUIRE(rhoT_size == rhoT_orig.SerializedSizeInBytes()); + + auto [rhoSie_size, rhoSie_data] = rhoSie_orig.Serialize(); + REQUIRE(rhoSie_size == rhoSie_orig.SerializedSizeInBytes()); + + auto [eospac_size, eospac_data] = eospac_orig.Serialize(); + REQUIRE(eospac_size == eospac_orig.SerializedSizeInBytes()); + + const std::size_t rhoT_shared_size = rhoT_orig.DynamicMemorySizeInBytes(); + REQUIRE(rhoT_size > rhoT_shared_size); + + const std::size_t rhoSie_shared_size = rhoSie_orig.DynamicMemorySizeInBytes(); + REQUIRE(rhoSie_size > rhoSie_shared_size); + + const std::size_t eospac_shared_size = eospac_orig.DynamicMemorySizeInBytes(); + REQUIRE(eospac_size > eospac_shared_size); + + THEN("We can deserialize into shared memory") { + using singularity::SharedMemSettings; + using RhoTTricks = singularity::table_utils::SpinerTricks; + using RhoSieTricks = + singularity::table_utils::SpinerTricks; + + char *rhoT_shared_data = (char *)malloc(rhoT_shared_size); + char *rhoSie_shared_data = (char *)malloc(rhoSie_shared_size); + char *eospac_shared_data = (char *)malloc(eospac_shared_size); + + SpinerEOSDependsRhoT eos_rhoT; + std::size_t read_size_rhoT = + eos_rhoT.DeSerialize(rhoT_data, SharedMemSettings(rhoT_shared_data, true)); + REQUIRE(read_size_rhoT == rhoT_size); + REQUIRE(RhoTTricks::DataBoxesPointToDifferentMemory(rhoT_orig, eos_rhoT)); + + SpinerEOSDependsRhoSie eos_rhoSie; + std::size_t read_size_rhoSie = eos_rhoSie.DeSerialize( + rhoSie_data, SharedMemSettings(rhoSie_shared_data, true)); + REQUIRE(read_size_rhoSie == rhoSie_size); + REQUIRE(RhoSieTricks::DataBoxesPointToDifferentMemory(rhoSie_orig, eos_rhoSie)); + + eospac_orig.Finalize(); + EOS eos_eospac = EOSPAC(); + std::size_t read_size_eospac = eos_eospac.DeSerialize( + eospac_data, SharedMemSettings(eospac_shared_data, true)); + REQUIRE(read_size_eospac == eospac_size); + + AND_THEN("EOS lookups work") { + constexpr Real rho_trial = 1; + constexpr Real sie_trial = 1e12; + const Real P_eospac = + eos_eospac.PressureFromDensityInternalEnergy(rho_trial, sie_trial); + const Real P_spiner_orig = + rhoT_orig.PressureFromDensityInternalEnergy(rho_trial, sie_trial); + const Real P_spiner_rhoT = + eos_rhoT.PressureFromDensityInternalEnergy(rho_trial, sie_trial); + const Real P_spiner_rhoSie = + eos_rhoSie.PressureFromDensityInternalEnergy(rho_trial, sie_trial); + REQUIRE(isClose(P_eospac, P_spiner_orig)); + REQUIRE(isClose(P_eospac, P_spiner_rhoT)); + REQUIRE(isClose(P_eospac, P_spiner_rhoSie)); + } + + eos_eospac.Finalize(); + free(rhoT_shared_data); + free(rhoSie_shared_data); + free(eospac_shared_data); + } + free(rhoT_data); + free(rhoSie_data); + free(eospac_data); + } + + rhoT_orig.Finalize(); + rhoSie_orig.Finalize(); + } +} + #endif // SINGULARITY_USE_EOSPAC #endif // SINGULARITY_TEST_SESAME #endif // SPINER_USE_HDF diff --git a/test/test_get_sg_eos.cpp b/test/test_get_sg_eos.cpp index 7a9ea0c6b65..686f10b39a1 100644 --- a/test/test_get_sg_eos.cpp +++ b/test/test_get_sg_eos.cpp @@ -82,7 +82,8 @@ int run_sg_get_eos_tests() { } // obtain converged and consistent PTE solution // do rho-T input solve - Real p_check, vfrac_check[NMAT], ie_check[NMAT]; + Real p_check = 1; // sanitize pressure guess + Real vfrac_check[NMAT], ie_check[NMAT]; get_sg_eos(NMAT, 1, 1, -3, eos_offset, eoss, &cell_offset, &p_check, &pmax, &v_true, &spvol, &sie_tot_check, &T_true_ev, &bmod, &dpde, &cv, mfrac, vfrac_check, ie_check, nullptr, nullptr, nullptr, 1.0e-12); diff --git a/test/test_spiner_tricks.cpp b/test/test_spiner_tricks.cpp new file mode 100644 index 00000000000..314f645f4f4 --- /dev/null +++ b/test/test_spiner_tricks.cpp @@ -0,0 +1,130 @@ +//------------------------------------------------------------------------------ +// © 2024. 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. +//------------------------------------------------------------------------------ + +#ifdef SINGULARITY_USE_SPINER + +#include +#include + +#include +#include +#include + +#include +#include + +#ifndef CATCH_CONFIG_FAST_COMPILE +#define CATCH_CONFIG_FAST_COMPILE +#include +#endif + +using namespace singularity; +class DummyEOS; +using STricks = table_utils::SpinerTricks; + +class DummyEOS { + public: + friend class table_utils::SpinerTricks; + using Grid_t = Spiner::RegularGrid1D; + using DataBox = Spiner::DataBox; + + DummyEOS() = default; + DummyEOS(int N) : N_(N), a_(N), b_(N), memoryStatus_(DataStatus::OnHost) { + for (int i = 0; i < N; ++i) { + a_(i) = i; + b_(i) = i + N; + } + } + void Finalize() { STricks::Finalize(this); } + DummyEOS GetOnDevice() { return STricks::GetOnDevice(this); } + std::size_t DynamicMemorySizeInBytes() const { + return STricks::DynamicMemorySizeInBytes(this); + } + std::size_t DumpDynamicMemory(char *dst) const { + return STricks::DumpDynamicMemory(dst, this); + } + std::size_t SetDynamicMemory(char *src) { return STricks::SetDynamicMemory(src, this); } + + // Public so we can inspect in unit tests + int N_ = -1; + DataBox a_, b_; + DataStatus memoryStatus_ = DataStatus::Deallocated; + + // Private so we can ensure class friendship is working + private: +#define DBLIST &a_, &b_ + std::vector GetDataBoxPointers_() const { + return std::vector{DBLIST}; + } + std::vector GetDataBoxPointers_() { return std::vector{DBLIST}; } +#undef DBLIST +}; + +constexpr int N = 5; +SCENARIO("The SpinerTricks tool can copy databoxes to device and finalize them", + "[SpinerTricks]") { + WHEN("We initialize a host-side DummyEOS") { + DummyEOS eos_h(N); + REQUIRE(eos_h.memoryStatus_ == DataStatus::OnHost); + THEN("We can copy it to device") { + DummyEOS eos_d = eos_h.GetOnDevice(); + REQUIRE(eos_d.memoryStatus_ == DataStatus::OnDevice); + AND_THEN("The device-side memory is correct") { + int nwrong = 0; + portableReduce( + "Check Dummy EOS", 0, N, + PORTABLE_LAMBDA(const int i, int &nw) { + nw += (eos_d.a_(i) != i) + (eos_d.b_(i) != i + N); + }, + nwrong); + REQUIRE(nwrong == 0); + } + eos_d.Finalize(); + REQUIRE(eos_d.memoryStatus_ == DataStatus::Deallocated); + } + + eos_h.Finalize(); + REQUIRE(eos_h.memoryStatus_ == DataStatus::Deallocated); + } +} + +SCENARIO("The SpinerTricks tool can serialize dynamic memory", "[SpinerTricks]") { + GIVEN("A DummyEOS") { + DummyEOS eos1(N); + std::size_t size = eos1.DynamicMemorySizeInBytes(); + REQUIRE(eos1.memoryStatus_ == DataStatus::OnHost); + REQUIRE(size == 2 * N * sizeof(std::size_t)); + THEN("We can serialize it") { + char *data = (char *)malloc(size); + eos1.DumpDynamicMemory(data); + WHEN("We can de-serialize it twice") { + DummyEOS eos2(N), eos3(N); + eos2.SetDynamicMemory(data); + eos3.SetDynamicMemory(data); + THEN("The data is different from the original object") { + REQUIRE(STricks::DataBoxesPointToDifferentMemory(eos1, eos2)); + REQUIRE(STricks::DataBoxesPointToDifferentMemory(eos1, eos3)); + AND_THEN("The serialized objects point to the same underlying memory as each " + "other") { + REQUIRE(STricks::DataBoxesPointToSameMemory(eos2, eos3)); + } + } + } + free(data); + } + eos1.Finalize(); + } +} + +#endif // SINGULARITY_USE_SPINER diff --git a/utils/spiner b/utils/spiner index 0d0d7474f34..bd57161576a 160000 --- a/utils/spiner +++ b/utils/spiner @@ -1 +1 @@ -Subproject commit 0d0d7474f34cdc168c141185cc692b65ee8ad0c0 +Subproject commit bd57161576a62a13341dada183f2b336a3e99b08