PISM, A Parallel Ice Sheet Model  stable v2.1-1-g6902d5502 committed by Ed Bueler on 2023-12-20 08:38:27 -0800
localMassBalance.cc
Go to the documentation of this file.
1 // Copyright (C) 2009, 2010, 2011, 2013, 2014, 2015, 2016, 2017, 2018, 2020, 2022, 2023 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 
33 namespace pism {
34 namespace 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 
50 std::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  */
69 unsigned int PDDMassBalance::get_timeseries_length(double dt) {
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 /*!
79 The 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]
84 This procedure computes the quantity in square brackets. The value \f$T_{ac}(t)\f$
85 in the above integral is in degrees C. Here we think of the argument `TacC`
86 as temperature in Celsius, but really it is the temperature above a threshold
87 at which it is "positive".
88 
89 This 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
91 length `dt` instead of a whole year as stated in \ref CalovGreve05 . If `sigma` is zero,
92 return the positive part of `TacC`.
93  */
94 static 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  */
115 void 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  */
147 void 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 /*!
296 Initializes the random number generator (RNG). The RNG is GSL's recommended default,
297 which seems to be "mt19937" and is DIEHARD (whatever that means ...). Seed with
298 wall 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  */
350 void 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 
371 FaustoGrevePDDObject::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("K");
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 /*!
435 Unfortunately 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:65
SpatialVariableMetadata & metadata(unsigned int N=0)
Returns a reference to the SpatialVariableMetadata object containing metadata for the compoment N.
Definition: Array.cc:553
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
const Config::ConstPtr m_config
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
double max(const array::Scalar &input)
Finds maximum over all the values in an array::Scalar object. Ignores ghosts.
Definition: Scalar.cc:165
double min(const array::Scalar &input)
Finds minimum over all the values in an array::Scalar object. Ignores ghosts.
Definition: Scalar.cc:193
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