-
Notifications
You must be signed in to change notification settings - Fork 21
Simple MACAW EOS #560
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Simple MACAW EOS #560
Conversation
… more things to do.
4df10b1
to
b9e6b59
Compare
Thanks for adding this, @bgclayton-lanl ! (And nice to virtually meet you.) Sorry for the delay; @jhp-lanl and I will take a look at this. Just haven't gotten to it yet. This is a particularly busy week for me. |
Hi, good to meet you. I'm sure there will be a lot of things to change or tweak. The main thing I wasn't too sure about was the unit test part (Catch2). I saw in some other unit tests that there are different things for "getting on device" and "Kokkos" and all of these things. But I just kept it basic for the time being. But I can update them to replicate the testing from other EOS. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some small changes, but overall it looks great!
In order to maintain thermodynamic consistency, a sufficient set of constraints | ||
is given by :math:`A > 0`, :math:`B > 0`, :math:`C^\infty_v > 0`, :math:`v_0 > | ||
0`, :math:`T_0 > 0`, and :math:`\Gamma_c > 0`. If one wants to maintain | ||
thermodynamic consistency, we also require :math:`\Gamma_c \in (0,1)`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great addition! But why is there an upper bound on
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two thoughts here after reading the paper:
- The upper bound on
$\Gamma$ is actually for thermodynamic stability not thermodynamic consistency. We routinely deal with EOS that are not fully stable (i.e. VdW loops). - The upper bound on Gamma is overly restrictive. It is a guarantee that the isothermal sound speed is
positivenot imaginary (the paper says$c_T$ > 0 in the text, but really it's$c_T^2 > 0$ ), but it's possible to achieve that over a domain of interest without this restriction or for a particular selection of$B$ .
As a result, I'd like to provide our users with the ability to decide what their domain is and how to deal with this constraint. I think the best course of action would be to reproduce equation 22 from the paper and discuss that
PORTABLE_ALWAYS_REQUIRE(_Cvinf > 0, "Specific heat capacity (at constant volume), `Cvinf`, must be positive"); | ||
PORTABLE_ALWAYS_REQUIRE(_v0 > 0, "Reference specific volume, 'v0', must be positive"); | ||
PORTABLE_ALWAYS_REQUIRE(_T0 >= 0, "Reference temperature, 'T0', must be non-negative"); | ||
PORTABLE_ALWAYS_REQUIRE(_Gc > 0 && _Gc < 1, "Gruneisen parameter, 'Gc', must be in the interval (0,1)"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For redundancy, can you also explain the upper-bound here as well as in the documentation? It's just a bit surprising to me. A comment is fine.
If the upper bound is more of a desirable thing rather than a required thing, then we can use a PORTABLE_WARN()
if the value is above 1.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given the discussion above (and after skimming the paper), I think that while _Gc > 0
is a hard restriction, we should only warn if _Gc > 1
. Please add a comment about thermodynamic stability being the impetus for the restriction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, and I should add that although they give the stability criterion as an open interval, I think the stability criterion is actually a closed interval on the high side.
template <typename Indexer_t> | ||
PORTABLE_INLINE_FUNCTION void | ||
SimpleMACAW::FillEos(Real &rho, Real &temp, Real &sie, Real &press, Real &cv, Real &bmod, | ||
const unsigned long output, Indexer_t &&lambda) const { | ||
if (output & thermalqs::density && output & thermalqs::specific_internal_energy) { | ||
if (output & thermalqs::pressure || output & thermalqs::temperature) { | ||
UNDEFINED_ERROR; | ||
} | ||
DensityEnergyFromPressureTemperature(press, temp, lambda, rho, sie); | ||
} | ||
if (output & thermalqs::pressure && output & thermalqs::specific_internal_energy) { | ||
if (output & thermalqs::density || output & thermalqs::temperature) { | ||
UNDEFINED_ERROR; | ||
} | ||
sie = InternalEnergyFromDensityTemperature(rho, temp, lambda); | ||
} | ||
if (output & thermalqs::temperature && output & thermalqs::specific_internal_energy) { | ||
sie = InternalEnergyFromDensityPressure(rho, press); | ||
} | ||
if (output & thermalqs::pressure) press = PressureFromDensityInternalEnergy(rho, sie); | ||
if (output & thermalqs::temperature) | ||
temp = TemperatureFromDensityInternalEnergy(rho, sie); | ||
if (output & thermalqs::bulk_modulus) | ||
bmod = BulkModulusFromDensityInternalEnergy(rho, sie); | ||
if (output & thermalqs::specific_heat) | ||
cv = SpecificHeatFromDensityInternalEnergy(rho, sie); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can have this in-line if you'd like. I don't think there's a reason to define it separate from where you declare it in the header. @Yurlungur correct me if I'm wrong
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you're correct. Only reason to separate it out is for clarity. Choice left to the author.
if (output & thermalqs::pressure || output & thermalqs::temperature) { | ||
UNDEFINED_ERROR; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have some cruft in our code unfortunately. I think we'd ideally like to update these to be PORTABLE_REQUIRE
statements instead.
@Yurlungur thoughts on this versus PORTABLE_ALWAYS_REQUIRE
?
FYI @bgclayton-lanl the ALWAYS
essentially is for causing an abort regardless of whether it's a debug build or not. Without ALWAYS
, the check is only performed in debug builds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PORTABLE_ALWAYS_REQUIRE
would work. You could also use PORTABLE_ALWAYS_THROW_OR_ABORT
with a useful error message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand here. What needs to be changed? Are you saying to do a PORTABLE_REQUIRE
or PORTABLE_ALWAYS_REQUIRE
on the specific thermo flag that gets passed in?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the current structure if if (condition) {ERROR}
. That's essentiallyPORTABLE_REQUIRE(!condition)
. The latter is I think easier to read and ensures consistent error generation/handling across platforms.
As for ALWAYS
part, @Yurlungur or @jonahm-LANL can you advise on what you think is appropriate here? My philosophy is that assert
statements (i.e. those that are only checked at debug) should be reserved for highly unlikely conditions the developer thinks shouldn't happen; if they trigger, then they tell the developer that they need to refactor their logic. Illegal that a user can easily set (either by mistake or on purpose) should be always checked in my opinion unless they can be shown to be a performance bottleneck. I would argue that this falls into the latter category, so I would propose changing these error conditions to PORTABLE_ALWAYS_REQUIRE
statements (with the appropriate negation of course).
If @Yurlungur doesn't give his opinion though, just go with the ALWAYS
version for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should always fail if you hit it, so I think PORTABLE_ALWAYS
.
test/test_eos_simple_macaw.cpp
Outdated
// Create the EOS | ||
auto host_eos = SimpleMACAW(A, B, Cvinf, v0, T0, Gc); | ||
auto eos = host_eos.GetOnDevice(); | ||
|
||
// Only run exception checking when we aren't offloading to the GPUs | ||
SCENARIO("Zero temperature on cold curve", "[SimpleMACAWEOS][Temperature]") { | ||
GIVEN("Parameters for a Simple MACAW EOS") { | ||
Real rho = 0.5; | ||
for (int i = 0; i < 10; i++) { | ||
rho += rho + i; // cylce through a variety of densities | ||
THEN("The temperature evaluated on the cold curve should produce zero") { | ||
Real v = 1.0 / rho; | ||
Real e = eos.SieColdCurve(v); | ||
REQUIRE(eos.TemperatureFromDensityInternalEnergy(rho, e) == 0.0); | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd just group all of this under one scenario to test the SimpleMACAW
eos. Although we have our tests fairly isolated, I think it's still good practice to scope things inside the SCENARIO
.
Also, there are specific ways to test in loops with Catch2. Essentially the scenario, etc. all form a leaf in a tree and each leaf needs to be unique. In essence it means that some concatenation of all the names of the sections (GIVEN
, THEN
, etc.) needs to be unique.
So this test would look like this:
SCENARIO("Testing the SimpleMACAW EOS", "[SimpleMACAWEOS]") {
GIVEN("Parameters for a Simple MACAW EOS") {
constexpr Real A = 7.3;
constexpr Real B = 3.9;
constexpr Real Cvinf = 0.000389;
constexpr Real v0 = 1. / 8.952;
constexpr Real T0 = 150.;
constexpr Real Gc = 0.5;
auto host_eos = SimpleMACAW(A, B, Cvinf, v0, T0, Gc);
auto eos = host_eos.GetOnDevice(); // Not needed since no dynamic memory
WHEN("A set of densities is provided") {
Real rho = 0.5;
for (int i = 0; i < 10; i++) {
rho += rho + i; // cycle through a variety of densities
DYNAMIC_SECTION("For a given density " << rho << " and energy from the cold curve") {
Real v = 1.0 / rho;
Real e = eos.SieColdCurve(v);
INFO("rho = " << rho << " v = " << v << " e = " << e);
THEN("The temperature at this density and energy should be zero") {
REQUIRE(eos.TemperatureFromDensityInternalEnergy(rho, e) == 0.0);
}
}
}
}
}
}
All of the GIVEN
, THEN
, WHEN
, etc. macros all the same from a code perspective. It really just changes how the test prints out when it fails. So feel free to change out whatever you like. The DYNAMIC_SELECTION
macro is just there for ease of changing the name for each iteration of the loop (needed when you declare a section inside a loop like that).
I also added the INFO
macro to print out the inputs to the lookup if it fails. That can help diagnose issues.
Oh, and one more thing: can you make sure to include some test that looks something like this: WHEN("We put the SimpleMACAW EOS into a Variant") {
using EOS = Variant<SimpleMACAW>;
EOS eos_in_variant = eos;
THEN("The SimpleMACAW model conforms to the Variant API") {}
} Alternatively, you could do this with your inversion test and just use the The point here is that the test will fail to compile if you are missing any API functions in the |
@@ -1320,6 +1322,58 @@ This constructor also optionally accepts `MeanAtomicProperties` for | |||
the atomic mass and number as a final optional parameter. | |||
|
|||
|
|||
.. _MACAW EOS: https://pubs.aip.org/aip/jap/article/134/12/125102/2912256 | |||
|
|||
Simple MACAW |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good documentation! Nice work.
PORTABLE_INLINE_FUNCTION Real PressureFromDensityTemperature( | ||
const Real rho, const Real temperature, | ||
Indexer_t &&lambda = static_cast<Real *>(nullptr)) const { | ||
/* Equation (15) */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice including equation numbers
template <typename Indexer_t = Real *> | ||
PORTABLE_INLINE_FUNCTION Real SieColdCurve( | ||
const Real rho, Indexer_t &&lambda = static_cast<Real *>(nullptr)) const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want these functions exposed in the public API for this EOS? If so, you can leave them here. If not, move them to private scope and add a trailing underscore to the name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in general it should be. In my own research, I can imagine ways in which one may need to use the cold curve. The main point is that the cold curve dictates the admissible set. But for the thermal portions, I don't know when someone might need those. I think I could move those ones into the private scope.
static std::string EosPyType() { return EosType(); } | ||
|
||
private: | ||
Real _A, _B, _Cvinf, _v0, _T0, _Gc; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not your fault for this---we're not consistent throughout the code. But in our newer EOS's we are trying to use a trailing underscore for private cope, rather than a leading underscore. Do you mind changing the naming convention here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I can change this one. When should a leading underscore be used in general?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we use leading underscores anywhere. It's really just a matter of taste/convention.
if (output & thermalqs::pressure || output & thermalqs::temperature) { | ||
UNDEFINED_ERROR; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PORTABLE_ALWAYS_REQUIRE
would work. You could also use PORTABLE_ALWAYS_THROW_OR_ABORT
with a useful error message.
template <typename Indexer_t> | ||
PORTABLE_INLINE_FUNCTION void | ||
SimpleMACAW::FillEos(Real &rho, Real &temp, Real &sie, Real &press, Real &cv, Real &bmod, | ||
const unsigned long output, Indexer_t &&lambda) const { | ||
if (output & thermalqs::density && output & thermalqs::specific_internal_energy) { | ||
if (output & thermalqs::pressure || output & thermalqs::temperature) { | ||
UNDEFINED_ERROR; | ||
} | ||
DensityEnergyFromPressureTemperature(press, temp, lambda, rho, sie); | ||
} | ||
if (output & thermalqs::pressure && output & thermalqs::specific_internal_energy) { | ||
if (output & thermalqs::density || output & thermalqs::temperature) { | ||
UNDEFINED_ERROR; | ||
} | ||
sie = InternalEnergyFromDensityTemperature(rho, temp, lambda); | ||
} | ||
if (output & thermalqs::temperature && output & thermalqs::specific_internal_energy) { | ||
sie = InternalEnergyFromDensityPressure(rho, press); | ||
} | ||
if (output & thermalqs::pressure) press = PressureFromDensityInternalEnergy(rho, sie); | ||
if (output & thermalqs::temperature) | ||
temp = TemperatureFromDensityInternalEnergy(rho, sie); | ||
if (output & thermalqs::bulk_modulus) | ||
bmod = BulkModulusFromDensityInternalEnergy(rho, sie); | ||
if (output & thermalqs::specific_heat) | ||
cv = SpecificHeatFromDensityInternalEnergy(rho, sie); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you're correct. Only reason to separate it out is for clarity. Choice left to the author.
using singularity::SimpleMACAW; | ||
using EOS = singularity::Variant<SimpleMACAW>; | ||
|
||
constexpr Real REAL_TOL = std::numeric_limits<Real>::epsilon() * 1e4; // 1e-12 for double |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case is there a reason not to just set this to 1e-12? Is the 1e4 times machine epsilon significant or is this just what worked?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was just following what another EOS test was using. I've changed it to 1e-12
now.
test/test_eos_simple_macaw.cpp
Outdated
// Create the EOS | ||
auto host_eos = SimpleMACAW(A, B, Cvinf, v0, T0, Gc); | ||
auto eos = host_eos.GetOnDevice(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably don't want this here. You should put this inside the setup for a given scenario. (e.g., in line 48). That's so that at the end of the scenario you can call
eos.Finalize();
host_eos.Finalize();
and Catch will set up and tear down the EOS as appropriate.
I know that for an analytic EOS this really doesn't matter. But it does matter for more complicated EOS's so we should try to consistently use the known "safe" and "correct" style.
test/test_eos_simple_macaw.cpp
Outdated
} | ||
} | ||
|
||
SCENARIO("Inversion equivalence", "[SimpleMACAWEOS][PT-RhoSie Equivalence]") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment above about set up and tear down of the EOS object.
if (output & thermalqs::pressure || output & thermalqs::temperature) { | ||
UNDEFINED_ERROR; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should always fail if you hit it, so I think PORTABLE_ALWAYS
.
PR Summary
This update implements the Simple MACAW EOS from Tariq and Eduardo: https://www.osti.gov/biblio/2479474
PR Checklist
make format
command after configuring withcmake
.If preparing for a new release, in addition please check the following:
when='@main'
dependencies are updated to the release version in the package.py