Skip to content

Commit 645f108

Browse files
katsmith133alicebarthel
authored andcommitted
adds test for eos clamping
1 parent 13b1d61 commit 645f108

File tree

4 files changed

+192
-70
lines changed

4 files changed

+192
-70
lines changed

components/omega/doc/devGuide/EOS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ An enumeration listing all implemented schemes is provided. It needs to be exten
1515
EOS is added. It is used to identify which EOS method is to be used at run time.
1616

1717
```c++
18-
enum class EosType { Linear, Teos10Poly75t };
18+
enum class EosType { LinearEos, Teos10Eos };
1919
```
2020
2121
## Initialization
@@ -52,9 +52,9 @@ Eos.computeSpecVolDisp(ConsrvTemp, AbsSalinity, Pressure, KDisp);
5252
where `KDisp` is the number of `k` levels you want to displace each specific volume level to.
5353
For example, to displace each level to one below, set `KDisp = 1`.
5454

55-
## Bounds check (and truncation) for the state variables (under Teos-10)
55+
## Bounds check (and truncation) for the state variables (under TEOS-10)
5656

57-
The implemented 75-term polynomial for the calculation of the specific volume under Teos-10 is considered valid for ocean states contained in the ''oceanographic funnel'' defined in [McDougall et al., 2003](https://journals.ametsoc.org/view/journals/atot/20/5/1520-0426_2003_20_730_aaceaf_2_0_co_2.xml). When using teos-10, the Eos uses member methods `calcSLimits(P)` and `calcTLimits(Sa, P)` to calculate the valid ranges of Sa and T given the pressure. The conservative temperature lower bound depends is set by the freezing temperature, using the member method `calcCtFreezing(Sa, P, SaturationFract)`. This method implements the polynomial approximation of the conservative freezing temperature (called `gsw_ct_freezing_poly` in the GSW package), which is known to produce erros in the (-5e-4 K, 6e-4 K) range. Once we calculate the upper and lower bounds of validity, the state variables are clipped to the valid range (if outside the bounds) before we run the specific volume calculation. The state fields themselves are not changed.
57+
The implemented 75-term polynomial for the calculation of the specific volume under TEOS-10 is considered valid for ocean states contained in the ''oceanographic funnel'' defined in [McDougall et al., 2003](https://journals.ametsoc.org/view/journals/atot/20/5/1520-0426_2003_20_730_aaceaf_2_0_co_2.xml). When using TEOS-10, the Eos uses member methods `calcSLimits(P)` and `calcTLimits(Sa, P)` to calculate the valid ranges of Sa and T given the pressure. The conservative temperature lower bound is set by the freezing temperature, using the member method `calcCtFreezing(Sa, P, SaturationFract)`. This method implements the polynomial approximation of the conservative freezing temperature (called `gsw_ct_freezing_poly` in the GSW package), which is known to produce erros in the (-5e-4 K, 6e-4 K) range. Once we calculate the upper and lower bounds of validity, the state variables are clipped to the valid range (if outside the bounds) before we run the specific volume calculation. The state fields themselves are not changed.
5858

5959
## Removal of Eos
6060

components/omega/doc/userGuide/EOS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ where `DRhoDT` is the thermal expansion coefficient ($\textrm{kg}/(\textrm{m}^3
2121

2222
In addition to `SpecVol`, the displaced specific volume `SpecVolDisplaced` is also calculated by the EOS. This calculates the density of a parcel of fluid that is adiabatically displaced by a relative `k` levels, capturing the effects of pressure/depth changes. This is primarily used to calculate quantities for determining the water column stability (i.e. the stratification) and the vertical mixing coefficients (viscosity and diffusivity). Note: when using the linear EOS, `SpecVolDisplaced` will be the same as `SpecVol` since the specific volume calculation is independent of pressure/depth.
2323

24-
When using `Teos-10`, the state variables are checked against the range over which the polynomial is considered to be valid (see [Roquet et al. 2015](https://www.sciencedirect.com/science/article/pii/S1463500315000566)). If the values are outside of the accepted values, we use the valid bounds for the specific volume calculation. Note that the state variable values themselves are not modified, only that they are not used as is in the calculation.
24+
When using TEOS-10, the state variables are checked against the range over which the polynomial is considered to be valid (see [Roquet et al. 2015](https://www.sciencedirect.com/science/article/pii/S1463500315000566)). If the values are outside of the accepted values, we use the valid bounds for the specific volume calculation. Note that the state variable values themselves are not modified, only that they are not used as is in the calculation.

components/omega/src/ocn/Eos.h

Lines changed: 58 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ enum class EosType {
2626
Teos10Eos /// Roquet et al. 2015 75 term expansion
2727
};
2828

29-
struct Range {
30-
Real lo;
31-
Real hi;
29+
struct EosRange {
30+
Real Lo;
31+
Real Hi;
3232
};
3333

3434
/// TEOS10 75-term Polynomial Equation of State
@@ -88,12 +88,12 @@ class Teos10Eos {
8888
constexpr Real SAu = 40.0 * 35.16504 / 35.0;
8989
constexpr Real CTu = 40.0;
9090
constexpr Real DeltaS = 24.0;
91-
Range Srange = calcSLimits(P);
92-
Range Trange = calcTLimits(Sa, P);
91+
EosRange SRange = calcSLimits(P);
92+
EosRange TRange = calcTLimits(Sa, P);
9393
Real SaInFunnel = Kokkos::clamp(
94-
Sa, Srange.lo, Srange.hi); // Salt limited to Poly75t valid range
94+
Sa, SRange.Lo, SRange.Hi); // Salt limited to Poly75t valid range
9595
Real Ss = Kokkos::sqrt((SaInFunnel + DeltaS) / SAu);
96-
Real Tt = Kokkos::clamp(Ct, Trange.lo, Trange.hi) / CTu;
96+
Real Tt = Kokkos::clamp(Ct, TRange.Lo, TRange.Hi) / CTu;
9797

9898
/// Coefficients for the polynomial expansion
9999
constexpr Real V000 = 1.0769995862e-03;
@@ -249,87 +249,85 @@ class Teos10Eos {
249249
}
250250

251251
/// Calculate S limits of validity given pressure p
252-
KOKKOS_FUNCTION Range calcSLimits(const Real P) const {
253-
Real lo = 0.0;
254-
Real hi = 42.0;
255-
Real lo2 = P * 5e-3 - 2.5;
256-
Real lo3 = 30.0;
252+
KOKKOS_FUNCTION EosRange calcSLimits(const Real P) const {
253+
Real Lo = 0.0;
254+
Real Hi = 42.0;
255+
Real Lo2 = P * 5e-3 - 2.5;
256+
Real Lo3 = 30.0;
257257

258258
if (P >= 500.0 && P < 6500.0) {
259-
lo = Kokkos::max(lo, lo2);
259+
Lo = Kokkos::max(Lo, Lo2);
260260
} else if (P >= 6500.0) {
261-
lo = Kokkos::max(lo, lo3);
261+
Lo = Kokkos::max(Lo, Lo3);
262262
}
263-
return {lo, hi};
263+
return {Lo, Hi};
264264
}
265265

266266
/// Calculate T limits of validity given p,Sa
267-
KOKKOS_FUNCTION Range calcTLimits(const Real Sa, const Real P) const {
268-
Real lo = -15.0;
269-
Real hi = 95.0;
267+
KOKKOS_FUNCTION EosRange calcTLimits(const Real Sa, const Real P) const {
268+
Real Lo = -15.0;
269+
Real Hi = 95.0;
270270

271271
if (P < 500.0) {
272-
lo = calcCtFreezing(Sa, P, Real{0.0});
272+
Lo = calcCtFreezing(Sa, P, 0.0_Real);
273273
} else if (P < 6500.0) {
274-
lo = calcCtFreezing(Sa, Real{500.0}, Real{0.0});
275-
hi = 31.66666666666667 - P * 3.333333333333334e-3;
274+
Lo = calcCtFreezing(Sa, 500.0_Real, 0.0_Real);
275+
Hi = 31.66666666666667 - P * 3.333333333333334e-3;
276276
} else {
277-
lo = calcCtFreezing(Sa, Real{500.0}, Real{0.0});
278-
hi = 10.0;
277+
Lo = calcCtFreezing(Sa, 500.0_Real, 0.0_Real);
278+
Hi = 10.0;
279279
}
280-
return {lo, hi};
280+
return {Lo, Hi};
281281
}
282282

283283
/// Calculates freezing CTemperature (polynomial error in [-5e-4,6e-4] K)
284284
KOKKOS_FUNCTION Real calcCtFreezing(const Real Sa, const Real P,
285285
const Real SaturationFract) const {
286286
constexpr Real Sso = 35.16504;
287-
constexpr Real c0 = 0.017947064327968736;
288-
constexpr Real c1 = -6.076099099929818;
289-
constexpr Real c2 = 4.883198653547851;
290-
constexpr Real c3 = -11.88081601230542;
291-
constexpr Real c4 = 13.34658511480257;
292-
constexpr Real c5 = -8.722761043208607;
293-
constexpr Real c6 = 2.082038908808201;
294-
constexpr Real c7 = -7.389420998107497;
295-
constexpr Real c8 = -2.110913185058476;
296-
constexpr Real c9 = 0.2295491578006229;
297-
constexpr Real c10 = -0.9891538123307282;
298-
constexpr Real c11 = -0.08987150128406496;
299-
constexpr Real c12 = 0.3831132432071728;
300-
constexpr Real c13 = 1.054318231187074;
301-
constexpr Real c14 = 1.065556599652796;
302-
constexpr Real c15 = -0.7997496801694032;
303-
constexpr Real c16 = 0.3850133554097069;
304-
constexpr Real c17 = -2.078616693017569;
305-
constexpr Real c18 = 0.8756340772729538;
306-
constexpr Real c19 = -2.079022768390933;
307-
constexpr Real c20 = 1.596435439942262;
308-
constexpr Real c21 = 0.1338002171109174;
309-
constexpr Real c22 = 1.242891021876471;
287+
constexpr Real C0 = 0.017947064327968736;
288+
constexpr Real C1 = -6.076099099929818;
289+
constexpr Real C2 = 4.883198653547851;
290+
constexpr Real C3 = -11.88081601230542;
291+
constexpr Real C4 = 13.34658511480257;
292+
constexpr Real C5 = -8.722761043208607;
293+
constexpr Real C6 = 2.082038908808201;
294+
constexpr Real C7 = -7.389420998107497;
295+
constexpr Real C8 = -2.110913185058476;
296+
constexpr Real C9 = 0.2295491578006229;
297+
constexpr Real C10 = -0.9891538123307282;
298+
constexpr Real C11 = -0.08987150128406496;
299+
constexpr Real C12 = 0.3831132432071728;
300+
constexpr Real C13 = 1.054318231187074;
301+
constexpr Real C14 = 1.065556599652796;
302+
constexpr Real C15 = -0.7997496801694032;
303+
constexpr Real C16 = 0.3850133554097069;
304+
constexpr Real C17 = -2.078616693017569;
305+
constexpr Real C18 = 0.8756340772729538;
306+
constexpr Real C19 = -2.079022768390933;
307+
constexpr Real C20 = 1.596435439942262;
308+
constexpr Real C21 = 0.1338002171109174;
309+
constexpr Real C22 = 1.242891021876471;
310310

311311
// Note: a = 0.502500117621 / Sso
312-
constexpr Real a = 0.014289763856964;
313-
constexpr Real b = 0.057000649899720;
314-
315-
Real CtFreez;
312+
constexpr Real A = 0.014289763856964;
313+
constexpr Real B = 0.057000649899720;
316314

317315
Real Sar = Sa * 1.0e-2;
318316
Real X = Kokkos::sqrt(Sar);
319317
Real Pr = P * 1.0e-4;
320318

321-
CtFreez =
322-
c0 + Sar * (c1 + X * (c2 + X * (c3 + X * (c4 + X * (c5 + c6 * X))))) +
323-
Pr * (c7 + Pr * (c8 + c9 * Pr)) +
319+
Real CtFreez =
320+
C0 + Sar * (C1 + X * (C2 + X * (C3 + X * (C4 + X * (C5 + C6 * X))))) +
321+
Pr * (C7 + Pr * (C8 + C9 * Pr)) +
324322
Sar * Pr *
325-
(c10 + Pr * (c12 + Pr * (c15 + c21 * Sar)) +
326-
Sar * (c13 + c17 * Pr + c19 * Sar) +
327-
X * (c11 + Pr * (c14 + c18 * Pr) +
328-
Sar * (c16 + c20 * Pr + c22 * Sar)));
323+
(C10 + Pr * (C12 + Pr * (C15 + C21 * Sar)) +
324+
Sar * (C13 + C17 * Pr + C19 * Sar) +
325+
X * (C11 + Pr * (C14 + C18 * Pr) +
326+
Sar * (C16 + C20 * Pr + C22 * Sar)));
329327

330328
/* Adjust for the effects of dissolved air */
331-
CtFreez = CtFreez - SaturationFract * (1e-3) * (2.4 - a * Sa) *
332-
(1.0 + b * (1.0 - Sa / Sso));
329+
CtFreez = CtFreez - SaturationFract * (1e-3) * (2.4 - A * Sa) *
330+
(1.0 + B * (1.0 - Sa / Sso));
333331

334332
return CtFreez;
335333
}

components/omega/test/ocn/EosTest.cpp

Lines changed: 130 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ constexpr int NVertLayers = 60;
3838

3939
/// Published values (TEOS-10 and linear) to test against
4040
const Real TeosExpValue = 0.0009732819628; // Expected value for TEOS-10 eos
41+
const Real TeosClampValue1 =
42+
0.0009714522912320203; // Expected value for TEOS-10 eos clamping test 1
43+
const Real TeosClampValue2 =
44+
0.0009662964459162306; // Expected value for TEOS-10 eos clamping test 2
45+
const Real TeosClampValue3 =
46+
0.0010086299185825206; // Expected value for TEOS-10 eos clamping test 3
4147
const Real LinearExpValue =
4248
0.0009784735812133072; // Expected value for Linear eos
4349

@@ -284,6 +290,117 @@ int testEosTeos10Displaced() {
284290
return Err;
285291
}
286292

293+
int testEosTeos10Clamping() {
294+
int Err = 0;
295+
const auto *Mesh = HorzMesh::getDefault();
296+
/// Get Eos instance to test
297+
Eos *TestEos = Eos::getInstance();
298+
TestEos->EosChoice = EosType::Teos10Eos;
299+
300+
/// Create and fill ocean state arrays
301+
Array2DReal SArray = Array2DReal("SArray", Mesh->NCellsAll, NVertLevels);
302+
Array2DReal TArray = Array2DReal("TArray", Mesh->NCellsAll, NVertLevels);
303+
Array2DReal PArray = Array2DReal("PArray", Mesh->NCellsAll, NVertLevels);
304+
/// Use Kokkos::deep_copy to fill the entire view with the ref value
305+
/// Test with valid poly75t values first.
306+
deepCopy(SArray, 35.0);
307+
deepCopy(TArray, 5.0);
308+
deepCopy(PArray, 400.0);
309+
deepCopy(TestEos->SpecVol, 0.0);
310+
311+
/// Compute specific volume
312+
TestEos->computeSpecVol(TArray, SArray, PArray);
313+
314+
/// Check all array values against expected value
315+
int numMismatches = 0;
316+
Array2DReal SpecVol = TestEos->SpecVol;
317+
parallelReduce(
318+
"CheckSpecVolMatrix-Teos", {Mesh->NCellsAll, NVertLevels},
319+
KOKKOS_LAMBDA(int i, int j, int &localCount) {
320+
if (!isApprox(SpecVol(i, j), TeosClampValue1, RTol)) {
321+
localCount++;
322+
}
323+
},
324+
numMismatches);
325+
326+
auto SpecVolH = createHostMirrorCopy(SpecVol);
327+
if (numMismatches != 0) {
328+
Err++;
329+
LOG_ERROR("EosTest: TEOS SpecVolClampingNone isApprox FAIL, "
330+
"expected {}, got {} with {} mismatches",
331+
TeosClampValue1, SpecVolH(1, 1), numMismatches);
332+
}
333+
if (Err == 0) {
334+
LOG_INFO("EosTest SpecVolClampingNone TEOS-10: PASS");
335+
}
336+
337+
/// Test with an ivalid poly75t salinity value second
338+
deepCopy(SArray, 45.0);
339+
deepCopy(TArray, 5.0);
340+
deepCopy(PArray, 400.0);
341+
deepCopy(TestEos->SpecVol, 0.0);
342+
343+
/// Compute specific volume
344+
TestEos->computeSpecVol(TArray, SArray, PArray);
345+
346+
/// Check all array values against expected value
347+
numMismatches = 0;
348+
SpecVol = TestEos->SpecVol;
349+
parallelReduce(
350+
"CheckSpecVolMatrix-Teos", {Mesh->NCellsAll, NVertLevels},
351+
KOKKOS_LAMBDA(int i, int j, int &localCount) {
352+
if (!isApprox(SpecVol(i, j), TeosClampValue2, RTol)) {
353+
localCount++;
354+
}
355+
},
356+
numMismatches);
357+
358+
SpecVolH = createHostMirrorCopy(SpecVol);
359+
if (numMismatches != 0) {
360+
Err++;
361+
LOG_ERROR("EosTest: TEOS SpecVolClampingSalt isApprox FAIL, "
362+
"expected {}, got {} with {} mismatches",
363+
TeosClampValue2, SpecVolH(1, 1), numMismatches);
364+
}
365+
if (Err == 0) {
366+
LOG_INFO("EosTest SpecVolClampingSalt TEOS-10: PASS");
367+
}
368+
369+
/// Test with an ivalid poly75t temperature value third
370+
deepCopy(SArray, 35.0);
371+
deepCopy(TArray, 100.0);
372+
deepCopy(PArray, 400.0);
373+
deepCopy(TestEos->SpecVol, 0.0);
374+
375+
/// Compute specific volume
376+
TestEos->computeSpecVol(TArray, SArray, PArray);
377+
378+
/// Check all array values against expected value
379+
numMismatches = 0;
380+
SpecVol = TestEos->SpecVol;
381+
parallelReduce(
382+
"CheckSpecVolMatrix-Teos", {Mesh->NCellsAll, NVertLevels},
383+
KOKKOS_LAMBDA(int i, int j, int &localCount) {
384+
if (!isApprox(SpecVol(i, j), TeosClampValue3, RTol)) {
385+
localCount++;
386+
}
387+
},
388+
numMismatches);
389+
390+
SpecVolH = createHostMirrorCopy(SpecVol);
391+
if (numMismatches != 0) {
392+
Err++;
393+
LOG_ERROR("EosTest: TEOS SpecVolClampingTemp isApprox FAIL, "
394+
"expected {}, got {} with {} mismatches",
395+
TeosClampValue3, SpecVolH(1, 1), numMismatches);
396+
}
397+
if (Err == 0) {
398+
LOG_INFO("EosTest SpecVolClampingTemp TEOS-10: PASS");
399+
}
400+
401+
return Err;
402+
}
403+
287404
/// Finalize and clean up all test infrastructure
288405
void finalizeEosTest() {
289406
HorzMesh::clear();
@@ -296,8 +413,7 @@ void finalizeEosTest() {
296413

297414
/// Test that the external GSW-C library returns the expected specific volume
298415
int checkValueGswcSpecVol() {
299-
int Err = 0;
300-
const Real RTol = 1e-10;
416+
int Err = 0;
301417

302418
/// Get specific volume from GSW-C library
303419
double SpecVol = gsw_specvol(Sa, Ct, P);
@@ -317,8 +433,8 @@ int checkValueGswcSpecVol() {
317433

318434
/// Test that the external GSW-C library returns the expected specific volume
319435
int checkValueCtFreezing() {
320-
int Err = 0;
321-
const Real RTol = 1e-10;
436+
int Err = 0;
437+
322438
Teos10Eos TestEos(1);
323439
// TestEos->EosChoice = EosType::Teos10Eos;
324440
constexpr Real SaturationFrac = 0.0;
@@ -344,14 +460,19 @@ int checkValueCtFreezing() {
344460
}
345461

346462
// the main test (all in one to have the same log)
347-
// Single value test:
348-
// --> test calls the external GSW-C library
463+
// Single value tests:
464+
// --> one test calls the external GSW-C library for specific volume
465+
// --> next test calls the external GSW-C library for freezing value
349466
// and compares the specific volume to the published value
350467
// Full array tests:
351468
// --> one tests the value on a Eos with linear option
352469
// --> next checks the value on a Eos with linear displaced option
353470
// --> next checks the value on a Eos with TEOS-10 option
354471
// --> next checks the value on a Eos with TEOS-10 displaced option
472+
// Clamping test:
473+
// --> test check the clamping works for values that (1) don't need
474+
// clamping, (2) need claiming on the salinity, and (3) need clamping
475+
// on the temperature
355476
int eosTest(const std::string &MeshFile = "OmegaMesh.nc") {
356477
int Err = initEosTest(MeshFile);
357478
if (Err != 0) {
@@ -369,6 +490,9 @@ int eosTest(const std::string &MeshFile = "OmegaMesh.nc") {
369490
Err += testEosTeos10();
370491
Err += testEosTeos10Displaced();
371492

493+
LOG_INFO("Clamping check:");
494+
Err += testEosTeos10Clamping();
495+
372496
if (Err == 0) {
373497
LOG_INFO("EosTest: Successful completion");
374498
}

0 commit comments

Comments
 (0)