PISM, A Parallel Ice Sheet Model  stable v2.0.6 committed by Constantine Khrulev on 2023-01-23 15:14:38 -0900
EnthalpyConverter.cc
Go to the documentation of this file.
1 // Copyright (C) 2009-2017, 2021 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 
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 //! Return `true` if ice at `(E, P)` is temperate.
71 //! Determines if E >= E_s(p), that is, if the ice is at the pressure-melting point.
72 bool EnthalpyConverter::is_temperate(double E, double P) const {
74  return is_temperate_relaxed(E, P);
75  }
76 
77  return (E >= enthalpy_cts(P));
78 }
79 
80 //! A relaxed version of `is_temperate()`.
81 /*! Returns `true` if the pressure melting temperature corresponding to `(E, P)` is within
82  `enthalpy_converter.relaxed_is_temperate_tolerance` from `fresh_water.melting_point_temperature`.
83  */
84 bool EnthalpyConverter::is_temperate_relaxed(double E, double P) const {
86 }
87 
88 
89 void EnthalpyConverter::validate_T_omega_P(double T, double omega, double P) const {
90 #if (Pism_DEBUG==1)
91  const double T_melting = melting_temperature(P);
92  if (T <= 0.0) {
93  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "T = %f <= 0 is not a valid absolute temperature",T);
94  }
95  const double eps = 1.0e-6;
96  if ((omega < 0.0 - eps) || (1.0 + eps < omega)) {
97  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "water fraction omega=%f not in range [0,1]",omega);
98  }
99  if (T > T_melting + eps) {
100  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "T=%f exceeds T_melting=%f; not allowed",T,T_melting);
101  }
102  if ((T < T_melting - eps) && (omega > 0.0 + eps)) {
103  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "T < T_melting AND omega > 0 is contradictory;"
104  " got T=%f, T_melting=%f, omega=%f",
105  T, T_melting, omega);
106  }
107 #else
108  (void) T;
109  (void) omega;
110  (void) P;
111 #endif
112 }
113 
114 void EnthalpyConverter::validate_E_P(double E, double P) const {
115 #if (Pism_DEBUG==1)
116  if (E >= enthalpy_liquid(P)) {
117  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "E=%f J/kg at P=%f Pa equals or exceeds that of liquid water (%f J/kg)",
118  E, P, enthalpy_liquid(P));
119  }
120 #else
121  (void) E;
122  (void) P;
123 #endif
124 }
125 
126 
127 //! Get pressure in ice from depth below surface using the hydrostatic assumption.
128 /*! If \f$d\f$ is the depth then
129  \f[ p = p_{\text{air}} + \rho_i g d. \f]
130 Frequently \f$d\f$ is computed from the thickess minus a level in the ice,
131 something like `ice_thickness(i, j) - z[k]`. The input depth to this routine is allowed to
132 be negative, representing a position above the surface of the ice.
133  */
134 double EnthalpyConverter::pressure(double depth) const {
135  if (depth >= 0.0) {
136  return m_p_air + m_rho_i * m_g * depth;
137  }
138 
139  return m_p_air; // at or above surface of ice
140 }
141 
142 //! Compute pressure in a column of ice. Does not check validity of `depth`.
143 void EnthalpyConverter::pressure(const std::vector<double> &depth,
144  unsigned int ks,
145  std::vector<double> &result) const {
146  for (unsigned int k = 0; k <= ks; ++k) {
147  result[k] = m_p_air + m_rho_i * m_g * depth[k];
148  }
149 }
150 
151 //! Get melting temperature from pressure p.
152 /*!
153  \f[ T_m(p) = T_{melting} - \beta p. \f]
154  */
156  return m_T_melting - m_beta * P;
157 }
158 
159 
160 //! @brief Compute the maximum allowed value of ice enthalpy
161 //! (corresponds to @f$ \omega = 1 @f$).
162 double EnthalpyConverter::enthalpy_liquid(double P) const {
163  return enthalpy_cts(P) + L(melting_temperature(P));
164 }
165 
166 
167 //! Get absolute (not pressure-adjusted) ice temperature (K) from enthalpy and pressure.
168 /*! From \ref AschwandenBuelerKhroulevBlatter,
169  \f[ T= T(E,p) = \begin{cases}
170  c_i^{-1} E + T_0, & E < E_s(p), \\
171  T_m(p), & E_s(p) \le E < E_l(p).
172  \end{cases} \f]
173 
174 We do not allow liquid water (%i.e. water fraction \f$\omega=1.0\f$) so we
175 throw an exception if \f$E \ge E_l(p)\f$.
176  */
177 double EnthalpyConverter::temperature(double E, double P) const {
178  validate_E_P(E, P);
179 
180  if (E < enthalpy_cts(P)) {
181  return temperature_cold(E);
182  }
183 
184  return melting_temperature(P);
185 }
186 
187 
188 //! Get pressure-adjusted ice temperature, in Kelvin, from enthalpy and pressure.
189 /*!
190 The pressure-adjusted temperature is:
191  \f[ T_{pa}(E,p) = T(E,p) - T_m(p) + T_{melting}. \f]
192  */
193 double EnthalpyConverter::pressure_adjusted_temperature(double E, double P) const {
194  return temperature(E, P) - melting_temperature(P) + m_T_melting;
195 }
196 
197 
198 //! Get liquid water fraction from enthalpy and pressure.
199 /*!
200  From [@ref AschwandenBuelerKhroulevBlatter],
201  @f[
202  \omega(E,p) =
203  \begin{cases}
204  0.0, & E \le E_s(p), \\
205  (E-E_s(p)) / L, & E_s(p) < E < E_l(p).
206  \end{cases}
207  @f]
208 
209  We do not allow liquid water (i.e. water fraction @f$ \omega=1.0 @f$).
210  */
211 double EnthalpyConverter::water_fraction(double E, double P) const {
212  validate_E_P(E, P);
213 
214  double E_s = enthalpy_cts(P);
215  if (E <= E_s) {
216  return 0.0;
217  }
218 
219  return (E - E_s) / L(melting_temperature(P));
220 }
221 
222 
223 //! Compute enthalpy from absolute temperature, liquid water fraction, and pressure.
224 /*! This is an inverse function to the functions \f$T(E,p)\f$ and
225 \f$\omega(E,p)\f$ [\ref AschwandenBuelerKhroulevBlatter]. It returns:
226  \f[E(T,\omega,p) =
227  \begin{cases}
228  c_i (T - T_0), & T < T_m(p) \quad\text{and}\quad \omega = 0, \\
229  E_s(p) + \omega L, & T = T_m(p) \quad\text{and}\quad \omega \ge 0.
230  \end{cases} \f]
231 Certain cases are not allowed and throw exceptions:
232 - \f$T<=0\f$ (error code 1)
233 - \f$\omega < 0\f$ or \f$\omega > 1\f$ (error code 2)
234 - \f$T>T_m(p)\f$ (error code 3)
235 - \f$T<T_m(p)\f$ and \f$\omega > 0\f$ (error code 4)
236 These inequalities may be violated in the sixth digit or so, however.
237  */
238 double EnthalpyConverter::enthalpy(double T, double omega, double P) const {
239  validate_T_omega_P(T, omega, P);
240 
241  const double T_melting = melting_temperature(P);
242 
243  if (T < T_melting) {
244  return enthalpy_cold(T);
245  }
246 
247  return enthalpy_cts(P) + omega * L(T_melting);
248 }
249 
250 
251 //! Compute enthalpy more permissively than enthalpy().
252 /*! Computes enthalpy from absolute temperature, liquid water fraction, and
253 pressure. Use this form of enthalpy() when outside sources (e.g. information
254 from a coupler) might generate a temperature above the pressure melting point or
255 generate cold ice with a positive water fraction.
256 
257 Treats temperatures above pressure-melting point as \e at the pressure-melting
258 point. Interprets contradictory case of \f$T < T_m(p)\f$ and \f$\omega > 0\f$
259 as cold ice, ignoring the water fraction (\f$\omega\f$) value.
260 
261 Calls enthalpy(), which validates its inputs.
262 
263 Computes:
264  @f[
265  E =
266  \begin{cases}
267  E(T,0.0,p), & T < T_m(p) \quad \text{and} \quad \omega \ge 0, \\
268  E(T_m(p),\omega,p), & T \ge T_m(p) \quad \text{and} \quad \omega \ge 0,
269  \end{cases}
270  @f]
271  but ensures @f$ 0 \le \omega \le 1 @f$ in second case. Calls enthalpy() for
272  @f$ E(T,\omega,p) @f$.
273  */
274 double EnthalpyConverter::enthalpy_permissive(double T, double omega, double P) const {
275  const double T_m = melting_temperature(P);
276 
277  if (T < T_m) {
278  return enthalpy(T, 0.0, P);
279  }
280 
281  // T >= T_m(P) replaced with T = T_m(P)
282  return enthalpy(T_m, std::max(0.0, std::min(omega, 1.0)), P);
283 }
284 
286  : EnthalpyConverter(config) {
287  // turn on the "cold" enthalpy converter mode
288  m_do_cold_ice_methods = true;
289  // set melting temperature to one million Kelvin so that all ice is cold
290  m_T_melting = 1e6;
291  // disable pressure-dependence of the melting temperature by setting Clausius-Clapeyron beta to
292  // zero
293  m_beta = 0.0;
294 }
295 
296 //! Latent heat of fusion of water as a function of pressure melting
297 //! temperature.
298 /*!
299 
300  Following a re-interpretation of [@ref
301  AschwandenBuelerKhroulevBlatter], we require that @f$ \Diff{H}{p} =
302  0 @f$:
303 
304  @f[
305  \Diff{H}{p} = \diff{H_w}{p} + \diff{H_w}{p}\Diff{T}{p}
306  @f]
307 
308  We assume that water is incompressible, so @f$ \Diff{T}{p} = 0 @f$
309  and the second term vanishes.
310 
311  As for the first term, equation (5) of [@ref
312  AschwandenBuelerKhroulevBlatter] defines @f$ H_w @f$ as follows:
313 
314  @f[
315  H_w = \int_{T_0}^{T_m(p)} C_i(t) dt + L + \int_{T_m(p)}^T C_w(t)dt
316  @f]
317 
318  Using the fundamental theorem of Calculus, we get
319  @f[
320  \diff{H_w}{p} = (C_i(T_m(p)) - C_w(T_m(p))) \diff{T_m(p)}{p} + \diff{L}{p}
321  @f]
322 
323  Assuming that @f$ C_i(T) = c_i @f$ and @f$ C_w(T) = c_w @f$ (i.e. specific heat
324  capacities of ice and water do not depend on temperature) and using
325  the Clausius-Clapeyron relation
326  @f[
327  T_m(p) = T_m(p_{\text{air}}) - \beta p,
328  @f]
329 
330  we get
331  @f{align}{
332  \Diff{H}{p} &= (c_i - c_w)\diff{T_m(p)}{p} + \diff{L}{p}\\
333  &= \beta(c_w - c_i) + \diff{L}{p}\\
334  @f}
335  Requiring @f$ \Diff{H}{p} = 0 @f$ implies
336  @f[
337  \diff{L}{p} = -\beta(c_w - c_i),
338  @f]
339  and so
340  @f{align}{
341  L(p) &= -\beta p (c_w - c_i) + C\\
342  &= (T_m(p) - T_m(p_{\text{air}})) (c_w - c_i) + C.
343  @f}
344 
345  Letting @f$ p = p_{\text{air}} @f$ we find @f$ C = L(p_\text{air}) = L_0 @f$, so
346  @f[
347  L(p) = (T_m(p) - T_m(p_{\text{air}})) (c_w - c_i) + L_0,
348  @f]
349  where @f$ L_0 @f$ is the latent heat of fusion of water at atmospheric
350  pressure.
351 
352  Therefore a consistent interpretation of [@ref
353  AschwandenBuelerKhroulevBlatter] requires the temperature-dependent
354  approximation of the latent heat of fusion of water given above.
355 
356  Note that this form of @f$ L(p) @f$ also follows from Kirchhoff's
357  law of thermochemistry.
358 */
359 double EnthalpyConverter::L(double T_pm) const {
360  return m_L + (m_c_w - m_c_i) * (T_pm - 273.15);
361 }
362 
363 //! Specific heat capacity of ice.
364 double EnthalpyConverter::c() const {
365  return m_c_i;
366 }
367 
368 //! Get enthalpy E_s(p) at cold-temperate transition point from pressure p.
369 /*! Returns
370  \f[ E_s(p) = c_i (T_m(p) - T_0), \f]
371  */
372 double EnthalpyConverter::enthalpy_cts(double P) const {
373  return m_c_i * (melting_temperature(P) - m_T_0);
374 }
375 
376 //! Convert temperature into enthalpy (cold case).
377 double EnthalpyConverter::enthalpy_cold(double T) const {
378  return m_c_i * (T - m_T_0);
379 }
380 
381 //! Convert enthalpy into temperature (cold case).
382 double EnthalpyConverter::temperature_cold(double E) const {
383  return (E / m_c_i) + m_T_0;
384 }
385 
386 } // end of namespace pism
ColdEnthalpyConverter(const Config &config)
double get_number(const std::string &name, UseFlag flag=REMEMBER_THIS_USE) const
bool get_flag(const std::string &name, UseFlag flag=REMEMBER_THIS_USE) const
A class for storing and accessing PISM configuration flags and parameters.
double enthalpy(double T, double omega, double P) const
Compute enthalpy from absolute temperature, liquid water fraction, and pressure.
EnthalpyConverter(const Config &config)
void validate_E_P(double E, double P) const
double m_rho_i
density of ice
bool is_temperate(double E, double P) const
double melting_temperature(double P) const
Get melting temperature from pressure p.
double m_T_tolerance
temperature tolerance used in is_temperate() in cold ice mode
double temperature(double E, double P) const
Get absolute (not pressure-adjusted) ice temperature (K) from enthalpy and pressure.
double m_beta
beta in the Clausius-Clapeyron relation ( ).
bool m_do_cold_ice_methods
if cold ice methods are selected, use is_temperate() check based on temperature, not enthalpy
double enthalpy_cold(double T) const
Convert temperature into enthalpy (cold case).
double enthalpy_liquid(double P) const
Compute the maximum allowed value of ice enthalpy (corresponds to ).
double m_c_i
specific heat capacity of ice
double c() const
Specific heat capacity of ice.
double m_g
acceleration due to gravity
double enthalpy_permissive(double T, double omega, double P) const
Compute enthalpy more permissively than enthalpy().
double m_L
latent heat of fusion of water at atmospheric pressure
double pressure_adjusted_temperature(double E, double P) const
Get pressure-adjusted ice temperature, in Kelvin, from enthalpy and pressure.
double enthalpy_cts(double P) const
Get enthalpy E_s(p) at cold-temperate transition point from pressure p.
bool is_temperate_relaxed(double E, double P) const
A relaxed version of is_temperate().
double temperature_cold(double E) const
Convert enthalpy into temperature (cold case).
double pressure(double depth) const
Get pressure in ice from depth below surface using the hydrostatic assumption.
double m_T_melting
melting temperature of pure water at atmospheric pressure
double water_fraction(double E, double P) const
Get liquid water fraction from enthalpy and pressure.
double m_c_w
specific heat capacity of pure water
double m_p_air
atmospheric pressure
double L(double T_pm) const
void validate_T_omega_P(double T, double omega, double P) const
double m_T_0
reference temperature in the definition of ice enthalpy
Converts between specific enthalpy and temperature or liquid content.
static RuntimeError formatted(const ErrorLocation &location, const char format[],...) __attribute__((format(printf
build a RuntimeError with a formatted message
#define PISM_ERROR_LOCATION
double max(const IceModelVec2S &input)
Finds maximum over all the values in an IceModelVec2S object. Ignores ghosts.
static const double k
Definition: exactTestP.cc:45
double min(const IceModelVec2S &input)
Finds minimum over all the values in an IceModelVec2S object. Ignores ghosts.