Loading [MathJax]/extensions/tex2jax.js
PISM, A Parallel Ice Sheet Model 2.2.1-cd005eec8 committed by Constantine Khrulev on 2025-03-07
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
localMassBalance.cc
Go to the documentation of this file.
1// Copyright (C) 2009, 2010, 2011, 2013, 2014, 2015, 2016, 2017, 2018, 2020, 2022, 2023, 2024 Ed Bueler and Constantine Khroulev and Andy Aschwanden
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 <cassert>
20#include <ctime> // for time(), used to initialize random number gen
21#include <gsl/gsl_rng.h>
22#include <gsl/gsl_randist.h>
23#include <gsl/gsl_math.h> // M_PI
24#include <cmath> // for erfc() in CalovGreveIntegrand()
25#include <algorithm>
26
27#include "pism/util/ConfigInterface.hh"
28#include "pism/coupler/surface/localMassBalance.hh"
29#include "pism/util/Grid.hh"
30#include "pism/util/Context.hh"
31#include "pism/util/VariableMetadata.hh"
32
33namespace pism {
34namespace surface {
35
37 firn_depth = 0.0;
38 snow_depth = 0.0;
39 melt = 0.0;
40 runoff = 0.0;
41 smb = 0.0;
42}
43
45 : m_config(myconfig), m_unit_system(system),
46 m_seconds_per_day(86400) {
47 // empty
48}
49
50std::string LocalMassBalance::method() const {
51 return m_method;
52}
53
55 : LocalMassBalance(config, system) {
56 precip_as_snow = m_config->get_flag("surface.pdd.interpret_precip_as_snow");
57 Tmin = m_config->get_number("surface.pdd.air_temp_all_precip_as_snow");
58 Tmax = m_config->get_number("surface.pdd.air_temp_all_precip_as_rain");
59 pdd_threshold_temp = m_config->get_number("surface.pdd.positive_threshold_temp");
60 refreeze_ice_melt = m_config->get_flag("surface.pdd.refreeze_ice_melt");
61
62 m_method = "an expectation integral";
63}
64
65
66/*! \brief Compute the number of points for temperature and
67 precipitation time-series.
68 */
70 const unsigned int NperYear = static_cast<unsigned int>(m_config->get_number("surface.pdd.max_evals_per_year"));
71 const double dt_years = units::convert(m_unit_system, dt, "seconds", "years");
72
73 return std::max(1U, static_cast<unsigned int>(ceil(NperYear * dt_years)));
74}
75
76
77//! Compute the integrand in integral (6) in [\ref CalovGreve05].
78/*!
79The integral is
80 \f[\mathrm{PDD} = \int_{t_0}^{t_0+\mathtt{dt}} dt\,
81 \bigg[\frac{\sigma}{\sqrt{2\pi}}\,\exp\left(-\frac{T_{ac}(t)^2}{2\sigma^2}\right)
82 + \frac{T_{ac}(t)}{2}\,\mathrm{erfc}
83 \left(-\frac{T_{ac}(t)}{\sqrt{2}\,\sigma}\right)\bigg] \f]
84This procedure computes the quantity in square brackets. The value \f$T_{ac}(t)\f$
85in the above integral is in degrees C. Here we think of the argument `TacC`
86as temperature in Celsius, but really it is the temperature above a threshold
87at which it is "positive".
88
89This integral is used for the expected number of positive degree days. The user can choose
90\f$\sigma\f$ by option `-pdd_std_dev`. Note that the integral is over a time interval of
91length `dt` instead of a whole year as stated in \ref CalovGreve05 . If `sigma` is zero,
92return the positive part of `TacC`.
93 */
94static double CalovGreveIntegrand(double sigma, double TacC) {
95
96 if (sigma == 0) {
97 return std::max(TacC, 0.0);
98 }
99
100 const double Z = TacC / (sqrt(2.0) * sigma);
101 return (sigma / sqrt(2.0 * M_PI)) * exp(-Z*Z) + (TacC / 2.0) * erfc(-Z);
102}
103
104
105//! Compute the expected number of positive degree days from the input temperature time-series.
106/**
107 * Use the rectangle method for simplicity.
108 *
109 * @param S standard deviation for air temperature excursions
110 * @param dt_series length of the step for the time-series
111 * @param T air temperature (array of length N)
112 * @param N length of the T array
113 * @param[out] PDDs pointer to a pre-allocated array with N-1 elements
114 */
115void PDDMassBalance::get_PDDs(double dt_series,
116 const std::vector<double> &S,
117 const std::vector<double> &T,
118 std::vector<double> &PDDs) {
119 assert(S.size() == T.size() and T.size() == PDDs.size());
120 assert(dt_series > 0.0);
121
122 const double h_days = dt_series / m_seconds_per_day;
123 const size_t N = S.size();
124
125 for (unsigned int k = 0; k < N; ++k) {
126 PDDs[k] = h_days * CalovGreveIntegrand(S[k], T[k] - pdd_threshold_temp);
127 }
128}
129
130
131//! \brief Extract snow accumulation from mixed (snow and rain)
132//! precipitation using the temperature time-series.
133/** Uses the temperature time-series to determine whether the
134 * precipitation is snow or rain. Rain is removed entirely from the
135 * surface mass balance, and will not be included in the computed
136 * runoff, which is meltwater runoff. There is an allowed linear
137 * transition for Tmin below which all precipitation is interpreted as
138 * snow, and Tmax above which all precipitation is rain (see, e.g.
139 * [\ref Hock2005b]).
140 *
141 * Sets P[i] to the *solid* (snow) accumulation *rate*.
142 *
143 * @param[in,out] P precipitation rate (array of length N)
144 * @param[in] T air temperature (array of length N)
145 * @param[in] N array length
146 */
147void PDDMassBalance::get_snow_accumulation(const std::vector<double> &T,
148 std::vector<double> &P) {
149
150 assert(T.size() == P.size());
151 const size_t N = T.size();
152
153 // Following \ref Hock2005b we employ a linear transition from Tmin to Tmax
154 for (unsigned int i = 0; i < N; i++) {
155 // do not allow negative precipitation
156 if (P[i] < 0.0) {
157 P[i] = 0.0;
158 continue;
159 }
160
161 if (precip_as_snow || T[i] <= Tmin) { // T <= Tmin, all precip is snow
162 // no change
163 } else if (T[i] < Tmax) { // linear transition from Tmin to Tmax
164 P[i] *= (Tmax - T[i]) / (Tmax - Tmin);
165 } else { // T >= Tmax, all precip is rain -- ignore it
166 P[i] = 0.0;
167 }
168 }
169
170}
171
172
173//! \brief Compute the surface mass balance at a location from the number of positive
174//! degree days and the accumulation amount in a time interval.
175/*!
176 * This is a PDD scheme. The input parameter `ddf.snow` is a rate of
177 * melting per positive degree day for snow.
178 *
179 * `accumulation` has units "meter / second".
180 *
181 * - a fraction of the melted snow and ice refreezes, conceptualized
182 * as superimposed ice, and this is controlled by parameter \c
183 * ddf.refreeze_fraction
184 *
185 * - the excess number of PDDs is used to melt both the ice that came
186 * from refreeze and then any ice which is already present.
187 *
188 * Ice melts at a constant rate per positive degree day, controlled by
189 * parameter `ddf.ice`.
190 *
191 * The scheme here came from EISMINT-Greenland [\ref RitzEISMINT], but
192 * is influenced by R. Hock (personal communication).
193 */
195 double PDDs,
196 double thickness,
197 double old_firn_depth,
198 double old_snow_depth,
199 double accumulation) {
200 double
201 firn_depth = old_firn_depth,
202 snow_depth = old_snow_depth,
203 max_snow_melted = PDDs * ddf.snow,
204 firn_melted = 0.0,
205 snow_melted = 0.0,
206 excess_pdds = 0.0;
207
208 assert(thickness >= 0);
209
210 // snow depth cannot exceed total thickness
211 snow_depth = std::min(snow_depth, thickness);
212
213 assert(snow_depth >= 0);
214
215 // firn depth cannot exceed thickness - snow_depth
216 firn_depth = std::min(firn_depth, thickness - snow_depth);
217
218 assert(firn_depth >= 0);
219
220 double ice_thickness = thickness - snow_depth - firn_depth;
221
222 assert(ice_thickness >= 0);
223
224 snow_depth += accumulation;
225
226 if (PDDs <= 0.0) { // The "no melt" case.
227 snow_melted = 0.0;
228 firn_melted = 0.0,
229 excess_pdds = 0.0;
230 } else if (max_snow_melted <= snow_depth) {
231 // Some of the snow melted and some is left; in any case, all of
232 // the energy available for melt, namely all of the positive
233 // degree days (PDDs) were used up in melting snow.
234 snow_melted = max_snow_melted;
235 firn_melted = 0.0;
236 excess_pdds = 0.0;
237 } else if (max_snow_melted <= firn_depth + snow_depth) {
238 // All of the snow is melted but some firn is left; in any case, all of
239 // the energy available for melt, namely all of the positive
240 // degree days (PDDs) were used up in melting snow and firn.
241 snow_melted = snow_depth;
242 firn_melted = max_snow_melted - snow_melted;
243 excess_pdds = 0.0;
244 } else {
245 // All (firn_depth and snow_depth meters) of snow and firn melted. Excess_pdds is the
246 // positive degree days available to melt ice.
247 firn_melted = firn_depth;
248 snow_melted = snow_depth;
249 excess_pdds = PDDs - ((firn_melted + snow_melted) / ddf.snow); // units: K day
250 }
251
252 double
253 ice_melted = std::min(excess_pdds * ddf.ice, ice_thickness),
254 melt = snow_melted + firn_melted + ice_melted,
255 ice_created_by_refreeze = 0.0;
256
257 if (refreeze_ice_melt) {
258 ice_created_by_refreeze = melt * ddf.refreeze_fraction;
259 } else {
260 // Should this only be snow melted?
261 ice_created_by_refreeze = (firn_melted + snow_melted) * ddf.refreeze_fraction;
262 }
263
264 const double runoff = melt - ice_created_by_refreeze;
265
266 snow_depth = std::max(snow_depth - snow_melted, 0.0);
267 firn_depth = std::max(firn_depth - firn_melted, 0.0);
268
269 // FIXME: need to add snow that hasn't melted, is this correct?
270 // firn_depth += (snow_depth - snow_melted);
271 // Turn firn into ice at X times accumulation
272 // firn_depth -= accumulation * m_config->get_number("surface.pdd.firn_compaction_to_accumulation_ratio");
273
274 const double smb = accumulation - runoff;
275
276 Changes result;
277 // Ensure that we never generate negative ice thicknesses. As far as I can tell the code
278 // above guarantees that thickness + smb >= 0 *in exact arithmetic*. The check below
279 // should make sure that we don't get bitten by rounding errors.
280 result.smb = thickness + smb >= 0 ? smb : -thickness;
281 result.firn_depth = firn_depth - old_firn_depth;
282 result.snow_depth = snow_depth - old_snow_depth;
283 result.melt = melt;
284 result.runoff = runoff;
285
286 assert(thickness + result.smb >= 0);
287
288 return result;
289}
290
292 gsl_rng *rng;
293};
294
295/*!
296Initializes the random number generator (RNG). The RNG is GSL's recommended default,
297which seems to be "mt19937" and is DIEHARD (whatever that means ...). Seed with
298wall clock time in seconds in non-repeatable case, and with 0 in repeatable case.
299 */
301 Kind kind)
302 : PDDMassBalance(config, system),
303 m_impl(new Impl)
304{
305 m_impl->rng = gsl_rng_alloc(gsl_rng_default); // so m_impl->rng != NULL now
306 gsl_rng_set(m_impl->rng, kind == REPEATABLE ? 0 : time(0));
307
308 m_method = (kind == NOT_REPEATABLE
309 ? "simulation of a random process"
310 : "repeatable simulation of a random process");
311}
312
313
315 if (m_impl->rng != NULL) {
316 gsl_rng_free(m_impl->rng);
317 m_impl->rng = NULL;
318 }
319 delete m_impl;
320}
321
322
323/*! We need to compute simulated random temperature each actual \e
324 day, or at least as close as we can reasonably get. Output `N` is
325 number of days or number of days plus one.
326
327 Thus this method ignores
328 `config.get_number("surface.pdd.max_evals_per_year")`, which is
329 used in the base class PDDMassBalance.
330
331 Implementation of get_PDDs() requires returned N >= 2, so we
332 guarantee that.
333 */
335 return std::max(static_cast<size_t>(ceil(dt / m_seconds_per_day)), (size_t)2);
336}
337
338/**
339 * Computes
340 * \f[
341 * \text{PDD} = \sum_{i=0}^{N-1} h_{\text{days}} \cdot \text{max}(T_i-T_{\text{threshold}}, 0).
342 * \f]
343 *
344 * @param S \f$\sigma\f$ (standard deviation for daily temperature excursions)
345 * @param dt_series time-series step, in seconds
346 * @param T air temperature
347 * @param N number of points in the temperature time-series, each corresponds to a sub-interval
348 * @param PDDs pointer to a pre-allocated array of length N
349 */
350void PDDrandMassBalance::get_PDDs(double dt_series,
351 const std::vector<double> &S,
352 const std::vector<double> &T,
353 std::vector<double> &PDDs) {
354 assert(S.size() == T.size() and T.size() == PDDs.size());
355 assert(dt_series > 0.0);
356
357 const double h_days = dt_series / m_seconds_per_day;
358 const size_t N = S.size();
359
360 for (unsigned int k = 0; k < N; ++k) {
361 // average temperature in k-th interval
362 double T_k = T[k] + gsl_ran_gaussian(m_impl->rng, S[k]); // add random: N(0,sigma)
363
364 if (T_k > pdd_threshold_temp) {
365 PDDs[k] = h_days * (T_k - pdd_threshold_temp);
366 }
367 }
368}
369
370
371FaustoGrevePDDObject::FaustoGrevePDDObject(std::shared_ptr<const Grid> grid)
372 : m_grid(grid), m_config(grid->ctx()->config()),
373 m_temp_mj(grid, "temp_mj_faustogreve")
374{
375
376 m_beta_ice_w = m_config->get_number("surface.pdd.fausto.beta_ice_w");
377 m_beta_snow_w = m_config->get_number("surface.pdd.fausto.beta_snow_w");
378
379 m_T_c = m_config->get_number("surface.pdd.fausto.T_c");
380 m_T_w = m_config->get_number("surface.pdd.fausto.T_w");
381 m_beta_ice_c = m_config->get_number("surface.pdd.fausto.beta_ice_c");
382 m_beta_snow_c = m_config->get_number("surface.pdd.fausto.beta_snow_c");
383
384 m_fresh_water_density = m_config->get_number("constants.fresh_water.density");
385 m_ice_density = m_config->get_number("constants.ice.density");
386 m_pdd_fausto_latitude_beta_w = m_config->get_number("surface.pdd.fausto.latitude_beta_w");
387 m_refreeze_fraction = m_config->get_number("surface.pdd.refreeze");
388
390 .long_name("mean July air temp from Fausto et al (2009) parameterization")
391 .units("kelvin");
392}
393
395 double latitude) {
396
399
401 const double T_mj = m_temp_mj(i,j);
402
403 if (latitude < m_pdd_fausto_latitude_beta_w) { // case latitude < 72 deg N
404 ddf.ice = m_beta_ice_w;
405 ddf.snow = m_beta_snow_w;
406 } else { // case > 72 deg N
407 if (T_mj >= m_T_w) {
408 ddf.ice = m_beta_ice_w;
409 ddf.snow = m_beta_snow_w;
410 } else if (T_mj <= m_T_c) {
411 ddf.ice = m_beta_ice_c;
412 ddf.snow = m_beta_snow_c;
413 } else { // middle case T_c < T_mj < T_w
414 const double
415 lam_i = pow((m_T_w - T_mj) / (m_T_w - m_T_c) , 3.0),
416 lam_s = (T_mj - m_T_c) / (m_T_w - m_T_c);
417 ddf.ice = m_beta_ice_w + (m_beta_ice_c - m_beta_ice_w) * lam_i;
418 ddf.snow = m_beta_snow_w + (m_beta_snow_c - m_beta_snow_w) * lam_s;
419 }
420 }
421
422 // degree-day factors in \ref Faustoetal2009 are water-equivalent
423 // thickness per degree day; ice-equivalent thickness melted per degree
424 // day is slightly larger; for example, iwfactor = 1000/910
425 const double iwfactor = m_fresh_water_density / m_ice_density;
426 ddf.snow *= iwfactor;
427 ddf.ice *= iwfactor;
428
429 return ddf;
430}
431
432
433//! Updates mean July near-surface air temperature.
434/*!
435Unfortunately this duplicates code in SeaRISEGreenland::update();
436 */
438 const array::Scalar &lat,
439 const array::Scalar &lon) {
440 const double
441 d_mj = m_config->get_number("atmosphere.fausto_air_temp.d_mj"), // K
442 gamma_mj = m_config->get_number("atmosphere.fausto_air_temp.gamma_mj"), // K m-1
443 c_mj = m_config->get_number("atmosphere.fausto_air_temp.c_mj"), // K (degN)-1
444 kappa_mj = m_config->get_number("atmosphere.fausto_air_temp.kappa_mj"); // K (degW)-1
445
446 const array::Scalar
447 &h = surfelev,
448 &lat_degN = lat,
449 &lon_degE = lon;
450
451 array::AccessScope list{&h, &lat_degN, &lon_degE, &m_temp_mj};
452
453 for (auto p = m_grid->points(); p; p.next()) {
454 const int i = p.i(), j = p.j();
455 m_temp_mj(i,j) = d_mj + gamma_mj * h(i,j) + c_mj * lat_degN(i,j) + kappa_mj * (-lon_degE(i,j));
456 }
457}
458
459} // end of namespace surface
460} // end of namespace pism
std::shared_ptr< const Config > ConstPtr
VariableMetadata & long_name(const std::string &input)
VariableMetadata & units(const std::string &input)
Makes sure that we call begin_access() and end_access() for all accessed array::Arrays.
Definition Array.hh:64
SpatialVariableMetadata & metadata(unsigned int N=0)
Returns a reference to the SpatialVariableMetadata object containing metadata for the compoment N.
Definition Array.cc:476
LocalMassBalance::DegreeDayFactors degree_day_factors(int i, int j, double latitude)
std::shared_ptr< const Grid > m_grid
FaustoGrevePDDObject(std::shared_ptr< const Grid > g)
void update_temp_mj(const array::Scalar &surfelev, const array::Scalar &lat, const array::Scalar &lon)
Updates mean July near-surface air temperature.
const units::System::Ptr m_unit_system
LocalMassBalance(Config::ConstPtr config, units::System::Ptr system)
Base class for a model which computes surface mass flux rate (ice thickness per time) from precipitat...
void get_snow_accumulation(const std::vector< double > &T, std::vector< double > &precip_rate)
Extract snow accumulation from mixed (snow and rain) precipitation using the temperature time-series.
bool refreeze_ice_melt
refreeze melted ice
bool precip_as_snow
interpret all the precipitation as snow (no rain)
virtual void get_PDDs(double dt_series, const std::vector< double > &S, const std::vector< double > &T, std::vector< double > &PDDs)
Compute the expected number of positive degree days from the input temperature time-series.
double Tmax
the temperature above which all precipitation is rain
Changes step(const DegreeDayFactors &ddf, double PDDs, double ice_thickness, double firn_depth, double snow_depth, double accumulation)
Compute the surface mass balance at a location from the number of positive degree days and the accumu...
double pdd_threshold_temp
threshold temperature for the PDD computation
double Tmin
the temperature below which all precipitation is snow
PDDMassBalance(Config::ConstPtr config, units::System::Ptr system)
virtual unsigned int get_timeseries_length(double dt)
Compute the number of points for temperature and precipitation time-series.
A PDD implementation which computes the local mass balance based on an expectation integral.
virtual unsigned int get_timeseries_length(double dt)
PDDrandMassBalance(Config::ConstPtr config, units::System::Ptr system, Kind kind)
virtual void get_PDDs(double dt_series, const std::vector< double > &S, const std::vector< double > &T, std::vector< double > &PDDs)
std::shared_ptr< System > Ptr
Definition Units.hh:47
static double CalovGreveIntegrand(double sigma, double TacC)
Compute the integrand in integral (6) in [CalovGreve05].
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
double ice
m day^-1 K^-1; ice-equivalent amount of ice melted, per PDD
double refreeze_fraction
fraction of melted snow which refreezes as ice
double snow
m day^-1 K^-1; ice-equivalent amount of snow melted, per PDD
A struct which holds degree day factors.
static double S(unsigned n)
Definition test_cube.c:58