PISM, A Parallel Ice Sheet Model 2.3.0-79cae578d committed by Constantine Khrulev on 2026-03-22
Loading...
Searching...
No Matches
utilities.cc
Go to the documentation of this file.
1/* Copyright (C) 2016, 2017, 2018, 2019, 2020, 2022, 2023, 2024, 2025 PISM Authors
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
20#include "pism/energy/utilities.hh"
21
22#include "pism/energy/bootstrapping.hh"
23#include "pism/util/Config.hh"
24#include "pism/util/Context.hh"
25#include "pism/util/EnthalpyConverter.hh"
26#include "pism/util/Grid.hh"
27#include "pism/util/Logger.hh"
28#include "pism/util/VariableMetadata.hh"
29#include "pism/util/array/Array3D.hh"
30#include "pism/util/array/Scalar.hh"
31#include "pism/util/error_handling.hh"
32#include "pism/util/pism_utilities.hh"
33
34namespace pism {
35namespace energy {
36
37//! Compute ice enthalpy from temperature temperature by assuming the ice has zero liquid fraction.
38/*!
39First this method makes sure the temperatures is at most the pressure-melting
40value, before computing the enthalpy for that temperature, using zero liquid
41fraction.
42
43Because of how EnthalpyConverter::pressure() works, the energy
44content in the air is set to the value that ice would have if it a chunk of it
45occupied the air; the atmosphere actually has much lower energy content. It is
46done this way for regularity (i.e. dEnth/dz computations).
47*/
48void compute_enthalpy_cold(const array::Array3D &temperature, const array::Scalar &ice_thickness,
49 array::Array3D &result) {
50
51 auto grid = result.grid();
52 auto EC = grid->ctx()->enthalpy_converter();
53
54 array::AccessScope list{ &temperature, &result, &ice_thickness };
55
56 const std::vector<double> &z = temperature.levels();
57 const unsigned int Mz = z.size();
58
59 for (auto p : grid->points()) {
60 const int i = p.i(), j = p.j();
61
62 const double *Tij = temperature.get_column(i, j);
63 double *Enthij = result.get_column(i, j);
64
65 for (unsigned int k = 0; k < Mz; ++k) {
66 const double depth = ice_thickness(i, j) - z[k]; // FIXME issue #15
67 Enthij[k] = EC->enthalpy_permissive(Tij[k], 0.0, EC->pressure(depth));
68 }
69 }
70
71 result.inc_state_counter();
72
73 result.update_ghosts();
74}
75
76void compute_temperature(const array::Array3D &enthalpy, const array::Scalar &ice_thickness,
77 array::Array3D &result) {
78
79 auto grid = result.grid();
80 auto EC = grid->ctx()->enthalpy_converter();
81
82 array::AccessScope list{ &enthalpy, &ice_thickness, &result };
83
84 const std::vector<double> &z = enthalpy.levels();
85 const unsigned int Mz = z.size();
86
87 for (auto p : grid->points()) {
88 const int i = p.i(), j = p.j();
89
90 const double *E = enthalpy.get_column(i, j), H = ice_thickness(i, j);
91 double *T = result.get_column(i, j);
92
93 for (unsigned int k = 0; k < Mz; ++k) {
94 const double depth = H - z[k]; // FIXME issue #15
95 T[k] = EC->temperature(E[k], EC->pressure(depth));
96 }
97 }
98
99 result.inc_state_counter();
100
101 result.update_ghosts();
102}
103
104//! Compute `result` (enthalpy) from `temperature` and liquid fraction.
105void compute_enthalpy(const array::Array3D &temperature,
106 const array::Array3D &liquid_water_fraction,
107 const array::Scalar &ice_thickness, array::Array3D &result) {
108
109 auto grid = result.grid();
110 auto EC = grid->ctx()->enthalpy_converter();
111
112 array::AccessScope list{ &temperature, &liquid_water_fraction, &ice_thickness, &result };
113
114 const std::vector<double> &z = temperature.levels();
115 const unsigned int Mz = z.size();
116
117 for (auto p : grid->points()) {
118 const int i = p.i(), j = p.j();
119
120 const double *T = temperature.get_column(i, j);
121 const double *omega = liquid_water_fraction.get_column(i, j);
122 double *E = result.get_column(i, j);
123
124 for (unsigned int k = 0; k < Mz; ++k) {
125 const double depth = ice_thickness(i, j) - z[k]; // FIXME issue #15
126 E[k] = EC->enthalpy_permissive(T[k], omega[k], EC->pressure(depth));
127 }
128 }
129
130 result.update_ghosts();
131
132 result.inc_state_counter();
133}
134
135//! Compute the liquid fraction corresponding to enthalpy and ice_thickness.
137 const array::Scalar &ice_thickness, array::Array3D &result) {
138
139 auto grid = result.grid();
140 auto EC = grid->ctx()->enthalpy_converter();
141
142 result.set_name("liqfrac");
143 result.metadata(0).set_name("liqfrac");
144 result.metadata(0)
145 .long_name("liquid water fraction in ice (between 0 and 1)")
146 .units("1");
147
148 array::AccessScope list{ &result, &enthalpy, &ice_thickness };
149
150 ParallelSection loop(grid->com);
151 try {
152 for (auto p : grid->points()) {
153 const int i = p.i(), j = p.j();
154
155 const double *Enthij = enthalpy.get_column(i, j);
156 double *omegaij = result.get_column(i, j);
157
158 for (unsigned int k = 0; k < grid->Mz(); ++k) {
159 const double depth = ice_thickness(i, j) - grid->z(k); // FIXME issue #15
160 omegaij[k] = EC->water_fraction(Enthij[k], EC->pressure(depth));
161 }
162 }
163 } catch (...) {
164 loop.failed();
165 }
166 loop.check();
167
168 result.inc_state_counter();
169}
170
171//! @brief Compute the CTS field, CTS = E/E_s(p), from `ice_enthalpy` and `ice_thickness`, and put
172//! in `result`.
173/*!
174 * The actual cold-temperate transition surface (CTS) is the level set CTS = 1.
175 *
176 * Does not communicate ghosts for array::Array3D result.
177 */
178void compute_cts(const array::Array3D &ice_enthalpy, const array::Scalar &ice_thickness,
179 array::Array3D &result) {
180
181 auto grid = result.grid();
182 auto EC = grid->ctx()->enthalpy_converter();
183
184 result.set_name("cts");
185 result.metadata(0).set_name("cts");
186 result.metadata(0)
187 .long_name("cts = E/E_s(p), so cold-temperate transition surface is at cts = 1")
188 .units("1");
189
190 array::AccessScope list{&ice_enthalpy, &ice_thickness, &result};
191
192 const std::vector<double> &z = ice_enthalpy.levels();
193 const unsigned int Mz = z.size();
194
195 for (auto p : grid->points()) {
196 const int i = p.i(), j = p.j();
197
198 double *CTS = result.get_column(i,j);
199 const double *enthalpy = ice_enthalpy.get_column(i,j);
200
201 for (unsigned int k = 0; k < Mz; ++k) {
202 const double depth = ice_thickness(i,j) - z[k]; // FIXME issue #15
203 CTS[k] = enthalpy[k] / EC->enthalpy_cts(EC->pressure(depth));
204 }
205 }
206
207 result.inc_state_counter();
208}
209
210//! Computes the total ice enthalpy in J.
211/*!
212 Units of the specific enthalpy field are J kg^-1. We integrate
213 \f$E(t,x,y,z)\f$ over the entire ice fluid region \f$\Omega(t)\f$, multiplying
214 by the density to get units of energy:
215 \f[ E_{\text{total}}(t) = \int_{\Omega(t)} E(t,x,y,z) \rho_i \,dx\,dy\,dz. \f]
216*/
217double total_ice_enthalpy(double thickness_threshold,
218 const array::Array3D &ice_enthalpy,
219 const array::Scalar &ice_thickness) {
220 double enthalpy_sum = 0.0;
221
222 auto grid = ice_enthalpy.grid();
223 auto config = grid->ctx()->config();
224
225 auto cell_area = grid->cell_area();
226
227 const std::vector<double> &z = ice_enthalpy.levels();
228
229 array::AccessScope list{&ice_enthalpy, &ice_thickness};
230 ParallelSection loop(grid->com);
231 try {
232 for (auto p : grid->points()) {
233 const int i = p.i(), j = p.j();
234
235 const double H = ice_thickness(i, j);
236
237 if (H >= thickness_threshold) {
238 const int ks = grid->kBelowHeight(H);
239
240 const double
241 *E = ice_enthalpy.get_column(i, j);
242
243 for (int k = 0; k < ks; ++k) {
244 enthalpy_sum += cell_area * E[k] * (z[k+1] - z[k]);
245 }
246 enthalpy_sum += cell_area * E[ks] * (H - z[ks]);
247 }
248 }
249 } catch (...) {
250 loop.failed();
251 }
252 loop.check();
253
254 enthalpy_sum *= config->get_number("constants.ice.density");
255
256 return GlobalSum(grid->com, enthalpy_sum);
257}
258
259//! Create a temperature field within the ice from provided ice thickness, surface temperature, surface mass balance, and geothermal flux.
260/*!
261In bootstrapping we need to determine initial values for the temperature within
262the ice (and the bedrock). There are various data available at bootstrapping,
263but not the 3D temperature field needed as initial values for the temperature. Here
264we take a "guess" based on an assumption of steady state and a simple model of
265the vertical velocity in the column. The rule is certainly heuristic but it
266seems to work well anyway.
267
268The result is *not* the temperature field which is in steady state with the ice
269dynamics. Spinup is most-definitely needed in many applications. Such spinup
270usually starts from the temperature field computed by this procedure and then
271runs for a long time (e.g. \f$10^4\f$ to \f$10^6\f$ years), possibly with fixed
272geometry, to get closer to thermomechanically-coupled equilibrium.
273
274Consider a horizontal grid point. Suppose the surface temperature
275\f$T_s\f$, surface mass balance \f$m\f$, and geothermal flux \f$g\f$ are given at that location.
276Within the column denote the temperature by \f$T(z)\f$ at height \f$z\f$ above
277the base of the ice. Suppose the column of ice has height \f$H\f$, the ice
278thickness.
279
280There are two alternative bootstrap methods determined by the configuration parameter
281`config.get_number("bootstrapping.temperature_heuristic"))`. Allowed values are `"smb"` and
282`"quartic_guess"`.
283
2841. If the `smb` method is chosen, which is the default, and if \f$m>0\f$,
285then the method sets the ice
286temperature to the solution of the steady problem [\ref Paterson]
287 \f[\rho_i c w \frac{\partial T}{\partial z} = k_i \frac{\partial^2 T}{\partial z^2} \qquad \text{with boundary conditions} \qquad T(H) = T_s \quad \text{and} \quad \frac{\partial T}{\partial z}(0) = - \frac{g}{k_i}, \f]
288where the vertical velocity is linear between the surface value \f$w=-m\f$ and
289a velocity of zero at the base:
290 \f[w(z) = - m z / H.\f]
291(Note that because \f$m>0\f$, this vertical velocity is downward.)
292This is a two-point boundary value problem for a linear ODE. In fact, if
293\f$K = k_i / (\rho_i c)\f$ then we can write the ODE as
294 \f[K T'' + \frac{m z}{H} T' = 0.\f]
295Then let
296 \f[C_0 = \frac{g \sqrt{\pi H K}}{k_i \sqrt{2 m}}, \qquad \gamma_0 = \sqrt{\frac{mH}{2K}}.\f]
297(Note \f$\gamma_0\f$ is, up to a constant, the square root of the Peclet number
298[\ref Paterson]; compare [\ref vanderWeletal2013].) The solution to the
299two-point boundary value problem is then
300 \f[T(z) = T_s + C_0 \left(\operatorname{erf}(\gamma_0) - \operatorname{erf}\left(\gamma_0 \frac{z}{H}\right)\right).\f]
301If `usesmb` is true and \f$m \le 0\f$, then the velocity in the column, relative
302to the base, is taken to be zero. Thus the solution is
303 \f[ T(z) = \frac{g}{k_i} \left( H - z \right) + T_s, \f]
304a straight line whose slope is determined by the geothermal flux and whose value
305at the ice surface is the surface temperature, \f$T(H) = T_s\f$.
3062. If the `quartic_guess` method is chosen, the "quartic guess" formula which was in older
307versions of PISM is used. Namely, within the ice we set
308\f[T(z) = T_s + \alpha (H-z)^2 + \beta (H-z)^4\f]
309where \f$\alpha,\beta\f$ are chosen so that
310\f[\frac{\partial T}{\partial z}\Big|_{z=0} = - \frac{g}{k_i} \qquad \text{and} \qquad \frac{\partial T}{\partial z}\Big|_{z=H/4} = - \frac{g}{2 k_i}.\f]
311The purpose of the second condition is that when ice is advecting downward then
312the temperature gradient is much larger in roughly the bottom quarter of the
313ice column. However, without the surface mass balance, much less the solution
314of the stress balance equations, we cannot estimate the vertical velocity, so
315we make such a rough guess.
316
317In either case the temperature within the ice is not allowed to exceed the
318pressure-melting temperature.
319
320We set \f$T(z)=T_s\f$ above the top of the ice.
321
322This method determines \f$T(0)\f$, the ice temperature at the ice base. This
323temperature is used by BedThermalUnit::bootstrap() to determine a
324bootstrap temperature profile in the bedrock.
325*/
326void bootstrap_ice_temperature(const array::Scalar &ice_thickness,
327 const array::Scalar &ice_surface_temp,
328 const array::Scalar &surface_mass_balance,
329 const array::Scalar &basal_heat_flux,
330 array::Array3D &result) {
331
332 auto grid = result.grid();
333 auto ctx = grid->ctx();
334 auto config = ctx->config();
335 auto log = ctx->log();
336 auto EC = ctx->enthalpy_converter();
337
338 auto use_smb = config->get_string("bootstrapping.temperature_heuristic") == "smb";
339
340 if (use_smb) {
341 log->message(2,
342 " - Filling 3D ice temperatures using surface temperature"
343 " (and mass balance for velocity estimate)...\n");
344
345 } else {
346 log->message(2,
347 " - Filling 3D ice temperatures using surface temperature"
348 " (and a quartic guess without SMB)...\n");
349 }
350
351 const double
352 ice_k = config->get_number("constants.ice.thermal_conductivity"),
353 ice_density = config->get_number("constants.ice.density"),
354 ice_c = config->get_number("constants.ice.specific_heat_capacity"),
355 K = ice_k / (ice_density * ice_c),
356 T_min = config->get_number("energy.minimum_allowed_temperature"),
357 T_melting = config->get_number("constants.fresh_water.melting_point_temperature",
358 "kelvin");
359
360 array::AccessScope list{&ice_surface_temp, &surface_mass_balance,
361 &ice_thickness, &basal_heat_flux, &result};
362
363 ParallelSection loop(grid->com);
364 try {
365 for (auto p : grid->points()) {
366 const int i = p.i(), j = p.j();
367
368 const double
369 T_surface = std::min(ice_surface_temp(i, j), T_melting),
370 H = ice_thickness(i, j),
371 G = basal_heat_flux(i, j);
372
373 const unsigned int ks = grid->kBelowHeight(H);
374
375 if (G < 0.0 and ks > 0) {
376 std::string units = basal_heat_flux.metadata()["units"];
377 int Mbz = config->get_number("grid.Mbz");
378 const char *quantity = (Mbz > 0 ?
379 "temperature of the bedrock thermal layer" :
380 "geothermal flux");
382 "Negative upward heat flux (%f %s) through the bottom of the ice column\n"
383 "is not allowed by PISM's ice temperature bootstrapping method.\n"
384 "Please check the %s at i=%d, j=%d.",
385 G, units.c_str(), quantity, i, j);
386 }
387
388 if (T_surface < T_min) {
390 "T_surface(%d,%d) = %f < T_min = %f kelvin",
391 i, j, T_surface, T_min);
392 }
393
394 double *T = result.get_column(i, j);
395
396 // within ice
397 if (use_smb) { // method 1: includes surface mass balance in estimate
398
399 // Convert SMB from "kg m-2 s-1" to "m second-1".
400 const double SMB = surface_mass_balance(i, j) / ice_density;
401
402 for (unsigned int k = 0; k < ks; k++) {
403 const double z = grid->z(k);
404 T[k] = ice_temperature_guess_smb(*EC, H, z, T_surface, G, ice_k, K, SMB);
405 }
406
407 } else { // method 2: a quartic guess; does not use SMB
408
409 for (unsigned int k = 0; k < ks; k++) {
410 const double z = grid->z(k);
411 T[k] = ice_temperature_guess(*EC, H, z, T_surface, G, ice_k);
412 }
413
414 }
415
416 // Make sure that resulting temperatures are not too low.
417 for (unsigned int k = 0; k < ks; k++) {
418 if (T[k] < T_min) {
420 "T(%d,%d,%d) = %f < T_min = %f kelvin",
421 i, j, k, T[k], T_min);
422 }
423 }
424
425 // above ice
426 for (unsigned int k = ks; k < grid->Mz(); k++) {
427 T[k] = T_surface;
428 }
429 }
430 } catch (...) {
431 loop.failed();
432 }
433 loop.check();
434
435 result.update_ghosts();
436}
437
438void bootstrap_ice_enthalpy(const array::Scalar &ice_thickness,
439 const array::Scalar &ice_surface_temp,
440 const array::Scalar &surface_mass_balance,
441 const array::Scalar &basal_heat_flux,
442 array::Array3D &result) {
443
444 bootstrap_ice_temperature(ice_thickness, ice_surface_temp,
445 surface_mass_balance, basal_heat_flux,
446 result);
447
448 compute_enthalpy_cold(result, ice_thickness, result);
449}
450
451} // end of namespace energy
452} // end of namespace pism
void failed()
Indicates a failure of a parallel section.
static RuntimeError formatted(const ErrorLocation &location, const char format[],...) __attribute__((format(printf
build a RuntimeError with a formatted message
VariableMetadata & long_name(const std::string &input)
VariableMetadata & units(const std::string &input)
VariableMetadata & set_name(const std::string &name)
double get_number(const std::string &name) const
Get a single-valued scalar attribute.
Makes sure that we call begin_access() and end_access() for all accessed array::Arrays.
Definition Array.hh:66
double * get_column(int i, int j)
Definition Array3D.cc:127
A virtual class collecting methods common to ice and bedrock 3D fields.
Definition Array3D.hh:33
void set_name(const std::string &name)
Sets the variable name to name.
Definition Array.cc:345
const std::vector< double > & levels() const
Definition Array.cc:142
std::shared_ptr< const Grid > grid() const
Definition Array.cc:134
void inc_state_counter()
Increment the object state counter.
Definition Array.cc:153
void update_ghosts()
Updates ghost points.
Definition Array.cc:645
VariableMetadata & metadata(unsigned int N=0)
Returns a reference to the VariableMetadata object containing metadata for the compoment N.
Definition Array.cc:467
#define PISM_ERROR_LOCATION
const double G
Definition exactTestK.c:40
void compute_liquid_water_fraction(const array::Array3D &enthalpy, const array::Scalar &ice_thickness, array::Array3D &result)
Compute the liquid fraction corresponding to enthalpy and ice_thickness.
Definition utilities.cc:136
void bootstrap_ice_temperature(const array::Scalar &ice_thickness, const array::Scalar &ice_surface_temp, const array::Scalar &surface_mass_balance, const array::Scalar &basal_heat_flux, array::Array3D &result)
Create a temperature field within the ice from provided ice thickness, surface temperature,...
Definition utilities.cc:326
double ice_temperature_guess_smb(EnthalpyConverter &EC, double H, double z, double T_surface, double G, double ice_k, double K, double SMB)
void compute_cts(const array::Array3D &ice_enthalpy, const array::Scalar &ice_thickness, array::Array3D &result)
Compute the CTS field, CTS = E/E_s(p), from ice_enthalpy and ice_thickness, and put in result.
Definition utilities.cc:178
double ice_temperature_guess(EnthalpyConverter &EC, double H, double z, double T_surface, double G, double ice_k)
void compute_enthalpy_cold(const array::Array3D &temperature, const array::Scalar &ice_thickness, array::Array3D &result)
Compute ice enthalpy from temperature temperature by assuming the ice has zero liquid fraction.
Definition utilities.cc:48
void compute_temperature(const array::Array3D &enthalpy, const array::Scalar &ice_thickness, array::Array3D &result)
Definition utilities.cc:76
void bootstrap_ice_enthalpy(const array::Scalar &ice_thickness, const array::Scalar &ice_surface_temp, const array::Scalar &surface_mass_balance, const array::Scalar &basal_heat_flux, array::Array3D &result)
Definition utilities.cc:438
void compute_enthalpy(const array::Array3D &temperature, const array::Array3D &liquid_water_fraction, const array::Scalar &ice_thickness, array::Array3D &result)
Compute result (enthalpy) from temperature and liquid fraction.
Definition utilities.cc:105
double total_ice_enthalpy(double thickness_threshold, const array::Array3D &ice_enthalpy, const array::Scalar &ice_thickness)
Computes the total ice enthalpy in J.
Definition utilities.cc:217
static const double k
Definition exactTestP.cc:42
void GlobalSum(MPI_Comm comm, double *local, double *result, int count)