PISM, A Parallel Ice Sheet Model 2.3.0-79cae578d committed by Constantine Khrulev on 2026-03-22
Loading...
Searching...
No Matches
flux_limiter.cc
Go to the documentation of this file.
1/* Copyright (C) 2022, 2023, 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
20#include "pism/geometry/flux_limiter.hh"
21
22#include <cassert>
23#include <algorithm>
24#include <cmath>
25#include <limits>
26
27#include "pism/util/array/Scalar.hh"
28#include "pism/util/array/Staggered.hh"
29#include "pism/util/pism_utilities.hh" // GlobalSum()
30
31namespace pism {
32
33/*! References:
34 *
35 * [Smolarkiewicz1989] P. K. Smolarkiewicz, “Comment on “A Positive Definite Advection
36 * Scheme Obtained by Nonlinear Renormalization of the Advective Fluxes”,” Monthly Weather
37 * Review, vol. 117, Art. no. 11, 1989.
38 *
39 * [Zalesak1979] S. T. Zalesak, “Fully multidimensional flux-corrected transport
40 * algorithms for fluids,” Journal of Computational Physics, vol. 31, Art. no. 3, Jun.
41 * 1979.
42 */
43
44namespace details {
45
46// positive part
47static inline double pp(double x) {
48 return std::max(x, 0.0);
49}
50
51// negative part
52//
53// Note the negative sign!
54static inline double np(double x) {
55 return -std::min(x, 0.0);
56}
57
58// Total flux out of a cell over a time step dt
59static inline double flux_out(const stencils::Star<double> &u, double dx, double dy, double dt) {
60 return dt * ((pp(u.e) + np(u.w)) / dx + (pp(u.n) + np(u.s)) / dy);
61}
62
63} // end of namespace details
64
65/*!
66 * Smolarkiewicz-Zalesak flux limiter (equation 10 in [Smolarkiewicz1989])
67 *
68 * @param[in] F total flux across a boundary between two cells ("c" and "n") over the course of a time step
69 * @param[in] F_out_c total flux leaving the "current" cell over the course of a time step
70 * @param[in] F_out_n total flux leaving the "neighbor" cell over the course of a time step
71 * @param[in] x_c value at the "current" cell (non-negative)
72 * @param[in] x_n value at the "neighbor" cell (non-negative)
73 *
74 * Returns the limited total flux over the course of a time step
75 *
76 * Notes:
77 *
78 * Equation 10 contains the fraction pp(F) / F_out_c, so we need to avoid division by
79 * zero.
80 *
81 * Note that F_out_c >= 0 by construction.
82 *
83 * Also, if pp(F) > 0 then F_out_c > 0 because F_out_c contains a term `pp(F) * dt / delta`
84 * where delta is grid spacing (dx or dy).
85 *
86 * This implies that if F_out_c == 0 then pp(F) == 0 (equivalently, F <= 0).
87 *
88 * But if pp(F) == 0 then F is a flux *into* the cell "c" and it cannot make the value x_c
89 * negative, so we don't need to limit it using the value of x_c. This implies that the
90 * term `pp(F) / F_out_c * x_c` should be replaced by 0.
91 *
92 * Equation 10 also contains the fraction -np(F) / F_out_n, so we need to avoid division
93 * by zero here as well.
94 *
95 * Note that F_out_n >= 0 by construction.
96 *
97 * Also, if np(F) > 0 then F_out_n > 0 because F_out_n contains a term np(F) * dt / delta`
98 * where delta is grid spacing (dx or dy).
99 *
100 * This implies that if F_out_n == 0 then np(F) == 0 (equivalently, F >= 0).
101 *
102 * But if np(F) == 0 then F is a flux *into* the cell "n" and it cannot make the value x_n
103 * negative, so we don't need to limit it using the value of x_n. This implies that the
104 * term `-np(F) / F_out_n * x_n` should be replaced by 0.
105 *
106 */
107double flux_limiter(double F, double F_out_c, double F_out_n, double x_c, double x_n) {
108 using details::np;
109 using details::pp;
110
111 double F_c = F_out_c != 0.0 ? pp(F) / F_out_c * x_c : 0.0;
112 double F_n = F_out_n != 0.0 ? -np(F) / F_out_n * x_n : 0.0;
113
114 double F_limited = std::max(std::min(F, F_c), F_n);
115
116 assert(std::isfinite(F_limited));
117
118 return F_limited;
119}
120
121/*! Limit fluxes to preserve non-negativity of a transported quantity.
122 *
123 * This method is described in [Smolarkiewicz1989].
124 *
125 * It is based on the [Zalesak1979] flux corrected transport limiter, but for the
126 * "regular" flux instead of the "anti-diffusive" flux and with a different limiting
127 * criterion (non-negativity instead of monotonicity).
128 *
129 * @param[in] Q_c fluxes through sides of the current ("c") cell
130 * @param[in] Q_e fluxes through sides of the eastern ("e") neighbor of the current cell
131 * @param[in] Q_n fluxes through sides of the northern ("n") neighbor of the current cell
132 * @param[in] x_c value at the current cell
133 * @param[in] x_e value at the eastern neighbor
134 * @param[in] x_n value at the northern neighbor
135 * @param[in] dx grid spacing in the X direction
136 * @param[in] dy grid spacing in the Y direction
137 * @param[in] dt time step length
138 * @param[in] eps lower bound of the transported quantity (a small positive constant)
139 *
140 * Returns {Q_e, Q_n} - limited fluxed through eastern and northern sides of the current
141 * grid cell.
142 */
143std::array<double, 2> flux_limiter(const stencils::Star<double> &Q_c,
144 const stencils::Star<double> &Q_e,
145 const stencils::Star<double> &Q_n, double x_c, double x_e,
146 double x_n, double dx, double dy, double dt, double eps) {
147
148 using details::flux_out;
149 using details::np;
150 using details::pp;
151
152 // compute total amounts moved *out* of the current cell and its north and east
153 // neighbors over the course of the time step dt
154 //
155 // see equation (A4) in [Smolarkiewicz1989]
156 //
157 // note that we can compute all these using the width=1 stencil because of the way
158 // PISM's staggered grid is set up
159 double F_out = flux_out(Q_c, dx, dy, dt);
160 double F_out_n = flux_out(Q_n, dx, dy, dt);
161 double F_out_e = flux_out(Q_e, dx, dy, dt);
162
163 // amounts moved through the eastern and northern cell faces
164 double F_e = Q_c.e * dt / dx;
165 double F_n = Q_c.n * dt / dy;
166
167 // Maximum amounts the current cell and its neighbors can lose while maintaining
168 // non-negativity
169 //
170 // Note: we limit total amounts so that
171 //
172 // - if a cell value X is below eps, the flux is zero
173 //
174 // - otherwise the total flux out of a cell can remove at most (X - eps) over the
175 // course of a time step
176 //
177 // This is needed to avoid small negative values resulting from rounding errors.
178 double X_c = pp(x_c - eps);
179 double X_e = pp(x_e - eps);
180 double X_n = pp(x_n - eps);
181
182 // limit total amounts (see equation (10) in [Smolarkiewicz1989])
183 double F_e_limited = flux_limiter(F_e, F_out, F_out_e, X_c, X_e);
184 assert(x_c - F_e_limited >= 0);
185 assert(x_e + F_e_limited >= 0);
186
187 double F_n_limited = flux_limiter(F_n, F_out, F_out_n, X_c, X_n);
188 assert(x_c - F_n_limited >= 0);
189 assert(x_n + F_n_limited >= 0);
190
191 // convert back to fluxes and return:
192 return { F_e_limited * dx / dt, F_n_limited * dy / dt };
193}
194
195/*! Limit fluxes to preserve non-negativity of a transported quantity.
196 *
197 * See flux_limiter() for details.
198 */
200 array::Staggered &result) {
201
202 auto grid = result.grid();
203
204 double eps = std::numeric_limits<double>::epsilon(), dx = grid->dx(), dy = grid->dy();
205
206 array::AccessScope list{ &flux, &x, &result };
207
208 // flux divergence
209 auto div = [dx, dy](const stencils::Star<double> &Q) {
210 return (Q.e - Q.w) / dx + (Q.n - Q.s) / dy;
211 };
212
213 int limiter_count = 0;
214
215 for (auto p : grid->points()) {
216 const int i = p.i(), j = p.j();
217
218 auto Q = flux.star(i, j);
219 auto Q_n = flux.star(i, j + 1);
220 auto Q_e = flux.star(i + 1, j);
221
222 double x_c = x(i, j);
223 double x_e = x.E(i, j);
224 double x_n = x.N(i, j);
225
226 const double div_Q = div(Q), div_Q_e = div(Q_e), div_Q_n = div(Q_n);
227
228 if ((div_Q <= 0.0 or x_c - dt * div_Q >= eps) and
229 (div_Q_e <= 0.0 or x_e - dt * div_Q_e >= eps) and
230 (div_Q_n <= 0.0 or x_n - dt * div_Q_n >= eps)) {
231 // No need to limit fluxes: total fluxes out of cells (i, j), (i + 1, j), (i, j + 1)
232 // may be able to create a negative thickness, but fluxes *into* these cells make up for it
233 //
234 // Without this check the limiter is a little over-eager and may affect results in
235 // areas where mass conservation is not an issue.
236 result(i, j, 0) = Q.e;
237 result(i, j, 1) = Q.n;
238 continue;
239 }
240
241 limiter_count += 1;
242
243 auto Q_l = flux_limiter(Q, Q_e, Q_n, x_c, x_e, x_n, dx, dy, dt, eps);
244
245 result(i, j, 0) = Q_l[0];
246 result(i, j, 1) = Q_l[1];
247 }
248
249 return GlobalSum(grid->com, limiter_count);
250}
251
252} // end of namespace pism
Makes sure that we call begin_access() and end_access() for all accessed array::Arrays.
Definition Array.hh:66
const T & N(int i, int j) const
Definition Array2D.hh:68
const T & E(int i, int j) const
Definition Array2D.hh:75
std::shared_ptr< const Grid > grid() const
Definition Array.cc:134
stencils::Star< double > star(int i, int j) const
Returns the values at interfaces of the cell i,j using the staggered grid.
Definition Staggered.hh:75
A class for storing and accessing internal staggered-grid 2D fields. Uses dof=2 storage....
Definition Staggered.hh:37
static double pp(double x)
static double np(double x)
static double flux_out(const stencils::Star< double > &u, double dx, double dy, double dt)
int make_nonnegative_preserving(double dt, const array::Scalar1 &x, const array::Staggered1 &flux, array::Staggered &result)
static double F(double SL, double B, double H, double alpha)
double flux_limiter(double F, double F_out_c, double F_out_n, double x_c, double x_n)
void GlobalSum(MPI_Comm comm, double *local, double *result, int count)
Star stencil points (in the map-plane).
Definition stencils.hh:30