Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions components/omega/configs/Default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ Omega:
DRhoDT: -0.2
DRhoDS: 0.8
RhoT0S0: 1000.0
ClampingEnable: false
EosLimits: Funnel
IOStreams:
InitialVertCoord:
UsePointerFile: false
Expand Down
8 changes: 6 additions & 2 deletions components/omega/doc/devGuide/EOS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ An enumeration listing all implemented schemes is provided. It needs to be exten
EOS is added. It is used to identify which EOS method is to be used at run time.

```c++
enum class EosType { Linear, Teos10Poly75t };
enum class EosType { LinearEos, Teos10Eos };
```

## Initialization
Expand Down Expand Up @@ -52,7 +52,11 @@ Eos.computeSpecVolDisp(ConsrvTemp, AbsSalinity, Pressure, KDisp);
where `KDisp` is the number of `k` levels you want to displace each specific volume level to.
For example, to displace each level to one below, set `KDisp = 1`.

## Removal of Eos
## Bounds check (and truncation) for the state variables (under TEOS-10)

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.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
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.
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 errors 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.

Copy link
Collaborator

Choose a reason for hiding this comment

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

should this also be updated to say we will issue a warning? Or we have an option to clip?


## Removal of Eos

To clear the Eos instance do:

Expand Down
2 changes: 2 additions & 0 deletions components/omega/doc/userGuide/EOS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ Eos:
where `DRhoDT` is the thermal expansion coefficient ($\textrm{kg}/(\textrm{m}^3 \cdot ^{\circ}\textrm{C})$), `DRhoDS` is the saline contraction coefficient ($\textrm{kg}/\textrm{m}^3$), and `RhoT0S0` is the reference density at (T,S)=(0,0) (in $\textrm{kg}/\textrm{m}^3$).

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.

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.
Copy link
Collaborator

Choose a reason for hiding this comment

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

same comment here should we specify we warn and have options for clipping

