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