PISM, A Parallel Ice Sheet Model 2.3.0-79cae578d committed by Constantine Khrulev on 2026-03-22
Loading...
Searching...
No Matches
EnthalpyModel.cc
Go to the documentation of this file.
1/* Copyright (C) 2016, 2017, 2018, 2022, 2023, 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/EnthalpyModel.hh"
21#include "pism/energy/DrainageCalculator.hh"
22#include "pism/energy/enthSystem.hh"
23#include "pism/energy/utilities.hh"
24#include "pism/util/Context.hh"
25#include "pism/util/EnthalpyConverter.hh"
26#include "pism/util/array/CellType.hh"
27#include "pism/util/io/File.hh"
28#include "pism/util/Logger.hh"
29#include "pism/util/io/IO_Flags.hh"
30
31namespace pism {
32namespace energy {
33
34EnthalpyModel::EnthalpyModel(std::shared_ptr<const Grid> grid,
35 std::shared_ptr<const stressbalance::StressBalance> stress_balance)
36 : EnergyModel(grid, stress_balance) {
37 // empty
38}
39
40void EnthalpyModel::restart_impl(const File &input_file, int record) {
41
42 m_log->message(2, "* Restarting the enthalpy-based energy balance model from %s...\n",
43 input_file.name().c_str());
44
45 m_basal_melt_rate.read(input_file, record);
46 init_enthalpy(input_file, false, record);
47
48 regrid("Energy balance model", m_basal_melt_rate, REGRID_WITHOUT_REGRID_VARS);
50}
51
52void EnthalpyModel::bootstrap_impl(const File &input_file,
53 const array::Scalar &ice_thickness,
54 const array::Scalar &surface_temperature,
55 const array::Scalar &climatic_mass_balance,
56 const array::Scalar &basal_heat_flux) {
57
58 m_log->message(2, "* Bootstrapping the enthalpy-based energy balance model from %s...\n",
59 input_file.name().c_str());
60
61 m_basal_melt_rate.regrid(input_file,
62 io::Default(m_config->get_number("bootstrapping.defaults.bmelt")));
63
64 regrid("Energy balance model", m_basal_melt_rate, REGRID_WITHOUT_REGRID_VARS);
65
66 int enthalpy_revision = m_ice_enthalpy.state_counter();
68
69 if (enthalpy_revision == m_ice_enthalpy.state_counter()) {
70 bootstrap_ice_enthalpy(ice_thickness, surface_temperature, climatic_mass_balance,
71 basal_heat_flux, m_ice_enthalpy);
72 }
73}
74
76 const array::Scalar &ice_thickness,
77 const array::Scalar &surface_temperature,
78 const array::Scalar &climatic_mass_balance,
79 const array::Scalar &basal_heat_flux) {
80
81 m_log->message(2, "* Bootstrapping the enthalpy-based energy balance model...\n");
82
84
85 regrid("Energy balance model", m_basal_melt_rate, REGRID_WITHOUT_REGRID_VARS);
86
87 int enthalpy_revision = m_ice_enthalpy.state_counter();
89
90 if (enthalpy_revision == m_ice_enthalpy.state_counter()) {
91 bootstrap_ice_enthalpy(ice_thickness, surface_temperature, climatic_mass_balance,
92 basal_heat_flux, m_ice_enthalpy);
93 }
94}
95
96//! Update ice enthalpy field based on conservation of energy.
97/*!
98This method is documented by the page \ref bombproofenth and by [\ref
99AschwandenBuelerKhroulevBlatter].
100
101This method updates array::Array3D m_work and array::Scalar basal_melt_rate.
102No communication of ghosts is done for any of these fields.
103
104We use an instance of enthSystemCtx.
105
106Regarding drainage, see [\ref AschwandenBuelerKhroulevBlatter] and references therein.
107 */
108
109void EnthalpyModel::update_impl(double t, double dt, const Inputs &inputs) {
110 // current time does not matter here
111 (void) t;
112
113 auto EC = m_grid->ctx()->enthalpy_converter();
114
115 const double
116 ice_density = m_config->get_number("constants.ice.density"), // kg m-3
117 bulgeEnthMax = m_config->get_number("energy.enthalpy.cold_bulge_max"), // J kg-1
118 target_water_fraction = m_config->get_number("energy.drainage_target_water_fraction");
119
121
122 inputs.check();
123
124 // give them names that are a bit shorter...
125 const array::Array3D
126 &strain_heating3 = *inputs.volumetric_heating_rate,
127 &u3 = *inputs.u3,
128 &v3 = *inputs.v3,
129 &w3 = *inputs.w3;
130
131 const auto &cell_type = *inputs.cell_type;
132
133 const array::Scalar
134 &basal_frictional_heating = *inputs.basal_frictional_heating,
135 &basal_heat_flux = *inputs.basal_heat_flux,
136 &surface_liquid_fraction = *inputs.surface_liquid_fraction,
137 &shelf_base_temp = *inputs.shelf_base_temp,
138 &ice_surface_temp = *inputs.surface_temp,
139 &till_water_thickness = *inputs.till_water_thickness;
140
141 const array::Scalar1 &ice_thickness = *inputs.ice_thickness;
142
143 energy::enthSystemCtx system(m_grid->z(), "energy.enthalpy", m_grid->dx(), m_grid->dy(), dt,
144 *m_config, m_ice_enthalpy, u3, v3, w3, strain_heating3, EC);
145
146 const size_t Mz_fine = system.z().size();
147 const double dz = system.dz();
148 std::vector<double> Enthnew(Mz_fine); // new enthalpy in column
149
150 array::AccessScope list{&ice_surface_temp, &shelf_base_temp, &surface_liquid_fraction,
151 &ice_thickness, &basal_frictional_heating, &basal_heat_flux, &till_water_thickness,
152 &cell_type, &u3, &v3, &w3, &strain_heating3, &m_basal_melt_rate, &m_ice_enthalpy,
153 &m_work};
154
155 double margin_threshold = m_config->get_number("energy.margin_ice_thickness_limit");
156
157 unsigned int liquifiedCount = 0;
158
159 ParallelSection loop(m_grid->com);
160 try {
161 for (auto pt : m_grid->points()) {
162 const int i = pt.i(), j = pt.j();
163
164 const double H = ice_thickness(i, j);
165
166 system.init(i, j,
167 marginal(ice_thickness, i, j, margin_threshold),
168 H);
169
170 // enthalpy and pressures at top of ice
171 const double
172 depth_ks = H - system.ks() * dz,
173 p_ks = EC->pressure(depth_ks); // FIXME issue #15
174
175 const double Enth_ks = EC->enthalpy_permissive(ice_surface_temp(i, j),
176 surface_liquid_fraction(i, j), p_ks);
177
178 const bool ice_free_column = (system.ks() == 0);
179
180 // deal completely with columns with no ice; enthalpy and basal_melt_rate need setting
181 if (ice_free_column) {
182 m_work.set_column(i, j, Enth_ks);
183 // The floating basal melt rate will be set later; cover this
184 // case and set to zero for now. Also, there is no basal melt
185 // rate on ice free land and ice free ocean
186 m_basal_melt_rate(i, j) = 0.0;
187 continue;
188 } // end of if (ice_free_column)
189
190 if (system.lambda() < 1.0) {
191 m_stats.reduced_accuracy_counter += 1; // count columns with lambda < 1
192 }
193
194 const bool
195 is_floating = cell_type.ocean(i, j),
196 base_is_warm = system.Enth(0) >= system.Enth_s(0),
197 above_base_is_warm = system.Enth(1) >= system.Enth_s(1);
198
199 // set boundary conditions and update enthalpy
200 {
201 system.set_surface_dirichlet_bc(Enth_ks);
202
203 // determine lowest-level equation at bottom of ice; see
204 // decision chart in the source code browser and page
205 // documenting BOMBPROOF
206 if (is_floating) {
207 // floating base: Dirichlet application of known temperature from ocean
208 // coupler; assumes base of ice shelf has zero liquid fraction
209 double Enth0 = EC->enthalpy_permissive(shelf_base_temp(i, j), 0.0, EC->pressure(H));
210
211 system.set_basal_dirichlet_bc(Enth0);
212 } else {
213 // grounded ice warm and wet
214 if (base_is_warm && (till_water_thickness(i, j) > 0.0)) {
215 if (above_base_is_warm) {
216 // temperate layer at base (Neumann) case: q . n = 0 (K0 grad E . n = 0)
217 system.set_basal_heat_flux(0.0);
218 } else {
219 // only the base is warm: E = E_s(p) (Dirichlet)
220 // ( Assumes ice has zero liquid fraction. Is this a valid assumption here?
221 system.set_basal_dirichlet_bc(system.Enth_s(0));
222 }
223 } else {
224 // (Neumann) case: q . n = q_lith . n + F_b
225 // a) cold and dry base, or
226 // b) base that is still warm from the last time step, but without basal water
227 system.set_basal_heat_flux(basal_heat_flux(i, j) + basal_frictional_heating(i, j));
228 }
229 }
230
231 // solve the system
232 system.solve(Enthnew);
233
234 }
235
236 // post-process (drainage and bulge-limiting)
237 double Hdrainedtotal = 0.0;
238 {
239 // drain ice segments by mechanism in [\ref AschwandenBuelerKhroulevBlatter],
240 // using DrainageCalculator dc
241 for (unsigned int k=0; k < system.ks(); k++) {
242 if (Enthnew[k] > system.Enth_s(k)) { // avoid doing any more work if cold
243
244 const double
245 depth = H - k * dz,
246 p = EC->pressure(depth), // FIXME issue #15
247 T_m = EC->melting_temperature(p),
248 L = EC->L(T_m);
249
250 if (Enthnew[k] >= system.Enth_s(k) + 0.5 * L) {
251 liquifiedCount++; // count these rare events...
252 Enthnew[k] = system.Enth_s(k) + 0.5 * L; // but lose the energy
253 }
254
255 double omega = EC->water_fraction(Enthnew[k], p);
256
257 if (omega > target_water_fraction) {
258 double fractiondrained = dc.get_drainage_rate(omega) * dt; // pure number
259
260 fractiondrained = std::min(fractiondrained,
261 omega - target_water_fraction);
262 Hdrainedtotal += fractiondrained * dz; // always a positive contribution
263 Enthnew[k] -= fractiondrained * L;
264 }
265 }
266 }
267
268 // apply bulge limiter
269 const double lowerEnthLimit = Enth_ks - bulgeEnthMax;
270 for (unsigned int k=0; k < system.ks(); k++) {
271 if (Enthnew[k] < lowerEnthLimit) {
272 // Count grid points which have very large cold limit advection bulge... enthalpy not
273 // too low.
275 Enthnew[k] = lowerEnthLimit;
276 }
277 }
278
279 // if there is subglacial water, don't allow ice base enthalpy to be below
280 // pressure-melting; that is, assume subglacial water is at the pressure-
281 // melting temperature and enforce continuity of temperature
282 if (till_water_thickness(i, j) > 0.0) {
283 Enthnew[0] = std::max(Enthnew[0], system.Enth_s(0));
284 }
285 } // end of post-processing
286
287 // compute basal melt rate
288 {
289 bool base_is_cold = (Enthnew[0] < system.Enth_s(0)) && (till_water_thickness(i,j) == 0.0);
290 // Determine melt rate, but only preliminarily because of
291 // drainage, from heat flux out of bedrock, heat flux into
292 // ice, and frictional heating
293 if (is_floating) {
294 // The floating basal melt rate will be set later; cover
295 // this case and set to zero for now. Note that
296 // Hdrainedtotal is discarded (the ocean model determines
297 // the basal melt).
298 m_basal_melt_rate(i, j) = 0.0;
299 } else {
300 if (base_is_cold) {
301 m_basal_melt_rate(i, j) = 0.0; // zero melt rate if cold base
302 } else {
303 const double
304 p_0 = EC->pressure(H),
305 p_1 = EC->pressure(H - dz), // FIXME issue #15
306 Tpmp_0 = EC->melting_temperature(p_0);
307
308 const bool k1_istemperate = EC->is_temperate(Enthnew[1], p_1); // level z = + \Delta z
309 double hf_up = 0.0;
310 if (k1_istemperate) {
311 const double
312 Tpmp_1 = EC->melting_temperature(p_1);
313
314 hf_up = -system.k_from_T(Tpmp_0) * (Tpmp_1 - Tpmp_0) / dz;
315 } else {
316 double T_0 = EC->temperature(Enthnew[0], p_0);
317 const double K_0 = system.k_from_T(T_0) / EC->c();
318
319 hf_up = -K_0 * (Enthnew[1] - Enthnew[0]) / dz;
320 }
321
322 // compute basal melt rate from flux balance:
323 //
324 // basal_melt_rate = - Mb / rho in [\ref AschwandenBuelerKhroulevBlatter];
325 //
326 // after we compute it we make sure there is no refreeze if
327 // there is no available basal water
328 m_basal_melt_rate(i, j) = (basal_frictional_heating(i, j) + basal_heat_flux(i, j) - hf_up) / (ice_density * EC->L(Tpmp_0));
329
330 if (till_water_thickness(i, j) <= 0 && m_basal_melt_rate(i, j) < 0) {
331 m_basal_melt_rate(i, j) = 0.0;
332 }
333 }
334
335 // Add drained water from the column to basal melt rate.
336 m_basal_melt_rate(i, j) += Hdrainedtotal / dt;
337 } // end of the grounded case
338 } // end of the basal melt rate computation
339
340 system.fine_to_coarse(Enthnew, i, j, m_work);
341 }
342 } catch (...) {
343 loop.failed();
344 }
345 loop.check();
346
347 m_stats.liquified_ice_volume = ((double) liquifiedCount) * dz * m_grid->cell_area();
348}
349
350std::set<VariableMetadata> EnthalpyModel::state_impl() const {
352}
353
355 m_ice_enthalpy.write(output);
356 m_basal_melt_rate.write(output);
357}
358
359} // end of namespace energy
360} // end of namespace pism
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
std::string name() const
Definition File.cc:274
High-level PISM I/O class.
Definition File.hh:57
void failed()
Indicates a failure of a parallel section.
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 set_column(int i, int j, double c)
Set all values of scalar quantity to given a single value in a particular column.
Definition Array3D.cc:56
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
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
virtual double get_drainage_rate(double omega)
Return D(omega), as in figure in [AschwandenBuelerKhroulevBlatter].
Compute the rate of drainage D(omega) for temperate ice.
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
void regrid_enthalpy()
Regrid enthalpy from the -regrid_file.
virtual 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)
virtual void restart_impl(const File &input_file, int record)
virtual 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)
virtual void write_state_impl(const OutputFile &output) const
The default (empty implementation).
virtual void update_impl(double t, double dt, const Inputs &inputs)
Update ice enthalpy field based on conservation of energy.
virtual std::set< VariableMetadata > state_impl() const
EnthalpyModel(std::shared_ptr< const Grid > grid, std::shared_ptr< const stressbalance::StressBalance > stress_balance)
const array::Scalar * surface_liquid_fraction
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
double Enth(size_t i) const
Definition enthSystem.hh:73
double Enth_s(size_t i) const
Definition enthSystem.hh:77
void init(int i, int j, bool ismarginal, double ice_thickness)
double k_from_T(double T) const
void set_basal_heat_flux(double heat_flux)
Set coefficients in discrete equation for Neumann condition at base of ice.
void solve(std::vector< double > &result)
Solve the tridiagonal system, in a single column, which determines the new values of the ice enthalpy...
void set_basal_dirichlet_bc(double E_basal)
Set coefficients in discrete equation for at base of ice.
void set_surface_dirichlet_bc(double E_surface)
Tridiagonal linear system for conservation of energy in vertical column of ice enthalpy.
Definition enthSystem.hh:38
static const double L
Definition exactTestL.cc:40
std::set< VariableMetadata > metadata(std::initializer_list< const Array * > vecs)
Definition Array.cc:1244
bool marginal(const array::Scalar1 &thickness, int i, int j, double threshold)
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
static const double k
Definition exactTestP.cc:42