19 changes: 18 additions & 1 deletion components/omega/src/ocn/Eos.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ void Eos::init() {
Err += EosConfig.get("EosType", EosTypeStr);
CHECK_ERROR_ABORT(Err, "Eos::init: EosType subgroup not found in EosConfig");

std::string EosLimStr;
Err += EosConfig.get("EosLimits", EosLimStr);
CHECK_ERROR_ABORT(Err,
"Eos::init: EosLimits subgroup not found in EosConfig");
/// EosLimits EosLimChoice;

/// Set EosChoice and parameters based on EosTypeStr
if (EosTypeStr == "Linear" or EosTypeStr == "linear") {
Config EosLinConfig("Linear");
Expand All @@ -104,12 +110,23 @@ void Eos::init() {
} else if ((EosTypeStr == "teos10") or (EosTypeStr == "teos-10") or
(EosTypeStr == "TEOS-10")) {
eos->EosChoice = EosType::Teos10Eos;
if (EosLimStr == "Funnel") {
eos->ComputeSpecVolTeos10.EosLimChoice = EosLimits::Funnel;
} else if (EosLimStr == "Cube") {
eos->ComputeSpecVolTeos10.EosLimChoice = EosLimits::Cube;
} else {
LOG_ERROR("Eos::init: Unknown EosLimits requested");
Err += EosConfig.get("ClampingEnable",
eos->ComputeSpecVolTeos10.ClampingEnable);
CHECK_ERROR_ABORT(
Err, "Eos::init: Parameter ClampingEnable not found in EosConfig");
}
} else {
LOG_ERROR("Eos::init: Unknown EosType requested");
}
} // end init

/// Compute specific volume for all cells/layers (no displacement)
/// Compute specific volume for all cells/levels (no displacement)
void Eos::computeSpecVol(const Array2DReal &ConservTemp,
const Array2DReal &AbsSalinity,
const Array2DReal &Pressure) {
Expand Down
137 changes: 131 additions & 6 deletions components/omega/src/ocn/Eos.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,22 @@ enum class EosType {
Teos10Eos /// Roquet et al. 2015 75 term expansion
};

enum class EosLimits {
Funnel, ///
Cube ///
};

struct EosRange {
Real Lo;
Real Hi;
};

/// TEOS10 75-term Polynomial Equation of State
class Teos10Eos {
public:
Array2DReal SpecVolPCoeffs;
EosLimits EosLimChoice; ///< EOS clamping option in use
bool ClampingEnable; ///< EOS clamping option in use

/// constructor declaration
Teos10Eos(int NVertLayers);
Expand All @@ -52,7 +64,7 @@ class Teos10Eos {
/// Calculate the local specific volume polynomial pressure
/// coefficients
calcPCoeffs(LocSpecVolPCoeffs, KVec, ConservTemp(ICell, K),
AbsSalinity(ICell, K));
AbsSalinity(ICell, K), Pressure(ICell, K));

/// Calculate the specific volume at the given pressure
/// If KDisp is 0, we use the current pressure, otherwise we use the
Expand All @@ -78,12 +90,21 @@ class Teos10Eos {
/// TEOS-10 helpers
/// Calculate pressure polynomial coefficients for TEOS-10
KOKKOS_FUNCTION void calcPCoeffs(Array2DReal SpecVolPCoeffs, const I4 K,
const Real Ct, const Real Sa) const {
const Real Ct, const Real Sa,
const Real P) const {
constexpr Real SAu = 40.0 * 35.16504 / 35.0;
constexpr Real CTu = 40.0;
constexpr Real DeltaS = 24.0;
EosRange SRange = calcSLimits(P);
EosRange TRange = calcTLimits(Sa, P);
Real Ss = Kokkos::sqrt((Sa + DeltaS) / SAu);
Real Tt = Ct / CTu;
if (ClampingEnable) {
Real SaInRange = Kokkos::clamp(
Sa, SRange.Lo, SRange.Hi); // Salt limited to Poly75t valid range
Ss = Kokkos::sqrt((SaInRange + DeltaS) / SAu);
Tt = Kokkos::clamp(Ct, TRange.Lo, TRange.Hi) / CTu;
}

/// Coefficients for the polynomial expansion
constexpr Real V000 = 1.0769995862e-03;
Expand Down Expand Up @@ -206,8 +227,14 @@ class Teos10Eos {
const Real P) const {

constexpr Real Pu = 1e4;
Real Pp = P / Pu;

const Real Pmax = (EosLimChoice == EosLimits::Funnel) ? 8000.0 : 10000.0;
Real Pp = P / Pu;
if (ClampingEnable) {
Pp = Kokkos::min(P, Pmax) / Pu; // P limited to Poly75t valid range
LOG_INFO("P={} exceeds Pmax={}; Clamping the pressure", P, Pmax);
} else if (P > Pmax) {
LOG_WARN("P={} exceeds Pmax={}", P, Pmax);
}
Real Delta = ((((SpecVolPCoeffs(5, K) * Pp + SpecVolPCoeffs(4, K)) * Pp +
SpecVolPCoeffs(3, K)) *
Pp +
Expand All @@ -220,22 +247,120 @@ class Teos10Eos {
}

/// Calculate reference profile for TEOS-10
KOKKOS_FUNCTION Real calcRefProfile(Real P) const {
KOKKOS_FUNCTION Real calcRefProfile(const Real P) const {
constexpr Real Pu = 1e4;
constexpr Real V00 = -4.4015007269e-05;
constexpr Real V01 = 6.9232335784e-06;
constexpr Real V02 = -7.5004675975e-07;
constexpr Real V03 = 1.7009109288e-08;
constexpr Real V04 = -1.6884162004e-08;
constexpr Real V05 = 1.9613503930e-09;
Real Pp = P / Pu;
const Real Pmax = (EosLimChoice == EosLimits::Funnel) ? 8000.0 : 10000.0;
Real Pp = P / Pu;
if (ClampingEnable) {
Pp = Kokkos::min(P, Pmax) / Pu; // P limited to Poly75t valid range
} else if (P > Pmax) {
LOG_WARN("P={} exceeds Pmax={}", P, Pmax);
}

Real V0 =
(((((V05 * Pp + V04) * Pp + V03) * Pp + V02) * Pp + V01) * Pp + V00) *
Pp;
return V0;
}

/// Calculate S limits of validity given pressure p
KOKKOS_FUNCTION EosRange calcSLimits(const Real P) const {
Real Lo = 0.0;
Real Hi = 42.0;
if (EosLimChoice == EosLimits::Funnel) {
Real Lo2 = P * 5e-3 - 2.5;
Real Lo3 = 30.0;

if (P >= 500.0 && P < 6500.0) {
Lo = Kokkos::max(Lo, Lo2);
} else if (P >= 6500.0) {
Lo = Kokkos::max(Lo, Lo3);
}
}
return {Lo, Hi};
}

/// Calculate T limits of validity given p,Sa
KOKKOS_FUNCTION EosRange calcTLimits(const Real Sa, const Real P) const {
Real Lo = -15.0;
Real Hi = 95.0;

if (EosLimChoice == EosLimits::Funnel) {
if (P < 500.0) {
Lo = calcCtFreezing(Sa, P, 0.0_Real);
} else if (P < 6500.0) {
Lo = calcCtFreezing(Sa, 500.0_Real, 0.0_Real);
Hi = 31.66666666666667 - P * 3.333333333333334e-3;
} else {
Lo = calcCtFreezing(Sa, 500.0_Real, 0.0_Real);
Hi = 10.0;
}
}
if (EosLimChoice == EosLimits::Cube) {
Lo = -2.0;
Hi = 40.0;
}
return {Lo, Hi};
}

/// Calculates freezing CTemperature (polynomial error in [-5e-4,6e-4] K)
KOKKOS_FUNCTION Real calcCtFreezing(const Real Sa, const Real P,
const Real SaturationFract) const {
constexpr Real Sso = 35.16504;
constexpr Real C0 = 0.017947064327968736;
constexpr Real C1 = -6.076099099929818;
constexpr Real C2 = 4.883198653547851;
constexpr Real C3 = -11.88081601230542;
constexpr Real C4 = 13.34658511480257;
constexpr Real C5 = -8.722761043208607;
constexpr Real C6 = 2.082038908808201;
constexpr Real C7 = -7.389420998107497;
constexpr Real C8 = -2.110913185058476;
constexpr Real C9 = 0.2295491578006229;
constexpr Real C10 = -0.9891538123307282;
constexpr Real C11 = -0.08987150128406496;
constexpr Real C12 = 0.3831132432071728;
constexpr Real C13 = 1.054318231187074;
constexpr Real C14 = 1.065556599652796;
constexpr Real C15 = -0.7997496801694032;
constexpr Real C16 = 0.3850133554097069;
constexpr Real C17 = -2.078616693017569;
constexpr Real C18 = 0.8756340772729538;
constexpr Real C19 = -2.079022768390933;
constexpr Real C20 = 1.596435439942262;
constexpr Real C21 = 0.1338002171109174;
constexpr Real C22 = 1.242891021876471;

// Note: a = 0.502500117621 / Sso
constexpr Real A = 0.014289763856964;
constexpr Real B = 0.057000649899720;

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

Real CtFreez =
C0 + Sar * (C1 + X * (C2 + X * (C3 + X * (C4 + X * (C5 + C6 * X))))) +
Pr * (C7 + Pr * (C8 + C9 * Pr)) +
Sar * Pr *
(C10 + Pr * (C12 + Pr * (C15 + C21 * Sar)) +
Sar * (C13 + C17 * Pr + C19 * Sar) +
X * (C11 + Pr * (C14 + C18 * Pr) +
Sar * (C16 + C20 * Pr + C22 * Sar)));

/* Adjust for the effects of dissolved air */
CtFreez = CtFreez - SaturationFract * (1e-3) * (2.4 - A * Sa) *
(1.0 + B * (1.0 - Sa / Sso));

return CtFreez;
}

private:
const int NVertLayers;
};
Expand Down
Loading
Loading