PISM, A Parallel Ice Sheet Model 2.3.0-79cae578d committed by Constantine Khrulev on 2026-03-22
Loading...
Searching...
No Matches
ForceThickness.cc
Go to the documentation of this file.
1// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2022, 2023, 2024, 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#include "pism/coupler/surface/ForceThickness.hh"
20#include "pism/util/Grid.hh"
21
22#include "pism/util/Config.hh"
23#include "pism/util/error_handling.hh"
24#include "pism/util/array/CellType.hh"
25#include "pism/util/MaxTimestep.hh"
26#include "pism/util/io/File.hh"
27#include "pism/geometry/Geometry.hh"
28#include "pism/util/Interpolation1D.hh"
29#include "pism/util/Logger.hh"
30#include "pism/util/io/IO_Flags.hh"
31#include "pism/util/pism_utilities.hh"
32
33namespace pism {
34namespace surface {
35
36///// "Force-to-thickness" mechanism
37ForceThickness::ForceThickness(std::shared_ptr<const Grid> g, std::shared_ptr<SurfaceModel> input)
38 : SurfaceModel(g, input),
39 m_target_thickness(m_grid, "thk"),
40 m_ftt_mask(m_grid, "ftt_mask")
41{
42
44
45 m_alpha = m_config->get_number("surface.force_to_thickness.alpha", "s-1");
46 m_alpha_ice_free_factor = m_config->get_number("surface.force_to_thickness.ice_free_alpha_factor");
47 m_ice_free_thickness_threshold = m_config->get_number("surface.force_to_thickness.ice_free_thickness_threshold");
48 m_start_time = m_config->get_number("surface.force_to_thickness.start_time", "seconds");
49
51 .long_name("mask specifying where to apply the force-to-thickness mechanism")
53 .set_time_dependent(false);
54
55 m_ftt_mask.set(1.0); // default: applied in whole domain
56
58
62}
63
64void ForceThickness::init_impl(const Geometry &geometry) {
65
66 m_input_model->init(geometry);
67
68 m_log->message(2, "* Initializing force-to-thickness mass-balance modifier...\n");
69
70 std::string input_file = m_config->get_string("surface.force_to_thickness.file");
71
72 if (input_file.empty()) {
73 throw RuntimeError(PISM_ERROR_LOCATION, "surface.force_to_thickness.file cannot be empty");
74 }
75
76 m_log->message(
77 2,
78 " alpha = %.6f year-1 for -force_to_thickness mechanism\n"
79 " alpha = %.6f year-1 in areas with target ice thickness of less than %.3f meters\n",
80 units::convert(m_sys, m_alpha, "s^-1", "year^-1"),
83
84 // check of the input file is really there and regrid the target thickness
85 File file(m_grid->com, input_file, io::PISM_GUESS, io::PISM_READONLY);
86
87 m_log->message(2,
88 " reading target thickness 'thk' from %s ...\n"
89 " (this field will appear in output file as 'ftt_target_thk')\n",
90 input_file.c_str());
91 {
92 m_target_thickness.metadata(0).set_name("thk"); // name to read by
93 // set attributes for the read stage; see below for reset
95 .long_name("target thickness for force-to-thickness mechanism (hit this at end of run)")
96 .units("m")
97 .standard_name("land_ice_thickness"); // standard_name *to read by*
98
100
101 // reset name to avoid confusion; set attributes again to overwrite "read by" choices above
102 m_target_thickness.metadata(0).set_name("ftt_target_thk");
103 m_target_thickness.metadata(0).standard_name(""); // no CF standard_name, to put it mildly
104 }
105
106 {
107 m_log->message(2,
108 " reading force-to-thickness mask 'ftt_mask' from %s ...\n",
109 input_file.c_str());
110 m_ftt_mask.regrid(input_file, io::Default::Nil());
111 }
112}
113
114/*!
115If `-force_to_thickness_file` `foo.nc` is in use then vthktarget will have a target ice thickness
116map. Let \f$H_{\text{tar}}\f$ be this target thickness,
117and let \f$H\f$ be the current model thickness. Recall that the mass continuity
118equation solved by GeometryEvolution is
119 \f[ \frac{\partial H}{\partial t} = M - S - \nabla\cdot \mathbf{q} \f]
120and that this procedure is supposed to produce \f$M\f$.
121In this context, the semantics of `-force_to_thickness` are that \f$M\f$ is modified
122by a multiple of the difference between the target thickness and the current thickness.
123In particular, the \f$\Delta M\f$ that is produced here is
124 \f[\Delta M = \alpha (H_{\text{tar}} - H)\f]
125where \f$\alpha>0\f$ is determined below. Note \f$\Delta M\f$ is positive in
126areas where \f$H_{\text{tar}} > H\f$, so we are adding mass there, and we are ablating
127in the other case.
128
129Let \f$t_s\f$ be the start time and \f$t_e\f$ the end time for the run.
130Without flow or basal mass balance, or any surface mass balance other than the
131\f$\Delta M\f$ computed here, we are solving
132 \f[ \frac{\partial H}{\partial t} = \alpha (H_{\text{tar}} - H) \f]
133Let's assume \f$H(t_s)=H_0\f$. This initial value problem has solution
134\f$H(t) = H_{\text{tar}} + (H_0 - H_{\text{tar}}) e^{-\alpha (t-t_s)}\f$
135and so
136 \f[ H(t_e) = H_{\text{tar}} + (H_0 - H_{\text{tar}}) e^{-\alpha (t_e-t_s)} \f]
137
138The constant \f$\alpha\f$ has a default value `pism_config:surface.force_to_thickness.alpha`.
139
140The next example uses files generated from the EISMINT-Greenland experiment;
141see the corresponding chapter of the User's Manual.
142
143Suppose we regard the SSL2 run as a spin-up to reach a better temperature field.
144It is a spinup in which the surface was allowed to evolve. Assume the
145early file `green20km_y1.nc` has the target thickness, because it essentially
146has the input thickness. This script adds a 500 a run, to finalize the spinup,
147in which the ice sheet geometry goes from the the thickness values in
148`green_ssl2_110ka.nc` to values very close to those in `green20km_y1.nc`:
149\code
150#!/bin/bash
151
152NN=8 # default number of processors
153if [ $# -gt 0 ] ; then # if user says "test_ftt.sh 8" then NN = 8
154 NN="$1"
155fi
156
157# set MPIDO if using different MPI execution command, for example:
158# $ export PISM_MPIDO="aprun -n "
159if [ -n "${PISM_MPIDO:+1}" ] ; then # check if env var is already set
160 echo "$SCRIPTNAME PISM_MPIDO = $PISM_MPIDO (already set)"
161else
162 PISM_MPIDO="mpiexec -n "
163 echo "$SCRIPTNAME PISM_MPIDO = $PISM_MPIDO"
164fi
165
166# check if env var PISM_DO was set (i.e. PISM_DO=echo for a 'dry' run)
167if [ -n "${PISM_DO:+1}" ] ; then # check if env var DO is already set
168 echo "$SCRIPTNAME PISM_DO = $PISM_DO (already set)"
169else
170 PISM_DO=""
171fi
172
173# prefix to pism (not to executables)
174if [ -n "${PISM_PREFIX:+1}" ] ; then # check if env var is already set
175 echo "$SCRIPTNAME PISM_PREFIX = $PISM_PREFIX (already set)"
176else
177 PISM_PREFIX="" # just a guess
178 echo "$SCRIPTNAME PISM_PREFIX = $PISM_PREFIX"
179fi
180
181# set PISM_EXEC if using different executables, for example:
182# $ export PISM_EXEC="pism -energy cold"
183if [ -n "${PISM_EXEC:+1}" ] ; then # check if env var is already set
184 echo "$SCRIPTNAME PISM_EXEC = $PISM_EXEC (already set)"
185else
186 PISM_EXEC="pism"
187 echo "$SCRIPTNAME PISM_EXEC = $PISM_EXEC"
188fi
189
190
191PISM="${PISM_PREFIX}${PISM_EXEC}"
192
193cmd="$PISM_MPIDO $NN $PISM -ys -1000.0 -ye 0 -skip 5 -i green_ssl2_110ka.nc -atmosphere searise_greenland \
194 -surface pdd -pdd_fausto \
195 -o no_force.nc -scalar_file ts_no_force.nc -scalar_times -1000:yearly:0"
196$PISM_DO $cmd
197
198echo
199
200cmd="$PISM_MPIDO $NN $PISM -ys -1000.0 -ye 0 -skip 5 -i green_ssl2_110ka.nc -atmosphere searise_greenland \
201 -surface pdd,forcing -pdd_fausto -force_to_thickness_file green20km_y1.nc \
202 -o default_force.nc -scalar_file ts_default_force.nc -scalar_times -1000:yearly:0"
203$PISM_DO $cmd
204
205echo
206
207cmd="$PISM_MPIDO $NN $PISM -ys -1000.0 -ye 0 -skip 5 -i green_ssl2_110ka.nc -atmosphere searise_greenland \
208 -surface pdd,forcing -pdd_fausto -force_to_thickness_file green20km_y1.nc -force_to_thickness_alpha 0.005 \
209 -o weak_force.nc -scalar_file ts_weak_force.nc -scalar_times -1000:yearly:0"
210$PISM_DO $cmd
211
212
213cmd="$PISM_MPIDO $NN $PISM -ys -1000.0 -ye 0 -skip 5 -i green_ssl2_110ka.nc -atmosphere searise_greenland \
214 -surface pdd,forcing -pdd_fausto -force_to_thickness_file green20km_y1.nc -force_to_thickness_alpha 0.05 \
215 -o strong_force.nc -scalar_file ts_strong_force.nc -scalar_times -1000:yearly:0"
216$PISM_DO $cmd
217
218\endcode
219The script also has a run with no forcing, one with forcing at a lower alpha value,
220a factor of five smaller than the default, and one with a forcing at a higher alpha value, a factor of five higher.
221 */
223 const array::Scalar &ice_thickness,
224 const array::CellType &cell_type,
225 array::Scalar &result) const {
226
227 if (time < m_start_time) {
228 return;
229 }
230
231 m_log->message(5,
232 " updating surface mass balance using -force_to_thickness mechanism ...");
233
234 double ice_density = m_config->get_number("constants.ice.density");
235
236 array::AccessScope list{&cell_type, &ice_thickness,
237 &m_target_thickness, &m_ftt_mask, &result};
238
239 for (auto p : m_grid->points()) {
240 const int i = p.i(), j = p.j();
241
242 if (m_ftt_mask(i,j) > 0.5 and cell_type.grounded(i, j)) {
244 result(i,j) += ice_density * m_alpha * (m_target_thickness(i,j) - ice_thickness(i,j));
245 } else {
246 result(i,j) += ice_density * m_alpha * m_alpha_ice_free_factor * (m_target_thickness(i,j) - ice_thickness(i,j));
247 }
248 }
249 }
250 // no communication needed
251}
252
253void ForceThickness::update_impl(const Geometry &geometry, double t, double dt) {
254 m_input_model->update(geometry, t, dt);
255
256 m_mass_flux->copy_from(m_input_model->mass_flux());
257
259 geometry.ice_thickness,
260 geometry.cell_type,
261 *m_mass_flux);
262
266}
267
271
275
277 return *m_melt;
278}
279
281 return *m_runoff;
282}
283
284/*!
285The timestep restriction is, by direct analogy, the same as for
286 \f[\frac{dy}{dt} = - \alpha y\f]
287with explicit (forward) Euler. If \f$\Delta t\f$ is the time step then Euler is
288\f$y_{n+1} = (1-\alpha \Delta t) y_n\f$. We require for stability that
289\f$|y_{n+1}|\le |y_n|\f$, which is to say \f$|1-\alpha \Delta t|\le 1\f$.
290Equivalently (since \f$\alpha \Delta t>0\f$),
291 \f[\alpha \Delta t\le 2\f]
292Therefore we set here
293 \f[\Delta t = \frac{2}{\alpha}.\f]
294 */
296 double max_dt = 2.0 / m_alpha;
297 MaxTimestep input_max_dt = m_input_model->max_timestep(my_t);
298
299 return std::min(input_max_dt, MaxTimestep(max_dt, "surface forcing"));
300}
301
302std::set<VariableMetadata> ForceThickness::state_impl() const {
303 auto variables = array::metadata({&m_ftt_mask, &m_target_thickness});
304
305 if (m_input_model != nullptr) {
306 return pism::combine(m_input_model->state(), variables);
307 }
308 return variables;
309}
310
312 m_ftt_mask.write(output);
314
315 if (m_input_model != NULL) {
316 m_input_model->write_state(output);
317 }
318}
319
320} // end of namespace surface
321} // end of namespace pism
const units::System::Ptr m_sys
unit system used by this component
Definition Component.hh:162
const Time & time() const
Definition Component.cc:111
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
std::shared_ptr< const Logger > m_log
logger (for easy access)
Definition Component.hh:164
High-level PISM I/O class.
Definition File.hh:57
array::CellType2 cell_type
Definition Geometry.hh:55
array::Scalar2 ice_thickness
Definition Geometry.hh:51
Combines the max. time step with the flag indicating if a restriction is active. Makes is possible to...
VariableMetadata & long_name(const std::string &input)
VariableMetadata & units(const std::string &input)
VariableMetadata & set_name(const std::string &name)
VariableMetadata & standard_name(const std::string &input)
VariableMetadata & set_output_type(io::Type type)
VariableMetadata & set_time_dependent(bool flag)
Makes sure that we call begin_access() and end_access() for all accessed array::Arrays.
Definition Array.hh:66
void set_interpolation_type(InterpolationType type)
Definition Array.cc:181
void write(const OutputFile &file) const
Definition Array.cc:859
void set(double c)
Result: v[j] <- c for all j.
Definition Array.cc:659
void regrid(const std::string &filename, io::Default default_value)
Definition Array.cc:758
VariableMetadata & metadata(unsigned int N=0)
Returns a reference to the VariableMetadata object containing metadata for the compoment N.
Definition Array.cc:467
bool grounded(int i, int j) const
Definition CellType.hh:38
"Cell type" mask. Adds convenience methods to array::Scalar.
Definition CellType.hh:30
static Default Nil()
Definition IO_Flags.hh:94
void adjust_mass_flux(double time, const array::Scalar &ice_thickness, const array::CellType &cell_type, array::Scalar &result) const
const array::Scalar & melt_impl() const
const array::Scalar & accumulation_impl() const
std::shared_ptr< array::Scalar > m_mass_flux
MaxTimestep max_timestep_impl(double t) const
void write_state_impl(const OutputFile &output) const
The default (empty implementation).
void init_impl(const Geometry &geometry)
const array::Scalar & runoff_impl() const
void update_impl(const Geometry &geometry, double t, double dt)
ForceThickness(std::shared_ptr< const Grid > g, std::shared_ptr< SurfaceModel > input)
const array::Scalar & mass_flux_impl() const
std::set< VariableMetadata > state_impl() const
static std::shared_ptr< array::Scalar > allocate_runoff(std::shared_ptr< const Grid > grid)
static std::shared_ptr< array::Scalar > allocate_mass_flux(std::shared_ptr< const Grid > grid)
void dummy_accumulation(const array::Scalar &smb, array::Scalar &result)
std::shared_ptr< array::Scalar > m_melt
static std::shared_ptr< array::Scalar > allocate_accumulation(std::shared_ptr< const Grid > grid)
static std::shared_ptr< array::Scalar > allocate_melt(std::shared_ptr< const Grid > grid)
std::shared_ptr< array::Scalar > m_runoff
void dummy_melt(const array::Scalar &smb, array::Scalar &result)
std::shared_ptr< SurfaceModel > m_input_model
std::shared_ptr< array::Scalar > m_accumulation
void dummy_runoff(const array::Scalar &smb, array::Scalar &result)
The interface of PISM's surface models.
#define PISM_ERROR_LOCATION
std::set< VariableMetadata > metadata(std::initializer_list< const Array * > vecs)
Definition Array.cc:1244
@ PISM_GUESS
Definition IO_Flags.hh:57
@ PISM_READONLY
open an existing file for reading only
Definition IO_Flags.hh:69
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 g
Definition exactTestP.cc:36
T combine(const T &a, const T &b)