Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6ba7a14
update ports
jonahm-LANL Apr 7, 2026
dc72ae5
thread spaces through API calls in base class
jonahm-LANL Apr 7, 2026
c4fa646
fill eos in variant class
jonahm-LANL Apr 7, 2026
af19c21
and threaded through the variant
jonahm-LANL Apr 7, 2026
215732b
Do it with SieFromDensityPressure
jonahm-LANL Apr 7, 2026
959f616
Merge branch 'jmm/python-warnings' into jmm/thread-spaces
jonahm-LANL Apr 8, 2026
7c45226
Merge branch 'jmm/python-warnings' into jmm/thread-spaces
jonahm-LANL Apr 8, 2026
afa924f
working commit
jonahm-LANL Apr 8, 2026
c78ab37
Thread it through the rest
jonahm-LANL Apr 8, 2026
a3d270e
Merge branch 'main' into jmm/thread-spaces
jonahm-LANL Apr 8, 2026
e65fd46
host space in python module
jonahm-LANL Apr 8, 2026
971fd73
leverage macros for overloads
jonahm-LANL Apr 8, 2026
b78d0b4
macro needs scratch
jonahm-LANL Apr 8, 2026
b2d4286
lets try with explicit overloads
jonahm-LANL Apr 9, 2026
bd4008f
documentation
jonahm-LANL Apr 9, 2026
fbcb830
test
jonahm-LANL Apr 9, 2026
d2422a6
Add tests
jonahm-LANL Apr 9, 2026
3dd1963
the sfinae needed a check of non-indexable index for Space and yes-in…
jonahm-LANL Apr 9, 2026
b81da8c
unused variable
jonahm-LANL Apr 9, 2026
e414821
name template SFINAEs
jonahm-LANL Apr 9, 2026
cb56624
name the SFINAE types
jonahm-LANL Apr 9, 2026
400a3a3
formatting
jonahm-LANL Apr 9, 2026
77b22bb
add overloads for space to the modifiers
jonahm-LANL Apr 9, 2026
c41b8ee
changelog
jonahm-LANL Apr 9, 2026
3db0a99
clean up doc
jonahm-LANL Apr 9, 2026
a5dec5e
formatting
jonahm-LANL Apr 9, 2026
668b871
add modifier wrapper macros
jonahm-LANL Apr 9, 2026
1e33c88
dramatically reduce boiler plate in modifiers with overloads with mor…
jonahm-LANL Apr 9, 2026
4e9d77d
macros comment
jonahm-LANL Apr 9, 2026
20dae89
formatting
jonahm-LANL Apr 9, 2026
76b9fe8
fix hard compare
jonahm-LANL Apr 9, 2026
9f9c1a8
CC
jonahm-LANL Apr 9, 2026
cb9a328
oops now CC
jonahm-LANL Apr 9, 2026
35c4a75
formatting
jonahm-LANL Apr 9, 2026
93bed3d
plan history for macros for modifier overloads
jonahm-LANL Apr 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Added (new features/APIs/variables/...)
- [[PR623]](https://github.com/lanl/singularity-eos/pull/623) Expanded the sesame2spiner syntax to support multiple material definitions in one input file.
- [[PR618]](https://github.com/lanl/singularity-eos/pull/618) Add PTDerivativesFromPreferred for computing derivatives of a mixture in a cell
- [[PR630]](https://github.com/lanl/singularity-eos/pull/630) Thread execution spaces through vector API.

### Fixed (Repair bugs, etc)

Expand Down
23 changes: 23 additions & 0 deletions doc/sphinx/src/using-eos.rst
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,23 @@ The vector API is templated to accept accessors. We do note, however,
that vectorization may suffer if your underlying data structure is not
contiguous in memory.

Execution spaces for the vector API
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

``singularity-eos`` borrows from the `Kokkos`_ notion of an *execution
space*. Each vector API call accepts an execution space as an optional
first argument, telling ``singularity-eos`` where to launch work,
either on CPU or GPU (for example). These are implemented via
`ports-of-call`_. By default, the two execution spaces available are
``PortsOfCall::Exec::Host`` and ``PortsOfCall::Exec::Device``. If you
build with the serial backend, these are equivalent. If you build with
``Kokkos`` enabled, you may also pass in any valid ``Kokkos``
execution space.

.. _Kokkos: https://kokkos.org/kokkos-core-wiki/

.. _ports-of-call: https://lanl.github.io/ports-of-call/main/index.html

.. _eospac_vector:

EOSPAC Vector Functions
Expand Down Expand Up @@ -506,6 +523,12 @@ available member function.
eos.TemperatureFromDensityInternalEnergy(density.data(), energy.data(), temperature.data(),
scratch.data(), density.size());

.. warning::

EOSPAC does **not** support selection of execution spaces. You will
get whatever execution space (likely host) that the EOSPAC backend
has been configured for.

The Evaluate Methods
~~~~~~~~~~~~~~~~~~~~~~

Expand Down
186 changes: 186 additions & 0 deletions plan_histories/MR630-2026-04-09-modifier-vector-macro-proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# MR630 Modifier Vector Macro Proposal

## Goal

Reduce the line count in `singularity-eos/eos/modifiers/*.hpp` for the raw-pointer
vector API, especially the overloads that only forward to
`PortsOfCall::Exec::Device()`.

## Current Situation

The modified vector API in the modifier headers now has two overload families for each
raw-pointer vector method:

1. A `Space` overload:

```cpp
template <typename Space, typename LambdaIndexer,
typename EnableIfSpace =
std::enable_if_t<!variadic_utils::has_int_index_v<Space>>>
inline void NAME(const Space &s, ...);
```

2. A default-device wrapper:

```cpp
template <typename LambdaIndexer>
inline void NAME(...) const {
NAME(PortsOfCall::Exec::Device(), ...);
}
```

This repeats across:

- `scaled_eos.hpp`
- `shifted_eos.hpp`
- `eos_unitsystem.hpp`
- `ramps_eos.hpp`
- `floored_energy.hpp`

## Important Constraint

The default-device wrappers cannot simply be deleted while preserving the current API.
The inherited wrappers in `eos/eos_base.hpp` do not automatically route into the
modifier-specific raw-pointer `Space` overloads. If we want to keep the existing
call surface, the realistic near-term win is to generate these wrappers, not remove the
behavior.

## Recommended Macro Scheme

Introduce a small modifier-only macro header, for example:

- `singularity-eos/eos/modifiers/modifier_vector_macros.hpp`

That header would define the default-device wrappers once.

### 2-in / 1-out wrapper

```cpp
#define SG_MODIFIER_DEVICE_WRAP_2IN_1OUT(NAME) \
template <typename LambdaIndexer> \
inline void NAME(const Real *in1, const Real *in2, Real *out, Real *scratch, \
const int num, LambdaIndexer &&lambdas, \
Transform &&transform = Transform()) const { \
NAME(PortsOfCall::Exec::Device(), in1, in2, out, scratch, num, \
std::forward<LambdaIndexer>(lambdas), std::forward<Transform>(transform)); \
}
```

### 1-in / 1-out wrapper

```cpp
#define SG_MODIFIER_DEVICE_WRAP_1IN_1OUT(NAME) \
template <typename LambdaIndexer> \
inline void NAME(const Real *in1, Real *out, Real *scratch, const int num, \
LambdaIndexer &&lambdas, \
Transform &&transform = Transform()) const { \
NAME(PortsOfCall::Exec::Device(), in1, out, scratch, num, \
std::forward<LambdaIndexer>(lambdas), std::forward<Transform>(transform)); \
}
```

### Aggregate wrapper list

```cpp
#define SG_MODIFIER_DEVICE_WRAP_ALL() \
SG_MODIFIER_DEVICE_WRAP_2IN_1OUT(TemperatureFromDensityInternalEnergy) \
SG_MODIFIER_DEVICE_WRAP_2IN_1OUT(PressureFromDensityTemperature) \
SG_MODIFIER_DEVICE_WRAP_2IN_1OUT(PressureFromDensityInternalEnergy) \
SG_MODIFIER_DEVICE_WRAP_1IN_1OUT(MinInternalEnergyFromDensity) \
SG_MODIFIER_DEVICE_WRAP_2IN_1OUT(SpecificHeatFromDensityTemperature) \
SG_MODIFIER_DEVICE_WRAP_2IN_1OUT(SpecificHeatFromDensityInternalEnergy) \
SG_MODIFIER_DEVICE_WRAP_2IN_1OUT(BulkModulusFromDensityTemperature) \
SG_MODIFIER_DEVICE_WRAP_2IN_1OUT(BulkModulusFromDensityInternalEnergy) \
SG_MODIFIER_DEVICE_WRAP_2IN_1OUT(GruneisenParamFromDensityTemperature) \
SG_MODIFIER_DEVICE_WRAP_2IN_1OUT(GruneisenParamFromDensityInternalEnergy) \
SG_MODIFIER_DEVICE_WRAP_2IN_1OUT(InternalEnergyFromDensityTemperature) \
SG_MODIFIER_DEVICE_WRAP_2IN_1OUT(EntropyFromDensityTemperature) \
SG_MODIFIER_DEVICE_WRAP_2IN_1OUT(EntropyFromDensityInternalEnergy)
```

## How This Would Be Used

Each modifier would keep only its `Space` overload implementations. At the end of the
vector API block, it would add:

```cpp
SG_MODIFIER_DEVICE_WRAP_ALL()
```

That removes most of the duplicate default-device wrapper code while preserving the
current behavior.

## Second Stage for Simple Forwarders

For `scaled_eos.hpp` and `eos_unitsystem.hpp`, the `Space` overload bodies are also
highly repetitive:

- update `transform`
- call `t_.NAME(s, ...)`

Those two files could use a second macro family such as:

```cpp
#define SG_MODIFIER_FORWARD_2IN_1OUT(NAME, PREPARE) \
template <typename Space, typename LambdaIndexer, \
typename EnableIfSpace = \
std::enable_if_t<!variadic_utils::has_int_index_v<Space>>> \
inline void NAME(const Space &s, const Real *in1, const Real *in2, Real *out, \
Real *scratch, const int num, LambdaIndexer &&lambdas, \
Transform &&transform = Transform()) const { \
PREPARE \
t_.NAME(s, in1, in2, out, scratch, num, \
std::forward<LambdaIndexer>(lambdas), std::forward<Transform>(transform)); \
} \
SG_MODIFIER_DEVICE_WRAP_2IN_1OUT(NAME)
```

Example usage:

```cpp
SG_MODIFIER_FORWARD_2IN_1OUT(
TemperatureFromDensityInternalEnergy,
transform.x.apply(scale_);
transform.y.apply(inv_scale_);)
```

## Where Not to Overuse Macros

I would not force the body macros into:

- `shifted_eos.hpp`
- `floored_energy.hpp`
- `ramps_eos.hpp`

Those files contain real per-method logic:

- scratch-buffer preprocessing and postprocessing
- local `portableFor` loops
- ramp-pressure overrides

For those files, the better tradeoff is:

- keep explicit `Space` overload bodies
- macro-generate only the default-device wrappers

## Alternative If True Elimination Is Desired

If the actual goal is to eliminate per-file default-device overloads entirely, use a CRTP
mixin instead of macros. Each modifier would define only the `Space` overloads, and the
mixin would provide the no-space wrappers once.

That is conceptually cleaner than making the macro scheme more aggressive, but it is a
larger refactor than the macro-only approach above.

## Recommendation

Preferred implementation order:

1. Add a shared modifier macro header that generates the default-device wrappers.
2. Convert all five modified modifier headers to use that wrapper macro set.
3. Optionally add the forwarding-body macros for `ScaledEOS` and `EOSUnitSystem`.
4. Leave `ShiftedEOS`, `FlooredEnergy`, and `BilinearRampEOS` with explicit `Space`
overload bodies.

This gives most of the line-count reduction without hiding the custom vector logic that
still needs to be readable.
35 changes: 23 additions & 12 deletions python/module.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>

#include <ports-of-call/portability.hpp>
#include <ports-of-call/portable_errors.hpp>

#include <singularity-eos/base/variadic_utils.hpp>
Expand Down Expand Up @@ -104,10 +105,11 @@ void func(const T & self, py::array_t<Real> a, py::array_t<Real> b,
auto av = a.unchecked<1>(); \
auto bv = b.unchecked<1>(); \
auto outv = PyArrayHelper(out.mutable_unchecked<1>()); \
PortsOfCall::Exec::Host host; \
if(lambdas_info.shape[1] > 0) { \
self.func(av, bv, outv, a.size(), LambdaHelper(lambdas)); \
self.func(host, av, bv, outv, a.size(), LambdaHelper(lambdas)); \
} else { \
self.func(av, bv, outv, a.size(), NoLambdaHelper()); \
self.func(host, av, bv, outv, a.size(), NoLambdaHelper()); \
} \
} \
\
Expand All @@ -122,15 +124,16 @@ void func##WithScratch(const T & self, py::array_t<Real> a, py::array_t<Real> b,
auto bv = PyArrayHelper(b.mutable_unchecked<1>()); \
auto outv = PyArrayHelper(out.mutable_unchecked<1>()); \
auto scrv = PyArrayHelper(scratch.mutable_unchecked<1>()); \
PortsOfCall::Exec::Host host; \
PORTABLE_REQUIRE(av.IsContiguous(), "arrays w/ scratch must be contiguous"); \
PORTABLE_REQUIRE(bv.IsContiguous(), "arrays w/ scratch must be contiguous"); \
PORTABLE_REQUIRE(outv.IsContiguous(), "arrays w/ scratch must be contiguous"); \
PORTABLE_REQUIRE(scrv.IsContiguous(), "arrays w/ scratch must be contiguous"); \
if(lambdas_info.shape[1] > 0) { \
self.func(av.data(), bv.data(), outv.data(), scrv.data(), a.size(), \
self.func(host, av.data(), bv.data(), outv.data(), scrv.data(), a.size(), \
LambdaHelper(lambdas)); \
} else { \
self.func(av.data(), bv.data(), outv.data(), scrv.data(), a.size(), \
self.func(host, av.data(), bv.data(), outv.data(), scrv.data(), a.size(), \
NoLambdaHelper()); \
} \
} \
Expand All @@ -141,7 +144,8 @@ void func##NoLambda(const T & self, py::array_t<Real> a, py::array_t<Real> b,
auto av = a.unchecked<1>(); \
auto bv = b.unchecked<1>(); \
auto outv = PyArrayHelper(out.mutable_unchecked<1>()); \
self.func(av, bv, outv, a.size(), NoLambdaHelper()); \
PortsOfCall::Exec::Host host; \
self.func(host, av, bv, outv, a.size(), NoLambdaHelper()); \
} \
\
template<typename T> \
Expand All @@ -151,11 +155,12 @@ void func##NoLambdaWithScratch(const T & self, py::array_t<Real> a, py::array_t<
auto bv = PyArrayHelper(b.mutable_unchecked<1>()); \
auto outv = PyArrayHelper(out.mutable_unchecked<1>()); \
auto scrv = PyArrayHelper(scratch.mutable_unchecked<1>()); \
PortsOfCall::Exec::Host host; \
PORTABLE_REQUIRE(av.IsContiguous(), "arrays w/ scratch must be contiguous"); \
PORTABLE_REQUIRE(bv.IsContiguous(), "arrays w/ scratch must be contiguous"); \
PORTABLE_REQUIRE(outv.IsContiguous(), "arrays w/ scratch must be contiguous"); \
PORTABLE_REQUIRE(scrv.IsContiguous(), "arrays w/ scratch must be contiguous"); \
self.func(av.data(), bv.data(), outv.data(), scrv.data(), a.size(), \
self.func(host, av.data(), bv.data(), outv.data(), scrv.data(), a.size(), \
NoLambdaHelper()); \
}

Expand Down Expand Up @@ -260,11 +265,13 @@ struct VectorFunctions {
auto bmodsv = PyArrayHelper(bmods.mutable_unchecked<1>());

if(lambdas_info.shape[1] > 0) {
self.FillEos(rhosv, temperaturesv,
self.FillEos(PortsOfCall::Exec::Host(),
rhosv, temperaturesv,
siesv, pressuresv, cvsv,
bmodsv, rhos.size(), output, LambdaHelper(lambdas));
} else {
self.FillEos(rhosv, temperaturesv,
self.FillEos(PortsOfCall::Exec::Host(),
rhosv, temperaturesv,
siesv, pressuresv, cvsv,
bmodsv, rhos.size(), output, NoLambdaHelper());
}
Expand All @@ -282,7 +289,8 @@ struct VectorFunctions {
auto pressuresv = PyArrayHelper(pressures.mutable_unchecked<1>());
auto cvsv = PyArrayHelper(cvs.mutable_unchecked<1>());
auto bmodsv = PyArrayHelper(bmods.mutable_unchecked<1>());
self.FillEos(rhosv, temperaturesv,
self.FillEos(PortsOfCall::Exec::Host(),
rhosv, temperaturesv,
siesv, pressuresv, cvsv,
bmodsv, rhos.size(), output, NoLambdaHelper());
}, py::arg("rhos"), py::arg("temperatures"), py::arg("sies"), py::arg("pressures"), py::arg("cvs"), py::arg("bmods"), py::arg("output"));
Expand Down Expand Up @@ -334,12 +342,14 @@ struct VectorFunctions<T,true> {
auto scrv = PyArrayHelper(scratch.mutable_unchecked<1>());

if(lambdas_info.shape[1] > 0) {
self.FillEos(rhosv.data(), temperaturesv.data(),
self.FillEos(PortsOfCall::Exec::Host(),
rhosv.data(), temperaturesv.data(),
siesv.data(), pressuresv.data(), cvsv.data(),
bmodsv.data(), scrv.data(), rhos.size(), output,
LambdaHelper(lambdas));
} else {
self.FillEos(rhosv.data(), temperaturesv.data(),
self.FillEos(PortsOfCall::Exec::Host(),
rhosv.data(), temperaturesv.data(),
siesv.data(), pressuresv.data(), cvsv.data(),
bmodsv.data(), scrv.data(), rhos.size(), output,
NoLambdaHelper());
Expand All @@ -361,7 +371,8 @@ struct VectorFunctions<T,true> {
auto bmodsv = PyArrayHelper(bmods.mutable_unchecked<1>());
auto scrv = PyArrayHelper(scratch.mutable_unchecked<1>());

self.FillEos(rhosv.data(), temperaturesv.data(),
self.FillEos(PortsOfCall::Exec::Host(),
rhosv.data(), temperaturesv.data(),
siesv.data(), pressuresv.data(), cvsv.data(),
bmodsv.data(), scrv.data(), rhos.size(),
output, NoLambdaHelper());
Expand Down
1 change: 0 additions & 1 deletion sesame2spiner/io_eospac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,6 @@ bool eosMassFraction(int matid, const Bounds &lRhoBounds, const Bounds &lTBounds
std::string &phase_names, Verbosity eospacWarn) {
using namespace EospacWrapper;

EOS_INTEGER errorCode = EOS_OK;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eliminates an unused variable warning

constexpr int NT = 2;
EOS_INTEGER tableHandle[NT];
EOS_INTEGER tableType[NT] = {EOS_M_DT, EOS_Comment};
Expand Down
1 change: 1 addition & 0 deletions singularity-eos/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ register_headers(
eos/eos_helmholtz.hpp
eos/eos_sap_polynomial.hpp
eos/eos_type_lists.hpp
eos/modifiers/modifier_vector_macros.hpp
eos/modifiers/relativistic_eos.hpp
eos/modifiers/scaled_eos.hpp
eos/modifiers/ramps_eos.hpp
Expand Down
8 changes: 4 additions & 4 deletions singularity-eos/base/indexable_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,8 @@ class VariadicIndexerBase {
PORTABLE_FORCEINLINE_FUNCTION
VariadicIndexerBase(const Data_t &data) : data_(data) {}

template <typename T,
typename = std::enable_if_t<variadic_utils::contains<T, Ts...>::value>>
template <typename T, typename EnableIfContained =
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming the SFINAE/concept type produced more useful error messages from the cuda compiler.

std::enable_if_t<variadic_utils::contains<T, Ts...>::value>>
PORTABLE_FORCEINLINE_FUNCTION Real &operator[](const T &t) {
constexpr std::size_t idx = variadic_utils::GetIndexInTL<T, Ts...>();
return data_[idx];
Expand All @@ -312,8 +312,8 @@ class VariadicIndexerBase {
PORTABLE_FORCEINLINE_FUNCTION
Real &operator[](const std::size_t idx) { return data_[idx]; }

template <typename T,
typename = std::enable_if_t<variadic_utils::contains<T, Ts...>::value>>
template <typename T, typename EnableIfContained =
std::enable_if_t<variadic_utils::contains<T, Ts...>::value>>
PORTABLE_FORCEINLINE_FUNCTION const Real &operator[](const T &t) const {
constexpr std::size_t idx = variadic_utils::GetIndexInTL<T, Ts...>();
return data_[idx];
Expand Down
Loading
Loading