Skip to content

Commit 9f561d2

Browse files
authored
Merge pull request #506 from lanl/jmm/pte-example-and-cleanup
pte example and robustness fix
2 parents f94929f + 38d99ab commit 9f561d2

File tree

12 files changed

+762
-5
lines changed

12 files changed

+762
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
## Current develop
44

55
### Added (new features/APIs/variables/...)
6+
- [[PR506]](https://github.com/lanl/singularity-eos/pull/506) Add two examples related to PTE
67

78
### Fixed (Repair bugs, etc)
9+
- [[PR506]](https://github.com/lanl/singularity-eos/pull/506) Added some robustness checks to the PTE solvers
810
- [[PR505]](https://github.com/lanl/singularity-eos/pull/505) rename LogType::TRUE to LogType::DOUBLE
911
- [[PR495]](https://github.com/lanl/singularity-eos/pull/495) Fix bug related to MinimumTemperature and MinimumDensity in SpinerEOSDependsRhoSie
1012
- [[PR496]](https://github.com/lanl/singularity-eos/pull/496) Re-enable stellarcollapse2spiner, which was disabled.

doc/sphinx/src/using-closures.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ compute the pressure response of the flow and how to partition the volume and
1717
energy between the individual materials. In this situation, one must decide how
1818
to compute thermodynamic quantities in that cell for each material.
1919

20+
For an example of how to use the PTE solvers, see
21+
``examples/pte_2mat.cpp`` in the ``examples`` folder.
22+
2023
Governing Equations and Assumptions
2124
------------------------------------
2225

example/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ if (SINGULARITY_USE_SPINER_WITH_HDF5)
2121
add_executable(eos_grid eos_grid.cpp)
2222
target_link_libraries(eos_grid PRIVATE
2323
singularity-eos::singularity-eos)
24+
add_executable(map_pt_space map_pt_space.cpp)
25+
target_link_libraries(map_pt_space PRIVATE
26+
singularity-eos::singularity-eos)
27+
add_executable(pte_2mat pte_2mat.cpp)
28+
target_link_libraries(pte_2mat PRIVATE
29+
singularity-eos::singularity-eos)
2430
endif()
2531

2632
if(SINGULARITY_USE_EOSPAC AND SINGULARITY_USE_SPINER_WITH_HDF5)

example/map_pt_space.cpp

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
//------------------------------------------------------------------------------
2+
// © 2025. Triad National Security, LLC. All rights reserved. This
3+
// program was produced under U.S. Government contract 89233218CNA000001
4+
// for Los Alamos National Laboratory (LANL), which is operated by Triad
5+
// National Security, LLC for the U.S. Department of Energy/National
6+
// Nuclear Security Administration. All rights in the program are
7+
// reserved by Triad National Security, LLC, and the U.S. Department of
8+
// Energy/National Nuclear Security Administration. The Government is
9+
// granted for itself and others acting on its behalf a nonexclusive,
10+
// paid-up, irrevocable worldwide license in this material to reproduce,
11+
// prepare derivative works, distribute copies to the public, perform
12+
// publicly and display publicly, and to permit others to do so.
13+
//------------------------------------------------------------------------------
14+
15+
// This example takes two materials and walks through valid sets of
16+
// volume fractions alpha1, alpha2, such that the volume-averaged
17+
// densities rhobar1 = alpha1 rho1 and rhobar2 = alpha2 rho2 are
18+
// conserved and the volume fractions sum to 1. Here rho1 and rho2 are
19+
// the microphysical densities of each material. At each volume
20+
// fraction pair, it computes the temperature so that the total energy
21+
// sums to a user specified value. When the pressures of each material
22+
// agree at a given pair of volume fractions, pressure temperature
23+
// equilibrium has been achived. This is useful for building an
24+
// intuition for how pressure temperature equilibrium works and also
25+
// seeing what it looks like for a given pairs of materials. The
26+
// script also outputs the residual as well as the P/T of each
27+
// material at each point. This produces a "map" of the PT landscape
28+
// for the material pair.
29+
30+
// C headers
31+
#include <cmath>
32+
#include <cstdio>
33+
34+
// C++ headers
35+
#include <iostream>
36+
#include <memory>
37+
#include <string>
38+
39+
// This library contains portable utilities
40+
#include <ports-of-call/portability.hpp>
41+
#include <ports-of-call/portable_errors.hpp>
42+
43+
// This contains useful tools for preventing things like divide by zero
44+
#include <singularity-eos/base/robust_utils.hpp>
45+
// 1D root finding
46+
#include <singularity-eos/base/root-finding-1d/root_finding.hpp>
47+
// Needed to import the eos models
48+
#include <singularity-eos/eos/eos.hpp>
49+
50+
// This library contains the spiner table object, which we will use to
51+
// store our output
52+
#include <spiner/databox.hpp>
53+
#include <spiner/interpolation.hpp>
54+
55+
// These are the specializations of spiner we will use
56+
using DataBox = Spiner::DataBox<Real>;
57+
using RegularGrid1D = Spiner::RegularGrid1D<Real>;
58+
using Spiner::DBDeleter;
59+
60+
// Number of equations of state to use, always 2
61+
constexpr std::size_t NEOS = 2;
62+
63+
// Set the EOS you want to use here.
64+
// Set the EOSs you want to use here.
65+
using EOS = singularity::Variant<singularity::SpinerEOSDependsRhoT>;
66+
67+
int main(int argc, char *argv[]) {
68+
if (argc < 6) {
69+
std::cerr << "Usage: " << argv[0]
70+
<< " residual_savename rhobar1 rhobar2 sietot nvfrac eosargs..."
71+
<< std::endl;
72+
std::exit(1);
73+
}
74+
75+
// This is needed for Kokkos
76+
#ifdef PORTABILITY_STRATEGY_KOKKOS
77+
Kokkos::initialize();
78+
#endif
79+
{
80+
// File we'll save the residual map to, in ascii. P/T trajectories will be printed to
81+
// to stdout
82+
const std::string savename = argv[1];
83+
// alpha rho for each material where here alpha is volume
84+
// fraction and rho is microphysical material density.
85+
const Real rhobar1 = atof(argv[2]);
86+
const Real rhobar2 = atof(argv[3]);
87+
// total specific internal energy in the problem.
88+
const Real sietot = atof(argv[4]);
89+
// number of vfrac points
90+
const std::size_t nvfrac = atoi(argv[5]);
91+
92+
// Below are the arguments for an individual equation of
93+
// state. Here I'm assuming SpinerEOSDependsRhoT but you could
94+
// modify for your needs.
95+
const std::string materialsfile = argv[6];
96+
const int matids[] = {atoi(argv[7]), atoi(argv[8])};
97+
98+
// Now let's load up the EOS's.
99+
EOS eos1 = singularity::SpinerEOSDependsRhoT(materialsfile, matids[0]);
100+
EOS eos2 = singularity::SpinerEOSDependsRhoT(materialsfile, matids[1]);
101+
102+
// The total bulk density
103+
const Real rhotot = rhobar1 + rhobar2;
104+
const Real utot = rhotot * sietot;
105+
106+
// We will be walking through volume fractions so let's grid that
107+
// up using a Spiner RegularGrid1D. This is the grid of alpha1s. alpha2 = 1 - alpha1.
108+
constexpr Real lvfrac_min = -5;
109+
constexpr Real lvfrac_max = -0.5;
110+
RegularGrid1D lvfracs(lvfrac_min, lvfrac_max, nvfrac);
111+
112+
// We will be doing root finds in T space so we need to know what
113+
// our bounds are. There is often a minimum temperature for an EOS
114+
// but not a maximum.
115+
const Real Tmin = std::max(eos1.MinimumTemperature(), eos2.MinimumTemperature());
116+
const Real Tmax = 1e10; // change this if needed
117+
118+
// We also want to store quantities of interest as we walk the volume fractions
119+
// This is a way of managing databoxes so that memory is automatically managed
120+
// Temperature
121+
std::unique_ptr<DataBox, DBDeleter> pTs(
122+
new DataBox(Spiner::AllocationTarget::Device, nvfrac));
123+
DataBox Ts = *pTs; // A shallow coppy of pTs. Unmanaged memory.
124+
// Pressure for materials 1 and 2
125+
std::unique_ptr<DataBox, DBDeleter> pPs(
126+
new DataBox(Spiner::AllocationTarget::Device, nvfrac, NEOS));
127+
DataBox Ps = *pPs;
128+
// vfrac, energy, and pressure residuals for the trajectory
129+
std::unique_ptr<DataBox, DBDeleter> pTrajRes(
130+
new DataBox(Spiner::AllocationTarget::Device, nvfrac, 3));
131+
DataBox trajRes = *pTrajRes;
132+
133+
// We also need to ensure whatever internal tabulated data for
134+
// each EOS is available on device.
135+
eos1 = eos1.GetOnDevice();
136+
eos2 = eos2.GetOnDevice();
137+
138+
// Ok let's walk the grid. We will do so on device with a portableFor loop
139+
portableFor(
140+
"Compute P and T for each alpha", 0, nvfrac, PORTABLE_LAMBDA(const int i) {
141+
// volume fractions
142+
const Real lvfrac = lvfracs.x(i);
143+
const Real vfrac = std::pow(10., lvfrac);
144+
const Real alpha1 = vfrac;
145+
const Real alpha2 = 1. - vfrac;
146+
147+
// microphysical densities
148+
Real rho1 = singularity::robust::ratio(rhobar1, alpha1);
149+
Real rho2 = singularity::robust::ratio(rhobar2, alpha2);
150+
const Real Tguess = 1500; // just an initial guess
151+
152+
// solve for the temperature such that sum of internal
153+
// energies is the total energy given microphysical densities rho
154+
auto fu = [=](const Real T) {
155+
return rhobar1 * eos1.InternalEnergyFromDensityTemperature(rho1, T) +
156+
rhobar2 * eos2.InternalEnergyFromDensityTemperature(rho2, T);
157+
};
158+
// sets the T variable by reference
159+
auto root_status = RootFinding1D::regula_falsi(fu, utot, Tguess, Tmin, Tmax,
160+
1e-16, 1e-16, Ts(i));
161+
if (root_status != RootFinding1D::Status::SUCCESS) {
162+
printf("# Root finder failed! "
163+
"alpha1 alpha2, rho1, rho2, sie1(rho1,Tguess), sie2(rho2,Tguess) "
164+
"sietot = "
165+
"%.14e %.14e %.14e %.14e %.14e %.14e %.14e\n",
166+
alpha1, alpha2, rho1, rho2,
167+
eos1.InternalEnergyFromDensityTemperature(rho1, Tguess),
168+
eos2.InternalEnergyFromDensityTemperature(rho2, Tguess), sietot);
169+
Ts(i) = Ps(i, 0) = Ps(i, 1) = -1;
170+
trajRes(i, 0) = trajRes(i, 1) = trajRes(i, 2) = -1;
171+
} else {
172+
// compute the pressures
173+
Ps(i, 0) = eos1.PressureFromDensityTemperature(rho1, Ts(i));
174+
Ps(i, 1) = eos2.PressureFromDensityTemperature(rho2, Ts(i));
175+
176+
trajRes(i, 0) = alpha1 + alpha2 - 1.0;
177+
178+
const Real u1 =
179+
rhobar1 * eos1.InternalEnergyFromDensityTemperature(rho1, Ts(i));
180+
const Real u2 =
181+
rhobar2 * eos2.InternalEnergyFromDensityTemperature(rho2, Ts(i));
182+
trajRes(i, 1) = singularity::robust::ratio(u1 + u2 - utot, utot);
183+
trajRes(i, 2) = singularity::robust::ratio(Ps(i, 1) - Ps(i, 0), utot);
184+
}
185+
});
186+
// wait for completion
187+
#ifdef PORTABILITY_STRATEGY_KOKKOS
188+
Kokkos::fence();
189+
#endif
190+
191+
// Copy the Ts array to host
192+
std::unique_ptr<DataBox, DBDeleter> pTs_h(
193+
new DataBox(Spiner::AllocationTarget::Host, nvfrac));
194+
DataBox Ts_h = *pTs_h;
195+
portableCopyToHost(Ts_h.data(), Ts.data(), Ts.sizeBytes());
196+
197+
// Get max temperature achieved. Note this could have been done in
198+
// a single step via a portableReduce, but we split it out for clarity.
199+
Real Tmax_walked = Ts_h.max();
200+
201+
// Let's make a T grid
202+
const Real nT = nvfrac + 1;
203+
204+
RegularGrid1D gTs(Tmin, Tmax_walked, nT);
205+
206+
// Now we can do one more loop where we compute the energy and
207+
// pressure residuals as a function of T and alpha
208+
std::unique_ptr<DataBox, DBDeleter> p_residuals(
209+
new DataBox(Spiner::AllocationTarget::Device, nT, nvfrac, NEOS));
210+
DataBox residuals = *p_residuals;
211+
212+
portableFor(
213+
"Compute residual", 0, nT, 0, nvfrac, PORTABLE_LAMBDA(const int j, const int i) {
214+
// Temperature
215+
const Real T = gTs.x(j);
216+
217+
// volume fractions
218+
const Real lvfrac = lvfracs.x(i);
219+
const Real vfrac = std::pow(10., lvfrac);
220+
const Real alpha1 = vfrac;
221+
const Real alpha2 = 1. - vfrac;
222+
223+
// microphysical densities
224+
Real rho1 = singularity::robust::ratio(rhobar1, alpha1);
225+
Real rho2 = singularity::robust::ratio(rhobar2, alpha2);
226+
227+
const Real u1 = rhobar1 * eos1.InternalEnergyFromDensityTemperature(rho1, T);
228+
const Real u2 = rhobar2 * eos2.InternalEnergyFromDensityTemperature(rho2, T);
229+
residuals(j, i, 0) = u1 + u2 - utot;
230+
231+
const Real P1 = eos1.PressureFromDensityTemperature(rho1, T);
232+
const Real P2 = eos2.PressureFromDensityTemperature(rho2, T);
233+
residuals(j, i, 1) = P1 - P2;
234+
});
235+
#ifdef PORTABILITY_STRATEGY_KOKKOS
236+
Kokkos::fence();
237+
#endif
238+
239+
// Now we need to copy all of this data to host so we can output it
240+
std::unique_ptr<DataBox, DBDeleter> pPs_h(
241+
new DataBox(Spiner::AllocationTarget::Host, nvfrac, NEOS));
242+
auto Ps_h = *pPs_h;
243+
portableCopyToHost(Ps_h.data(), Ps.data(), Ps.sizeBytes());
244+
245+
std::unique_ptr<DataBox, DBDeleter> pTrajRes_h(
246+
new DataBox(Spiner::AllocationTarget::Host, nvfrac, 3));
247+
auto trajRes_h = *pTrajRes_h;
248+
portableCopyToHost(trajRes_h.data(), trajRes.data(), trajRes.sizeBytes());
249+
250+
std::unique_ptr<DataBox, DBDeleter> p_residuals_h(
251+
new DataBox(Spiner::AllocationTarget::Host, nT, nvfrac, NEOS));
252+
auto residuals_h = *p_residuals_h;
253+
portableCopyToHost(residuals_h.data(), residuals.data(), residuals.sizeBytes());
254+
#ifdef PORTABILITY_STRATEGY_KOKKOS
255+
Kokkos::fence();
256+
#endif
257+
258+
FILE *file = fopen(savename.c_str(), "w");
259+
PORTABLE_REQUIRE(file != nullptr, "Unable to open file");
260+
// Now lets save the residuals
261+
fprintf(file, "# j i T alpha_1 res_sie res_P\n");
262+
for (std::size_t j = 0; j < nT; ++j) {
263+
for (std::size_t i = 0; i < nvfrac; ++i) {
264+
Real lvfrac = lvfracs.x(i);
265+
Real vfrac = std::pow(10., lvfrac);
266+
fprintf(file, "%ld %ld %.14e %.14e %.14e %.14e\n", j, i, gTs.x(j), vfrac,
267+
residuals_h(j, i, 0), residuals_h(j, i, 1));
268+
}
269+
}
270+
fclose(file);
271+
272+
printf("# sie and P residuals normalized by total energy\n");
273+
printf("# i alpha_1 T P_1 P_2 res_alpha res_sie res_P\n");
274+
for (std::size_t i = 0; i < nvfrac; ++i) {
275+
Real lvfrac = lvfracs.x(i);
276+
Real vfrac = std::pow(10., lvfrac);
277+
printf("%ld %.14e %.14e %.14e %.14e %.14e %.14e %.14e\n", i, vfrac, Ts_h(i),
278+
Ps_h(i, 0), Ps_h(i, 1), trajRes(i, 0), trajRes(i, 1), trajRes(i, 2));
279+
}
280+
281+
eos1.Finalize();
282+
eos2.Finalize();
283+
}
284+
#ifdef PORTABILITY_STRATEGY_KOKKOS
285+
Kokkos::finalize();
286+
#endif
287+
return 0;
288+
}

0 commit comments

Comments
 (0)