pism

[fork] customized build of PISM, the parallel ice sheet model (tillflux branch)
git clone git://src.adamsgaard.dk/pism # fast
git clone https://src.adamsgaard.dk/pism.git # slow
Log | Files | Refs | README | LICENSE Back to index

EnthalpyConverter.cc (13494B)


      1 // Copyright (C) 2009-2017 Andreas Aschwanden, Ed Bueler and Constantine Khroulev
      2 //
      3 // This file is part of PISM.
      4 //
      5 // PISM is free software; you can redistribute it and/or modify it under the
      6 // terms of the GNU General Public License as published by the Free Software
      7 // Foundation; either version 3 of the License, or (at your option) any later
      8 // version.
      9 //
     10 // PISM is distributed in the hope that it will be useful, but WITHOUT ANY
     11 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
     12 // FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
     13 // details.
     14 //
     15 // You should have received a copy of the GNU General Public License
     16 // along with PISM; if not, write to the Free Software
     17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
     18 
     19 #include "pism/util/pism_utilities.hh"
     20 #include "EnthalpyConverter.hh"
     21 #include "pism/util/ConfigInterface.hh"
     22 
     23 #include "pism/util/error_handling.hh"
     24 
     25 namespace pism {
     26 
     27 /*! @class EnthalpyConverter
     28 
     29   Maps from @f$ (H,p) @f$ to @f$ (T,\omega,p) @f$ and back.
     30 
     31   Requirements:
     32 
     33   1. A converter has to implement an invertible map @f$ (H,p) \to (T,
     34      \omega, p) @f$ *and* its inverse. Both the forward map and the
     35      inverse need to be defined for all permissible values of @f$
     36      (H,p) @f$ and @f$ (T, \omega, p) @f$, respectively.
     37 
     38   2. A converter has to be consistent with laws and parameterizations
     39      used elsewhere in the model. This includes models coupled to
     40      PISM.
     41 
     42   3. For a fixed volume of liquid (or solid) water and given two
     43      energy states @f$ (H_1, p_1) @f$ and @f$ (H_2, p_2) @f$ , let @f$
     44      \Delta U_H @f$ be the difference in internal energy of this
     45      volume between the two states *computed using enthalpy*. We require
     46      that @f$ \Delta U_T = \Delta U_H @f$ , where @f$ \Delta U_T @f$
     47      is the difference in internal energy *computed using corresponding*
     48      @f$ (T_1, \omega_1, p_1) @f$ *and* @f$ (T_2, \omega_2, p_2) @f$.
     49 
     50   4. We assume that ice and water are incompressible, so a change in
     51      pressure does no work, and @f$ \Diff{H}{p} = 0 @f$. In addition
     52      to this, for cold ice and liquid water @f$ \Diff{T}{p} = 0 @f$.
     53 */
     54 
     55 EnthalpyConverter::EnthalpyConverter(const Config &config) {
     56   m_p_air       = config.get_number("surface.pressure"); // Pa
     57   m_g           = config.get_number("constants.standard_gravity"); // m s-2
     58   m_beta        = config.get_number("constants.ice.beta_Clausius_Clapeyron"); // K Pa-1
     59   m_rho_i       = config.get_number("constants.ice.density"); // kg m-3
     60   m_c_i         = config.get_number("constants.ice.specific_heat_capacity"); // J kg-1 K-1
     61   m_c_w         = config.get_number("constants.fresh_water.specific_heat_capacity"); // J kg-1 K-1
     62   m_L           = config.get_number("constants.fresh_water.latent_heat_of_fusion"); // J kg-1
     63   m_T_melting   = config.get_number("constants.fresh_water.melting_point_temperature"); // K
     64   m_T_tolerance = config.get_number("enthalpy_converter.relaxed_is_temperate_tolerance"); // K
     65   m_T_0         = config.get_number("enthalpy_converter.T_reference"); // K
     66 
     67   m_do_cold_ice_methods  = config.get_flag("energy.temperature_based");
     68 }
     69 
     70 EnthalpyConverter::~EnthalpyConverter() {
     71   // empty
     72 }
     73 
     74 //! Return `true` if ice at `(E, P)` is temperate.
     75 //! Determines if E >= E_s(p), that is, if the ice is at the pressure-melting point.
     76 bool EnthalpyConverter::is_temperate(double E, double P) const {
     77   if (m_do_cold_ice_methods) {
     78     return is_temperate_relaxed(E, P);
     79   } else {
     80     return (E >= enthalpy_cts(P));
     81   }
     82 }
     83 
     84 //! A relaxed version of `is_temperate()`.
     85 /*! Returns `true` if the pressure melting temperature corresponding to `(E, P)` is within
     86     `enthalpy_converter.relaxed_is_temperate_tolerance` from `fresh_water.melting_point_temperature`.
     87  */
     88 bool EnthalpyConverter::is_temperate_relaxed(double E, double P) const {
     89   return (pressure_adjusted_temperature(E, P) >= m_T_melting - m_T_tolerance);
     90 }
     91 
     92 
     93 void EnthalpyConverter::validate_T_omega_P(double T, double omega, double P) const {
     94 #if (Pism_DEBUG==1)
     95   const double T_melting = melting_temperature(P);
     96   if (T <= 0.0) {
     97     throw RuntimeError::formatted(PISM_ERROR_LOCATION, "T = %f <= 0 is not a valid absolute temperature",T);
     98   }
     99   if ((omega < 0.0 - 1.0e-6) || (1.0 + 1.0e-6 < omega)) {
    100     throw RuntimeError::formatted(PISM_ERROR_LOCATION, "water fraction omega=%f not in range [0,1]",omega);
    101   }
    102   if (T > T_melting + 1.0e-6) {
    103     throw RuntimeError::formatted(PISM_ERROR_LOCATION, "T=%f exceeds T_melting=%f; not allowed",T,T_melting);
    104   }
    105   if ((T < T_melting - 1.0e-6) && (omega > 0.0 + 1.0e-6)) {
    106     throw RuntimeError::formatted(PISM_ERROR_LOCATION, "T < T_melting AND omega > 0 is contradictory;"
    107                                   " got T=%f, T_melting=%f, omega=%f",
    108                                   T, T_melting, omega);
    109   }
    110 #else
    111   (void) T;
    112   (void) omega;
    113   (void) P;
    114 #endif
    115 }
    116 
    117 void EnthalpyConverter::validate_E_P(double E, double P) const {
    118 #if (Pism_DEBUG==1)
    119   if (E >= enthalpy_liquid(P)) {
    120     throw RuntimeError::formatted(PISM_ERROR_LOCATION, "E=%f J/kg at P=%f Pa equals or exceeds that of liquid water (%f J/kg)",
    121                                   E, P, enthalpy_liquid(P));
    122   }
    123 #else
    124   (void) E;
    125   (void) P;
    126 #endif
    127 }
    128 
    129 
    130 //! Get pressure in ice from depth below surface using the hydrostatic assumption.
    131 /*! If \f$d\f$ is the depth then
    132      \f[ p = p_{\text{air}}  + \rho_i g d. \f]
    133 Frequently \f$d\f$ is computed from the thickess minus a level in the ice,
    134 something like `ice_thickness(i, j) - z[k]`.  The input depth to this routine is allowed to
    135 be negative, representing a position above the surface of the ice.
    136  */
    137 double EnthalpyConverter::pressure(double depth) const {
    138   if (depth >= 0.0) {
    139     return m_p_air + m_rho_i * m_g * depth;
    140   } else {
    141     return m_p_air; // at or above surface of ice
    142   }
    143 }
    144 
    145 //! Compute pressure in a column of ice. Does not check validity of `depth`.
    146 void EnthalpyConverter::pressure(const std::vector<double> &depth,
    147                                  unsigned int ks,
    148                                  std::vector<double> &result) const {
    149   for (unsigned int k = 0; k <= ks; ++k) {
    150     result[k] = m_p_air + m_rho_i * m_g * depth[k];
    151   }
    152 }
    153 
    154 //! Get melting temperature from pressure p.
    155 /*!
    156      \f[ T_m(p) = T_{melting} - \beta p. \f]
    157  */
    158 double EnthalpyConverter::melting_temperature(double P) const {
    159   return m_T_melting - m_beta * P;
    160 }
    161 
    162 
    163 //! @brief Compute the maximum allowed value of ice enthalpy
    164 //! (corresponds to @f$ \omega = 1 @f$).
    165 double EnthalpyConverter::enthalpy_liquid(double P) const {
    166   return enthalpy_cts(P) + L(melting_temperature(P));
    167 }
    168 
    169 
    170 //! Get absolute (not pressure-adjusted) ice temperature (K) from enthalpy and pressure.
    171 /*! From \ref AschwandenBuelerKhroulevBlatter,
    172      \f[ T= T(E,p) = \begin{cases}
    173                        c_i^{-1} E + T_0,  &  E < E_s(p), \\
    174                        T_m(p),            &  E_s(p) \le E < E_l(p).
    175                      \end{cases} \f]
    176 
    177 We do not allow liquid water (%i.e. water fraction \f$\omega=1.0\f$) so we
    178 throw an exception if \f$E \ge E_l(p)\f$.
    179  */
    180 double EnthalpyConverter::temperature(double E, double P) const {
    181   validate_E_P(E, P);
    182 
    183   if (E < enthalpy_cts(P)) {
    184     return temperature_cold(E);
    185   } else {
    186     return melting_temperature(P);
    187   }
    188 }
    189 
    190 
    191 //! Get pressure-adjusted ice temperature, in Kelvin, from enthalpy and pressure.
    192 /*!
    193 The pressure-adjusted temperature is:
    194      \f[ T_{pa}(E,p) = T(E,p) - T_m(p) + T_{melting}. \f]
    195  */
    196 double EnthalpyConverter::pressure_adjusted_temperature(double E, double P) const {
    197   return temperature(E, P) - melting_temperature(P) + m_T_melting;
    198 }
    199 
    200 
    201 //! Get liquid water fraction from enthalpy and pressure.
    202 /*!
    203   From [@ref AschwandenBuelerKhroulevBlatter],
    204    @f[
    205    \omega(E,p) =
    206    \begin{cases}
    207      0.0, & E \le E_s(p), \\
    208      (E-E_s(p)) / L, & E_s(p) < E < E_l(p).
    209    \end{cases}
    210    @f]
    211 
    212    We do not allow liquid water (i.e. water fraction @f$ \omega=1.0 @f$).
    213  */
    214 double EnthalpyConverter::water_fraction(double E, double P) const {
    215   validate_E_P(E, P);
    216 
    217   double E_s = enthalpy_cts(P);
    218   if (E <= E_s) {
    219     return 0.0;
    220   } else {
    221     return (E - E_s) / L(melting_temperature(P));
    222   }
    223 }
    224 
    225 
    226 //! Compute enthalpy from absolute temperature, liquid water fraction, and pressure.
    227 /*! This is an inverse function to the functions \f$T(E,p)\f$ and
    228 \f$\omega(E,p)\f$ [\ref AschwandenBuelerKhroulevBlatter].  It returns:
    229   \f[E(T,\omega,p) =
    230        \begin{cases}
    231          c_i (T - T_0),     & T < T_m(p) \quad\text{and}\quad \omega = 0, \\
    232          E_s(p) + \omega L, & T = T_m(p) \quad\text{and}\quad \omega \ge 0.
    233        \end{cases} \f]
    234 Certain cases are not allowed and throw exceptions:
    235 - \f$T<=0\f$ (error code 1)
    236 - \f$\omega < 0\f$ or \f$\omega > 1\f$ (error code 2)
    237 - \f$T>T_m(p)\f$ (error code 3)
    238 - \f$T<T_m(p)\f$ and \f$\omega > 0\f$ (error code 4)
    239 These inequalities may be violated in the sixth digit or so, however.
    240  */
    241 double EnthalpyConverter::enthalpy(double T, double omega, double P) const {
    242   validate_T_omega_P(T, omega, P);
    243 
    244   const double T_melting = melting_temperature(P);
    245 
    246   if (T < T_melting) {
    247     return enthalpy_cold(T);
    248   } else {
    249     return enthalpy_cts(P) + omega * L(T_melting);
    250   }
    251 }
    252 
    253 
    254 //! Compute enthalpy more permissively than enthalpy().
    255 /*! Computes enthalpy from absolute temperature, liquid water fraction, and
    256 pressure.  Use this form of enthalpy() when outside sources (e.g. information
    257 from a coupler) might generate a temperature above the pressure melting point or
    258 generate cold ice with a positive water fraction.
    259 
    260 Treats temperatures above pressure-melting point as \e at the pressure-melting
    261 point.  Interprets contradictory case of \f$T < T_m(p)\f$ and \f$\omega > 0\f$
    262 as cold ice, ignoring the water fraction (\f$\omega\f$) value.
    263 
    264 Calls enthalpy(), which validates its inputs.
    265 
    266 Computes:
    267   @f[
    268   E =
    269   \begin{cases}
    270     E(T,0.0,p),         & T < T_m(p) \quad \text{and} \quad \omega \ge 0, \\
    271     E(T_m(p),\omega,p), & T \ge T_m(p) \quad \text{and} \quad \omega \ge 0,
    272   \end{cases}
    273   @f]
    274   but ensures @f$ 0 \le \omega \le 1 @f$ in second case.  Calls enthalpy() for
    275   @f$ E(T,\omega,p) @f$.
    276  */
    277 double EnthalpyConverter::enthalpy_permissive(double T, double omega, double P) const {
    278   const double T_m = melting_temperature(P);
    279 
    280   if (T < T_m) {
    281     return enthalpy(T, 0.0, P);
    282   } else { // T >= T_m(P) replaced with T = T_m(P)
    283     return enthalpy(T_m, std::max(0.0, std::min(omega, 1.0)), P);
    284   }
    285 }
    286 
    287 ColdEnthalpyConverter::ColdEnthalpyConverter(const Config &config)
    288   : EnthalpyConverter(config) {
    289   // turn on the "cold" enthalpy converter mode
    290   m_do_cold_ice_methods = true;
    291   // set melting temperature to one million Kelvin so that all ice is cold
    292   m_T_melting = 1e6;
    293   // disable pressure-dependence of the melting temperature by setting Clausius-Clapeyron beta to
    294   // zero
    295   m_beta = 0.0;
    296 }
    297 
    298 ColdEnthalpyConverter::~ColdEnthalpyConverter() {
    299   // empty
    300 }
    301 
    302 //! Latent heat of fusion of water as a function of pressure melting
    303 //! temperature.
    304 /*!
    305 
    306   Following a re-interpretation of [@ref
    307   AschwandenBuelerKhroulevBlatter], we require that @f$ \Diff{H}{p} =
    308   0 @f$:
    309 
    310   @f[
    311   \Diff{H}{p} = \diff{H_w}{p} + \diff{H_w}{p}\Diff{T}{p}
    312   @f]
    313 
    314   We assume that water is incompressible, so @f$ \Diff{T}{p} = 0 @f$
    315   and the second term vanishes.
    316 
    317   As for the first term, equation (5) of [@ref
    318   AschwandenBuelerKhroulevBlatter] defines @f$ H_w @f$ as follows:
    319 
    320   @f[
    321   H_w = \int_{T_0}^{T_m(p)} C_i(t) dt + L + \int_{T_m(p)}^T C_w(t)dt
    322   @f]
    323 
    324   Using the fundamental theorem of Calculus, we get
    325   @f[
    326   \diff{H_w}{p} = (C_i(T_m(p)) - C_w(T_m(p))) \diff{T_m(p)}{p} + \diff{L}{p}
    327   @f]
    328 
    329   Assuming that @f$ C_i(T) = c_i @f$ and @f$ C_w(T) = c_w @f$ (i.e. specific heat
    330   capacities of ice and water do not depend on temperature) and using
    331   the Clausius-Clapeyron relation
    332   @f[
    333   T_m(p) = T_m(p_{\text{air}}) - \beta p,
    334   @f]
    335 
    336   we get
    337   @f{align}{
    338   \Diff{H}{p} &= (c_i - c_w)\diff{T_m(p)}{p} + \diff{L}{p}\\
    339   &= \beta(c_w - c_i) + \diff{L}{p}\\
    340   @f}
    341   Requiring @f$ \Diff{H}{p} = 0 @f$ implies
    342   @f[
    343   \diff{L}{p} = -\beta(c_w - c_i),
    344   @f]
    345   and so
    346   @f{align}{
    347   L(p) &= -\beta p (c_w - c_i) + C\\
    348   &= (T_m(p) - T_m(p_{\text{air}})) (c_w - c_i) + C.
    349   @f}
    350 
    351   Letting @f$ p = p_{\text{air}} @f$ we find @f$ C = L(p_\text{air}) = L_0 @f$, so
    352   @f[
    353   L(p) = (T_m(p) - T_m(p_{\text{air}})) (c_w - c_i) + L_0,
    354   @f]
    355   where @f$ L_0 @f$ is the latent heat of fusion of water at atmospheric
    356   pressure.
    357 
    358   Therefore a consistent interpretation of [@ref
    359   AschwandenBuelerKhroulevBlatter] requires the temperature-dependent
    360   approximation of the latent heat of fusion of water given above.
    361 
    362   Note that this form of @f$ L(p) @f$ also follows from Kirchhoff's
    363   law of thermochemistry.
    364 */
    365 double EnthalpyConverter::L(double T_pm) const {
    366   return m_L + (m_c_w - m_c_i) * (T_pm - 273.15);
    367 }
    368 
    369 //! Specific heat capacity of ice.
    370 double EnthalpyConverter::c() const {
    371   return m_c_i;
    372 }
    373 
    374 //! Get enthalpy E_s(p) at cold-temperate transition point from pressure p.
    375 /*! Returns
    376      \f[ E_s(p) = c_i (T_m(p) - T_0), \f]
    377  */
    378 double EnthalpyConverter::enthalpy_cts(double P) const {
    379   return m_c_i * (melting_temperature(P) - m_T_0);
    380 }
    381 
    382 //! Convert temperature into enthalpy (cold case).
    383 double EnthalpyConverter::enthalpy_cold(double T) const {
    384   return m_c_i * (T - m_T_0);
    385 }
    386 
    387 //! Convert enthalpy into temperature (cold case).
    388 double EnthalpyConverter::temperature_cold(double E) const {
    389   return (E / m_c_i) + m_T_0;
    390 }
    391 
    392 } // end of namespace pism