pism

[fork] customized build of PISM, the parallel ice sheet model (tillflux branch)
git clone git://src.adamsgaard.dk/pism # fast
git clone https://src.adamsgaard.dk/pism.git # slow
Log | Files | Refs | README | LICENSE Back to index

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