PISM, A Parallel Ice Sheet Model 2.3.0-79cae578d committed by Constantine Khrulev on 2026-03-22
Loading...
Searching...
No Matches
TemperatureModel.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/TemperatureModel.hh"
21#include "pism/energy/tempSystem.hh"
22#include "pism/energy/utilities.hh"
23#include "pism/util/Vars.hh"
24#include "pism/util/array/CellType.hh"
25#include "pism/util/io/File.hh"
26#include "pism/util/pism_utilities.hh"
27#include "pism/util/Logger.hh"
28#include "pism/util/io/IO_Flags.hh"
29
30namespace pism {
31namespace energy {
32
34 std::shared_ptr<const Grid> grid,
35 std::shared_ptr<const stressbalance::StressBalance> stress_balance)
36 : EnergyModel(grid, stress_balance),
37 m_ice_temperature(m_grid, "temp", array::WITH_GHOSTS, m_grid->z()) {
38
40 .long_name("ice temperature")
41 .units("kelvin")
42 .standard_name("land_ice_temperature");
43 m_ice_temperature.metadata()["valid_min"] = {0.0};
44}
45
49
50void TemperatureModel::restart_impl(const File &input_file, int record) {
51
52 m_log->message(2, "* Restarting the temperature-based energy balance model from %s...\n",
53 input_file.name().c_str());
54
55 m_basal_melt_rate.read(input_file, record);
56
57 const array::Scalar &ice_thickness = *m_grid->variables().get_2d_scalar("land_ice_thickness");
58
60 m_ice_temperature.read(input_file, record);
61 } else {
62 init_enthalpy(input_file, false, record);
64 }
65
66 regrid("Temperature-based energy balance model", m_basal_melt_rate, REGRID_WITHOUT_REGRID_VARS);
67 regrid("Temperature-based energy balance model", m_ice_temperature, REGRID_WITHOUT_REGRID_VARS);
68
70}
71
73 const array::Scalar &ice_thickness,
74 const array::Scalar &surface_temperature,
75 const array::Scalar &climatic_mass_balance,
76 const array::Scalar &basal_heat_flux) {
77
78 m_log->message(2, "* Bootstrapping the temperature-based energy balance model from %s...\n",
79 input_file.name().c_str());
80
81 m_basal_melt_rate.regrid(input_file,
82 io::Default(m_config->get_number("bootstrapping.defaults.bmelt")));
83 regrid("Temperature-based energy balance model", m_basal_melt_rate, REGRID_WITHOUT_REGRID_VARS);
84
85 int temp_revision = m_ice_temperature.state_counter();
86 regrid("Temperature-based energy balance model", m_ice_temperature, REGRID_WITHOUT_REGRID_VARS);
87
88 if (temp_revision == m_ice_temperature.state_counter()) {
89 bootstrap_ice_temperature(ice_thickness, surface_temperature, climatic_mass_balance,
90 basal_heat_flux, m_ice_temperature);
91 }
92
94}
95
97 const array::Scalar &ice_thickness,
98 const array::Scalar &surface_temperature,
99 const array::Scalar &climatic_mass_balance,
100 const array::Scalar &basal_heat_flux) {
101
102 m_log->message(2, "* Bootstrapping the temperature-based energy balance model...\n");
103
105 regrid("Temperature-based energy balance model", m_basal_melt_rate, REGRID_WITHOUT_REGRID_VARS);
106
107 int temp_revision = m_ice_temperature.state_counter();
108 regrid("Temperature-based energy balance model", m_ice_temperature, REGRID_WITHOUT_REGRID_VARS);
109
110 if (temp_revision == m_ice_temperature.state_counter()) {
111 bootstrap_ice_temperature(ice_thickness, surface_temperature, climatic_mass_balance,
112 basal_heat_flux, m_ice_temperature);
113 }
114
116}
117
118//! Takes a semi-implicit time-step for the temperature equation.
119/*!
120This method should be kept because it is worth having alternative physics, and
121 so that older results can be reproduced. In particular, this method is
122 documented by papers [\ref BBL,\ref BBssasliding]. The new browser page
123 \ref bombproofenth essentially documents the cold-ice-BOMBPROOF method here, but
124 the newer enthalpy-based method is slightly different and (we hope) a superior
125 implementation of the conservation of energy principle.
126
127 The conservation of energy equation written in terms of temperature is
128 \f[ \rho c_p(T) \frac{dT}{dt} = k \frac{\partial^2 T}{\partial z^2} + \Sigma,\f]
129 where \f$T(t,x,y,z)\f$ is the temperature of the ice. This equation is the shallow approximation
130 of the full 3D conservation of energy. Note \f$dT/dt\f$ stands for the material
131 derivative, so advection is included. Here \f$\rho\f$ is the density of ice,
132 \f$c_p\f$ is its specific heat, and \f$k\f$ is its conductivity. Also \f$\Sigma\f$ is the volume
133 strain heating (with SI units of \f$J/(\text{s} \text{m}^3) = \text{W}\,\text{m}^{-3}\f$).
134
135 We handle horizontal advection explicitly by first-order upwinding. We handle
136 vertical advection implicitly by centered differencing when possible, and retreat to
137 implicit first-order upwinding when necessary. There is a CFL condition
138 for the horizontal explicit upwinding [\ref MortonMayers]. We report
139 any CFL violations, but they are designed to not occur.
140
141 The vertical conduction term is always handled implicitly (%i.e. by backward Euler).
142
143 We work from the bottom of the column upward in building the system to solve
144 (in the semi-implicit time-stepping scheme). The excess energy above pressure melting
145 is converted to melt-water, and that a fraction of this melt water is transported to
146 the ice base according to the scheme in excessToFromBasalMeltLayer().
147
148 The method uses equally-spaced calculation but the columnSystemCtx
149 methods coarse_to_fine(), fine_to_coarse() interpolate
150 back-and-forth from this equally-spaced computational grid to the
151 (usually) non-equally spaced storage grid.
152
153 An instance of tempSystemCtx is used to solve the tridiagonal system set-up here.
154
155 In this procedure two scalar fields are modified: basal_melt_rate and m_work.
156 But basal_melt_rate will never need to communicate ghosted values (horizontal stencil
157 neighbors). The ghosted values for m_ice_temperature are updated from the values in m_work in the
158 communication done by energy_step().
159
160 The (older) scheme cold-ice-BOMBPROOF, implemented here, is very reliable, but there is
161 still an extreme and rare fjord situation which causes trouble. For example, it
162 occurs in one column of ice in one fjord perhaps only once
163 in a 200ka simulation of the whole sheet, in my (ELB) experience modeling the Greenland
164 ice sheet. It causes the discretized advection bulge to give temperatures below that
165 of the coldest ice anywhere, a continuum impossibility. So as a final protection
166 there is a "bulge limiter" which sets the temperature to the surface temperature of
167 the column minus the bulge maximum (15 K) if it is below that level. The number of
168 times this occurs is reported as a "BPbulge" percentage.
169 */
170void TemperatureModel::update_impl(double t, double dt, const Inputs &inputs) {
171 // current time does not matter here
172 (void) t;
173
174 using mask::ocean;
175
176 Logger log(MPI_COMM_SELF, m_log->get_threshold());
177
178 const double
179 ice_density = m_config->get_number("constants.ice.density"),
180 ice_c = m_config->get_number("constants.ice.specific_heat_capacity"),
181 L = m_config->get_number("constants.fresh_water.latent_heat_of_fusion"),
182 melting_point_temp = m_config->get_number("constants.fresh_water.melting_point_temperature"),
183 beta_CC_grad = m_config->get_number("constants.ice.beta_Clausius_Clapeyron") * ice_density * m_config->get_number("constants.standard_gravity");
184
185 const bool allow_above_melting = m_config->get_flag("energy.allow_temperature_above_melting");
186
187 // this is bulge limit constant in K; is max amount by which ice
188 // or bedrock can be lower than surface temperature
189 const double bulge_max = m_config->get_number("energy.enthalpy.cold_bulge_max") / ice_c;
190
191 inputs.check();
192 const array::Array3D
193 &strain_heating3 = *inputs.volumetric_heating_rate,
194 &u3 = *inputs.u3,
195 &v3 = *inputs.v3,
196 &w3 = *inputs.w3;
197
198 const auto &cell_type = *inputs.cell_type;
199
200 const array::Scalar
201 &basal_frictional_heating = *inputs.basal_frictional_heating,
202 &basal_heat_flux = *inputs.basal_heat_flux,
203 &shelf_base_temp = *inputs.shelf_base_temp,
204 &ice_surface_temp = *inputs.surface_temp,
205 &till_water_thickness = *inputs.till_water_thickness;
206
207 const array::Scalar1 &ice_thickness = *inputs.ice_thickness;
208
209 array::AccessScope list{&ice_surface_temp, &shelf_base_temp, &ice_thickness,
210 &cell_type, &basal_heat_flux, &till_water_thickness, &basal_frictional_heating,
211 &u3, &v3, &w3, &strain_heating3, &m_basal_melt_rate, &m_ice_temperature, &m_work};
212
213 energy::tempSystemCtx system(m_grid->z(), "temperature",
214 m_grid->dx(), m_grid->dy(), dt,
215 *m_config,
216 m_ice_temperature, u3, v3, w3, strain_heating3);
217
218 double dz = system.dz();
219 const std::vector<double>& z_fine = system.z();
220 size_t Mz_fine = z_fine.size();
221 std::vector<double> x(Mz_fine);// space for solution of system
222 std::vector<double> Tnew(Mz_fine); // post-processed solution
223
224 // counts unreasonably low temperature values; deprecated?
225 unsigned int maxLowTempCount = m_config->get_number("energy.max_low_temperature_count");
226 const double T_minimum = m_config->get_number("energy.minimum_allowed_temperature");
227
228 double margin_threshold = m_config->get_number("energy.margin_ice_thickness_limit");
229
230 ParallelSection loop(m_grid->com);
231 try {
232 for (auto p : m_grid->points()) {
233 const int i = p.i(), j = p.j();
234
235 MaskValue mask = static_cast<MaskValue>(cell_type.as_int(i,j));
236
237 const double H = ice_thickness(i, j);
238 const double T_surface = ice_surface_temp(i, j);
239
240 system.initThisColumn(i, j,
241 marginal(ice_thickness, i, j, margin_threshold),
242 mask, H);
243
244 const int ks = system.ks();
245
246 if (ks > 0) { // if there are enough points in ice to bother ...
247
248 if (system.lambda() < 1.0) {
249 m_stats.reduced_accuracy_counter += 1; // count columns with lambda < 1
250 }
251
252 // set boundary values for tridiagonal system
253 system.setSurfaceBoundaryValuesThisColumn(T_surface);
254 system.setBasalBoundaryValuesThisColumn(basal_heat_flux(i,j),
255 shelf_base_temp(i,j),
256 basal_frictional_heating(i,j));
257
258 // solve the system for this column; melting not addressed yet
259 system.solveThisColumn(x);
260 } // end of "if there are enough points in ice to bother ..."
261
262 // prepare for melting/refreezing
263 double bwatnew = till_water_thickness(i,j);
264
265 // insert solution for generic ice segments
266 for (int k=1; k <= ks; k++) {
267 if (allow_above_melting) { // in the ice
268 Tnew[k] = x[k];
269 } else {
270 const double
271 Tpmp = melting_point_temp - beta_CC_grad * (H - z_fine[k]); // FIXME issue #15
272 if (x[k] > Tpmp) {
273 Tnew[k] = Tpmp;
274 double Texcess = x[k] - Tpmp; // always positive
275 column_drainage(ice_density, ice_c, L, z_fine[k], dz, &Texcess, &bwatnew);
276 // Texcess will always come back zero here; ignore it
277 } else {
278 Tnew[k] = x[k];
279 }
280 }
281 if (Tnew[k] < T_minimum) {
282 log.message(1,
283 " [[too low (<200) ice segment temp T = %f at %d, %d, %d;"
284 " proc %d; mask=%d; w=%f m year-1]]\n",
285 Tnew[k], i, j, k, m_grid->rank(), mask,
286 units::convert(m_sys, system.w(k), "m second^-1", "m year^-1"));
287
289 }
290 if (Tnew[k] < T_surface - bulge_max) {
291 Tnew[k] = T_surface - bulge_max;
293 }
294 }
295
296 // insert solution for ice base segment
297 if (ks > 0) {
298 if (allow_above_melting == true) { // ice/rock interface
299 Tnew[0] = x[0];
300 } else { // compute diff between x[k0] and Tpmp; melt or refreeze as appropriate
301 const double Tpmp = melting_point_temp - beta_CC_grad * H; // FIXME issue #15
302 double Texcess = x[0] - Tpmp; // positive or negative
303 if (ocean(mask)) {
304 // when floating, only half a segment has had its temperature raised
305 // above Tpmp
306 column_drainage(ice_density, ice_c, L, 0.0, dz/2.0, &Texcess, &bwatnew);
307 } else {
308 column_drainage(ice_density, ice_c, L, 0.0, dz, &Texcess, &bwatnew);
309 }
310 Tnew[0] = Tpmp + Texcess;
311 if (Tnew[0] > (Tpmp + 0.00001)) {
312 throw RuntimeError(PISM_ERROR_LOCATION, "updated temperature came out above Tpmp");
313 }
314 }
315 if (Tnew[0] < T_minimum) {
316 log.message(1,
317 " [[too low (<200) ice/bedrock segment temp T = %f at %d,%d;"
318 " proc %d; mask=%d; w=%f]]\n",
319 Tnew[0],i,j,m_grid->rank(), mask,
320 units::convert(m_sys, system.w(0), "m second^-1", "m year^-1"));
321
323 }
324 if (Tnew[0] < T_surface - bulge_max) {
325 Tnew[0] = T_surface - bulge_max;
327 }
328 }
329
330 // set to air temp above ice
331 for (unsigned int k = ks; k < Mz_fine; k++) {
332 Tnew[k] = T_surface;
333 }
334
335 // transfer column into m_work; communication later
336 system.fine_to_coarse(Tnew, i, j, m_work);
337
338 // basal_melt_rate(i,j) is rate of mass loss at bottom of ice
339 if (ocean(mask)) {
340 m_basal_melt_rate(i,j) = 0.0;
341 } else {
342 // basalMeltRate is rate of change of bwat; can be negative
343 // (subglacial water freezes-on); note this rate is calculated
344 // *before* limiting or other nontrivial modelling of bwat,
345 // which is Hydrology's job
346 m_basal_melt_rate(i,j) = (bwatnew - till_water_thickness(i,j)) / dt;
347 } // end of the grounded case
348 }
349 } catch (...) {
350 loop.failed();
351 }
352 loop.check();
353
355 if (m_stats.low_temperature_counter > maxLowTempCount) {
356 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "too many low temps: %d",
358 }
359
360 // copy to m_ice_temperature, updating ghosts
362
363 // Set ice enthalpy in place. EnergyModel::update will scatter ghosts
364 compute_enthalpy_cold(m_work, ice_thickness, m_work);
365}
366
367std::set<VariableMetadata> TemperatureModel::state_impl() const {
368 // ice enthalpy is not a part of the model state
370}
371
373 m_ice_temperature.write(output);
374 m_basal_melt_rate.write(output);
375 // ice enthalpy is not a part of the model state
376}
377
378//! Compute the melt water which should go to the base if \f$T\f$ is above pressure-melting.
379void TemperatureModel::column_drainage(const double rho, const double c, const double L,
380 const double z, const double dz,
381 double *Texcess, double *bwat) const {
382
383 const double
384 darea = m_grid->cell_area(),
385 dvol = darea * dz,
386 dE = rho * c * (*Texcess) * dvol,
387 massmelted = dE / L;
388
389 if (*Texcess >= 0.0) {
390 // T is at or above pressure-melting temp, so temp needs to be set to
391 // pressure-melting; a fraction of excess energy is turned into melt water at base
392 // note massmelted is POSITIVE!
393 const double FRACTION_TO_BASE
394 = (z < 100.0) ? 0.2 * (100.0 - z) / 100.0 : 0.0;
395 // note: ice-equiv thickness:
396 *bwat += (FRACTION_TO_BASE * massmelted) / (rho * darea);
397 *Texcess = 0.0;
398 } else { // neither Texcess nor bwat needs to change if Texcess < 0.0
399 // Texcess negative; only refreeze (i.e. reduce bwat) if at base and bwat > 0.0
400 // note ONLY CALLED IF AT BASE! note massmelted is NEGATIVE!
401 if (z > 0.00001) {
402 throw RuntimeError(PISM_ERROR_LOCATION, "excessToBasalMeltLayer() called with z not at base and negative Texcess");
403 }
404 if (*bwat > 0.0) {
405 const double thicknessToFreezeOn = - massmelted / (rho * darea);
406 if (thicknessToFreezeOn <= *bwat) { // the water *is* available to freeze on
407 *bwat -= thicknessToFreezeOn;
408 *Texcess = 0.0;
409 } else { // only refreeze bwat thickness of water; update Texcess
410 *bwat = 0.0;
411 const double dTemp = L * (*bwat) / (c * dz);
412 *Texcess += dTemp;
413 }
414 }
415 // note: if *bwat == 0 and Texcess < 0.0 then Texcess unmolested; temp will go down
416 }
417}
418
419} // end of namespace energy
420} // end of namespace
const units::System::Ptr m_sys
unit system used by this component
Definition Component.hh:162
std::shared_ptr< const Config > m_config
configuration database used by this component
Definition Component.hh:160
const std::shared_ptr< const Grid > m_grid
grid used by this component
Definition Component.hh:158
void regrid(const std::string &module_name, array::Array &variable, RegriddingFlag flag=NO_REGRID_WITHOUT_REGRID_VARS)
Definition Component.cc:152
std::shared_ptr< const Logger > m_log
logger (for easy access)
Definition Component.hh:164
bool variable_exists(const std::string &short_name) const
Checks if a variable exists.
Definition File.cc:378
std::string name() const
Definition File.cc:274
High-level PISM I/O class.
Definition File.hh:57
void message(int threshold, const char format[],...) const __attribute__((format(printf
Print a message to the log.
Definition Logger.cc:50
A basic logging class.
Definition Logger.hh:40
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 & standard_name(const std::string &input)
std::string get_name() const
Makes sure that we call begin_access() and end_access() for all accessed array::Arrays.
Definition Array.hh:66
void copy_from(const Array2D< T > &source)
Definition Array2D.hh:101
void copy_from(const Array3D &input)
Definition Array3D.cc:215
A virtual class collecting methods common to ice and bedrock 3D fields.
Definition Array3D.hh:33
void read(const std::string &filename, unsigned int time)
Definition Array.cc:753
void write(const OutputFile &file) const
Definition Array.cc:859
int state_counter() const
Get the object state counter.
Definition Array.cc:130
void regrid(const std::string &filename, io::Default default_value)
Definition Array.cc:758
VariableMetadata & metadata(unsigned int N=0)
Returns a reference to the VariableMetadata object containing metadata for the compoment N.
Definition Array.cc:467
const std::vector< double > & z() const
void fine_to_coarse(const std::vector< double > &input, int i, int j, array::Array3D &output) const
unsigned int ks() const
void init_enthalpy(const File &input_file, bool regrid, int record)
Initialize enthalpy by reading it from a file, or by reading temperature and liquid water fraction,...
EnergyModelStats m_stats
const array::Scalar & basal_melt_rate() const
Basal melt rate in grounded areas. (It is set to zero elsewhere.)
array::Array3D m_ice_enthalpy
array::Scalar m_basal_melt_rate
const array::Scalar1 * ice_thickness
const array::Array3D * w3
const array::Array3D * v3
const array::Scalar * shelf_base_temp
const array::Scalar * basal_heat_flux
const array::Scalar * basal_frictional_heating
const array::Scalar * till_water_thickness
const array::Array3D * u3
const array::Scalar * surface_temp
const array::CellType * cell_type
const array::Array3D * volumetric_heating_rate
const array::Array3D & temperature() const
void initialize_impl(const array::Scalar &basal_melt_rate, const array::Scalar &ice_thickness, const array::Scalar &surface_temperature, const array::Scalar &climatic_mass_balance, const array::Scalar &basal_heat_flux)
void write_state_impl(const OutputFile &output) const
The default (empty implementation).
std::set< VariableMetadata > state_impl() const
void restart_impl(const File &input_file, int record)
TemperatureModel(std::shared_ptr< const Grid > grid, std::shared_ptr< const stressbalance::StressBalance > stress_balance)
void column_drainage(const double rho, const double c, const double L, const double z, const double dz, double *Texcess, double *bwat) const
Compute the melt water which should go to the base if is above pressure-melting.
void update_impl(double t, double dt, const Inputs &inputs)
Takes a semi-implicit time-step for the temperature equation.
void bootstrap_impl(const File &input_file, const array::Scalar &ice_thickness, const array::Scalar &surface_temperature, const array::Scalar &climatic_mass_balance, const array::Scalar &basal_heat_flux)
void initThisColumn(int i, int j, bool is_marginal, MaskValue new_mask, double ice_thickness)
Definition tempSystem.cc:68
void setSurfaceBoundaryValuesThisColumn(double my_Ts)
Definition tempSystem.cc:94
void solveThisColumn(std::vector< double > &x)
void setBasalBoundaryValuesThisColumn(double my_G0, double my_Tshelfbase, double my_Rb)
Tridiagonal linear system for vertical column of temperature-based conservation of energy problem.
Definition tempSystem.hh:49
#define PISM_ERROR_LOCATION
static const double L
Definition exactTestL.cc:40
#define rho
Definition exactTestM.c:35
std::set< VariableMetadata > metadata(std::initializer_list< const Array * > vecs)
Definition Array.cc:1244
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
bool marginal(const array::Scalar1 &thickness, int i, int j, double threshold)
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
bool ocean(int M)
An ocean cell (floating ice or ice-free).
Definition Mask.hh:40
double convert(System::Ptr system, double input, const std::string &spec1, const std::string &spec2)
Convert a quantity from unit1 to unit2.
Definition Units.cc:70
static const double k
Definition exactTestP.cc:42
MaskValue
Definition Mask.hh:30
void GlobalSum(MPI_Comm comm, double *local, double *result, int count)